Best practice implementing JavascriptInterface using WebView in Android? - java

I have a crash report of the same error like in this question: WebView methods on same thread error
There it is suggested to create a Runnable().
I don't understand why exactly this solves the problem. The error says "Webview methods on same Thread", but the answer suggests to create the method on the UI-Thread (Main Thread). But isn't the UI-Thread the one and only thread? Could someone explain this whole process in detail (considering I create a new Webview in every activity in the constructor)?
My code to implement Javascript functions/methods looks like this:
public class JS_Bind {
private static final String TAG = "JS_Bind";
private Context context;
private AdvancedWebView mWebView;
public JS_Bind(Context c, AdvancedWebView mWebView) {
context = c;
this.mWebView = mWebView;
}
#JavascriptInterface
public void openActivity(String activityName) {
try {
Class activityClass = Class.forName(PACKAGE_NAME + "." + activityName);
context.startActivity(new Intent(MainActivity.this, activityClass));
} catch (ClassNotFoundException e) {
Toast.makeText(context, "Invalid activity name: " + activityName, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
#JavascriptInterface
public void makeToast(String toast) {
Toast mToast = Toast.makeText(context, toast, Toast.LENGTH_SHORT);
mToast.setGravity(Gravity.CENTER, 0, 0);
mToast.show();
}
#JavascriptInterface
public void external(String url) {
mTracker.send(new HitBuilders.EventBuilder().setCategory("Action").setAction("External Link: " + url).build());
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
#JavascriptInterface
public String showToken() {
return gcmToken;
}
#JavascriptInterface
public int showUid() {
SharedPreferences pref = getSharedPreferences("Pref", Activity.MODE_PRIVATE);
int uid = pref.getInt("uid", 0);
return uid;
}
#JavascriptInterface
public void buyPremium() {
bp.purchase(MainActivity.this, PRODUCT_ID);
}
}
Do I have to change EVERY function to this code (first answer in the question I refered to):
#JavascriptInterface
mWebView.post(new Runnable() {
#Override
public void makeToast() {
// ...
}
});
?
By the way, this is how I create the webview in the constructor activies onCreate method:
mWebView = (AdvancedWebView) findViewById(R.id.webView);
mWebView.setListener(this, this);
mWebView.addJavascriptInterface(new JS_Bind(this, mWebView), "Android");
mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
if (!DetectConnection.checkInternetConnection(this)) {
mWebView.loadUrl("file:///android_asset/offline.html");
}
else {
mWebView.loadUrl("http://example.com/tw3/index.php?s=home");
}

But isn't the UI-Thread the one and only thread?
No. WebView has its own pool of threads. There can be many other threads in an Android application.
Do I have to change EVERY function to this code
Not necessarily.
First, I do not see how you are getting that error (A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread.) from your #JavascriptInterface methods shown above. That error is when you call a method on the WebView itself, and you are not doing that.
You will need to use runOnUiThread() (or equivalent techniques) if:
Your #JavascriptInterface methods refer to the WebView itself, or
Your #JavascriptInterface try to do something else that has to be done on the main application thread (which will yield a different error message, as it will not be tied specifically to WebView)
Your startActivity() call might need to be called on the main application thread — I forget if that can be called on a background thread or not. Similarly with your Toast work — while I think that can be done on a background thread, I am not certain of it. It has been ages since I tried doing either of those things from a background thread.
Also, please only use your code if you control every single byte of what is being displayed in the WebView. Exposing startActivity() to arbitrary Web content has significant security implications.

Related

How to get data from web page every hour using alarm manager in android?

I am making an app that needs to get data from an html element in a web page and send a notification that includes the data every hour when the app isn't running. To do this, I am using an AlarmManager. in my AlarmReceiver class I then tried using JSoup to connect to the website.
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intentt) {
try {
Document doc = Jsoup.connect(URL).get();
} catch (IOException e) {
e.printStackTrace();
}
This gave me exception NetworkOnMainThreadException
So I then tried this solution:
final WebView w = new WebView(context);
w.loadUrl(URL);
WebSettings webSettings = w.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setDefaultTextEncodingName("utf-8");
webView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
w.evaluateJavascript("(function() { return document.getElementById('app_notification_id').innerHTML;})()", new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
...
This time i saw in the debugger that the code inside the OnPageFinished wasn't even executed, although when I try it inside MainActivity when the app is active and running this code works fine.
How can I achieve my goal? Is there another approach that works or do I have a mistake in my code?
onReceive() method of BroadcastReceiver is called on the main application thread, and you should not do disk I/O or network I/O on the main application thread. That's why you got NetworkOnMainThreadException. Move that HTTP code into another IntentService, and call -
startService(intentService)
on onReceive() method of BroadcastReceiver.
Also You cannot add WebView in receiver because it requires activity reference as you are adding it from only context reference. Just apply above solution.
Use a JobScheduler instead of AlarmManager. AlarmManager has been outdated. Look at the link for more information.
https://www.vogella.com/tutorials/AndroidTaskScheduling/article.html
I found a better solution for my problem. I was able to create a new webview and use OnPageFinished which is what i was trying to achieve by adding these lines to the code
Handler handler = new Handler(Looper.getMainLooper());
try {
handler.post(
new Runnable() {
#Override
public void run() {
final WebView w = new WebView(context);
w.loadUrl(url);
WebSettings webSettings = w.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setDefaultTextEncodingName("utf-8");
w.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
w.evaluateJavascript("(function() { return document.getElementById('app_notification_id').innerHTML;})()", new ValueCallback<String>() {
...

button triggering function and intent at the same time. Android

So i am new to android and java, and i understand that i should move between activities with intents.
Intent randomintent = new Intent(profile.this, loggedactivity.class);
randomintent .putString("name", namestring);
startActivity(randomintent);
The thing is, that i also have a function that i want it to be executed just before this intent takes the user to another activity. So my code looks something like this.
btnUpload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
uploadImage();
//this uploads the image, it works without the intent i added
infosendstuff();
//this should be executed after the image is uploaded and stores the image link to a database (also works)
Intent randomintent = new Intent(profile.this, loggedactivity.class);
randomintent .putString("name", namestring);
startActivity(randomintent);
}
});
The problem seems to be the intent, when used, it ignores the other two functions above it, that upload the picture and store the link for that picture.
The goal is to upload the picture, once done, get the link, send the link to another activity though the intent (with the bundle) and thats about it.
it's better to use AsynkTask.
create a class, it should extend from AsynkTask. in doInBackground, upload your photos, process the response and send the link. and in onPostExecute method, go to the other activity.
update 1
class myClass extends AsyncTask<Void,Void,Void>{
#Override
protected Void doInBackground(Void... params) {
String result = uploadPhotos();
proccess result;
send links;
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
Intent randomintent = new Intent(profile.this, loggedactivity.class);
randomintent .putString("name", namestring);
startActivity(randomintent);
}
}
now you can use it like this:
new myClass().execute();
but before coding, i think you need to study more about web Connection process in android.
It seems that uploadImage() Method does some stuff on the network and since the network request and response is done in another thread the code continue to execute and loggedactivity will show before the uploadImage() method is done.
So one way is that you force main thread to wait for network thread and after the network thread is done main thread continue to work but it cause the UI thread to freeze.
another way is that you should use callbacks that when the uploadImage() method is done some method will invoke and in that method you start your new activity. something like code below :
uploadImage(new ResponeListener() {
#override
public void onDataReady(String nameString) {
Intent randomintent = new Intent(profile.this,loggedactivity.class);
Bundle b = new Bundle();
b.putString("name", namestring);
startActivity(randomintent);
}
}

JavaScript not calling method in Android using addJavascriptInterface

I have searched high and low and found so many examples of this, but unable to get it to work, my setup currently is:
Notifier.java
public class Notifier{
Context mContext;
Notifier(Context c) {
mContext = c;
}
#JavascriptInterface
public void showText()
{
Toast.makeText(mContext, "Some text!", Toast.LENGTH_LONG).show();
}
}
SearchLicenseActivity.java
public class SearchLicenseActivity extends Activity {
WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search_license);
LoadSearch();
}
public void LoadSearch(){
webView = (WebView)findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new Notifier(this), "Android");
webView.loadUrl("javascript:Android.showText();");
}
}
So I am expecting a Toast to show. It may be worth noting that this SearchActivity gets created when a button on a previous Activity is clicked; so I want it to execute LoadSearch straight away and get the Toast from the JavaScript.
I hope one of you out there can cure my woes over this!
Edit: I am also not getting any errors in LogCat.
OK, it turns out webView must have a webpage loaded.
Even if the url is "about:blank" this seems to work. So...
webView.loadUrl("about:blank");
webView.loadUrl("javascript:Android.showText();");
...works.

Providing delay between events in UiAutomator Android

How can I provide delay between event clicks in UiAutomator Android.
First event is entering a url in EditText :
new UiObject(new UiSelector().text("Search here")).setText("abc.com");
getUiDevice.waitForIdle(15000);
Here I am loading a webpage inside a webview. So when url loading finishes , then I need to check for second event.
Second event is checking content description of a object :
UiObject curClass = new UiObject(new UiSelector().className("android.widget.RelativeLayout"));
UiObject yCur = curClass.getChild(new UiSelector().description("ye"));
getCurCol();
public void getCurCol() throws UiObjectNotFoundException {
try {
if (yCur.getContentDescription() != null)
report.append("Success");
} catch (Exception e) {
System.err.println("\nCaught Exception: " + e.getMessage());
}
But this doesn't seem to be working.
I just want the app to wait for some time before checking for the second event.
I know these three methods are provided for delay in UI Automator -
public void waitForIdle(long timeout)
public void waitForIdle()
public boolean waitForWindowUpdate(String packageName, long timeout)
But I don't know how to use these methods.
Please suggest me a example how to use these.
Any help would be appreciated.
Thanks!
Try just
sleep(5000);
Let me know whether it works. Or you can try WaitForIdle( ) method.
If you need a wait after pressing a button
UiObject settingsButton = mDevice.findObject(new UiSelector().text("Settings"));
settingsButton.clickAndWaitForNewWindow();
or
settingsButton.waitUntilGone(1000);
In case of launching new activity from intent this way is the easiest I could find to wait for a settings package appear:
mDevice.wait(Until.hasObject(By.pkg("com.android.settings").depth(0)), 2000);
UiObject settingsValidation = mDevice.findObject(new UiSelector().packageName("com.android.settings").text("Settings"));
assertTrue("Unable to detect Settings", settingsValidation.waitForExists(4000));
public boolean waitForWindowUpdate(null, long timeout)
Works good for me. I think it's possible to bring any 'not existing' String packagaName, for ex. "blablabla", because you can get null instead of String packageName.
I've create a simply method to make a delay:
public void waitTime(int time) {
getUiDevice().waitForWindowUpdate(null, time);
}
Hope this is helpful.
I am no expert in UIautomator, but this can be done with Thread.sleep().
UiDevice provides a few methods that may work for you:
public void waitForIdle(long timeout)
public void waitForIdle()
public boolean waitForWindowUpdate(String packageName, long timeout)
i think TimeUnit.SECONDS.sleep(val); should be helpful here
for instance
import java.util.concurrent.TimeUnit;
private void waitFor(long val)
{
try
{
TimeUnit.SECONDS.sleep(val);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
This should work
I had a case where I had to wait for the Smart Lock dialog and find a way to dismiss it.
I ended up doing the following
final UiDevice mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Wait for the Smart Lock dialog to popup
if(mDevice.waitForWindowUpdate(null, 5000)) {
if("com.google.android.gms".equals(mDevice.getCurrentPackageName())) {
// Dismiss dialog
mDevice.pressBack();
}
}
This will wait for the dialog to appear and if it does it gets dismissed.
webView.loadUrl("http://abc.com");
webView.setWebChromeClient(new WebChromeClient());
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
}
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//Handle your code here
//Toast.makeText(TableContentsWithDisplay.this, "Width " + view.getWidth() +" *** " + "Height " + view.getHeight(), Toast.LENGTH_SHORT).show();
}
#Override
public void onReceivedSslError(WebView view,
SslErrorHandler handler, SslError error) {
// TODO Auto-generated method stub
super.onReceivedSslError(view, handler, error);
//Toast.makeText(TableContentsWithDisplay.this, "error "+error, Toast.LENGTH_SHORT).show();
}
});
Load your url then there is method onPageFinished() where you can handle your code.
The only way I was able to add delay in UIAutomator code was in a similar way to slott 's answer:
I basically lunched a small app (the basic app from android examples) and pressed back to close it. This allows time for all elements to load properly.
Context context = getApplicationContext();
final Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances
context.startActivity(intent);
// Wait for the app to appear
mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT);
//then close this small app
mDevice.pressBack();

How to call takePicture() method from another activity in android?

I have main activity class where i have written takePicturefromCamera() method like this:
public void takePicturefromCamera() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
try {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
mImageCaptureUri = Uri.fromFile(mFileTemp);
}
else {
// The solution is taken from here: http://stackoverflow.com/questions/10042695/how-to-get-camera-result-as-a-uri-in-data-folder
mImageCaptureUri = InternalStorageContentProvider.CONTENT_URI;
}
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, mImageCaptureUri);
intent.putExtra("return-data", true);
startActivityForResult(intent, REQUEST_CODE_TAKE_PICTURE);
} catch (ActivityNotFoundException e) {
Log.d("TAG", "cannot take picture", e);
}
}
Now I have another class where i am doing some cropping part and my requirement is if while cropping user feels that he want to take some another picture instead of previous there i want to call above takePicturefromCamera() method of main activity class on some button click. Can anyone help me how can I do like this. I have tried by doing like this:
retake_button.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
// setResult(RESULT_CANCELED);
main m = new main();
m.takePicturefromCamera();
}
});
But it gives me error NullPointerException at mImageCaptureUri = Uri.fromFile(mFileTemp);
Please help me.
I would suggest you to keep takePicturefromCamera() in separate class, and then call this method passing the activitycontext and data required to it.
Or else you can make Activity2 extend Activity1 and use takePicturefromCamera().
Make that method as static so you can use it anywhere in your application.
Also add one more parameter "File" to your function. By this, you also have flexibility to use the same function for different file object in your application.
Hope this will help you.

Categories

Resources