Is there a way to retrieve Browser's user-agent without having a WebView in activity?
I know it is possible to get it via WebView:
WebView view = (WebView) findViewById(R.id.someview);
String ua = view.getSettings().getUserAgentString() ;
But in my case I don't have/need a webview object and I don't want to create it just for retrieving user-agent string.
If you don't have one you can try taking it like this
String ua=new WebView(this).getSettings().getUserAgentString();
Edit-
The doc for getUserAgentString() says
Return the WebView's user-agent string.
So i don't think you can get it unless you declare one. Some one correct me if i am wrong
There is a much simpler way if you are on Android 2.1 or above. Granted this isn't the exact same User Agent string that a webview would return, but might serve you well enough for your purposes.
As an additional advantage to pulling from web view, you can use this from any thread (not just the UI thread).
There is a system property called http.agent, which can be used to retrieve the User-Agent string.
String userAgent = System.getProperty("http.agent");
See Programmatically get User-Agent String for more details.
I used to use solution proposed by DeRagan. But it turned out that creating a single WebView instance starts a thread "WebViewCoreThread" which stays on the background until application is terminated by the system. Maybe it doesn't consume too much resources but I don't like it anyway. So I use slightly different method now, which tries to avoid WebViewCoreThread creation:
// You may uncomment next line if using Android Annotations library, otherwise just be sure to run it in on the UI thread
// #UiThread
public static String getDefaultUserAgentString(Context context) {
if (Build.VERSION.SDK_INT >= 17) {
return NewApiWrapper.getDefaultUserAgent(context);
}
try {
Constructor<WebSettings> constructor = WebSettings.class.getDeclaredConstructor(Context.class, WebView.class);
constructor.setAccessible(true);
try {
WebSettings settings = constructor.newInstance(context, null);
return settings.getUserAgentString();
} finally {
constructor.setAccessible(false);
}
} catch (Exception e) {
return new WebView(context).getSettings().getUserAgentString();
}
}
#TargetApi(17)
static class NewApiWrapper {
static String getDefaultUserAgent(Context context) {
return WebSettings.getDefaultUserAgent(context);
}
}
It creates WebSettings instance directly using package-visible constructor and if that is not available for some reason (e.g. due to API changes in future Android versions) - silently falls back to "WebView-like" solution.
UPDATE
As pointed by #Skywalker5446, starting from Android 4.2/API 17, there is a public static method to get default user agent value. I've updated my code to use that method on the supported platforms.
Since Android 2.1 you should use System.getProperty("http.agent");
You also dont need to create a WebView first AND , thats the advantage,
you can use it inside a non-uithread.
greetings steve
This is an updated solution based on previous answers that works when you compile for KitKat. Now the WebSettings class is abstract and the WebSettingsClassic class has been removed.
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static String getUserAgent(final Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return WebSettings.getDefaultUserAgent(context);
}
else {
try {
final Class<?> webSettingsClassicClass = Class.forName("android.webkit.WebSettingsClassic");
final Constructor<?> constructor = webSettingsClassicClass.getDeclaredConstructor(Context.class, Class.forName("android.webkit.WebViewClassic"));
constructor.setAccessible(true);
final Method method = webSettingsClassicClass.getMethod("getUserAgentString");
return (String) method.invoke(constructor.newInstance(context, null));
}
catch (final Exception e) {
return new WebView(context).getSettings()
.getUserAgentString();
}
}
}
Thanks to Idolon's answer my app could process this in the background.
But somehow on HTC Inspire 4G from AT&T that runs 2.3.3, it goes to the catch statement and it can be no longer run on the background thread.
My solution for this is the following:
public static String getUserAgent(Context context) {
try {
Constructor<WebSettings> constructor = WebSettings.class.getDeclaredConstructor(Context.class, WebView.class);
constructor.setAccessible(true);
try {
WebSettings settings = constructor.newInstance(context, null);
return settings.getUserAgentString();
} finally {
constructor.setAccessible(false);
}
} catch (Exception e) {
String ua;
if(Thread.currentThread().getName().equalsIgnoreCase("main")){
WebView m_webview = new WebView(context);
ua = m_webview.getSettings().getUserAgentString();
}else{
mContext = context;
((Activity) mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
WebView webview = new WebView(mContext);
mUserAgent = webview.getSettings().getUserAgentString();
}
});
return mUserAgent;
}
return ua;
}
}
(suppose you have mContext and mUserAgent in the field)
Related
I am trying billow Code from this answer to check if the permission is enabled. but it is returning false even when the permission is enabled from the settings.
public static boolean canDrawOverlayViews(Context con){
if(Build.VERSION.SDK_INT< Build.VERSION_CODES.LOLLIPOP){return true;}
try {
return Settings.canDrawOverlays(con);
}
catch(NoSuchMethodError e){
return canDrawOverlaysUsingReflection(con);
}
}
public static boolean canDrawOverlaysUsingReflection(Context context) {
try {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Class clazz = AppOpsManager.class;
Method dispatchMethod = clazz.getMethod("checkOp", new Class[] { int.class, int.class, String.class });
//AppOpsManager.OP_SYSTEM_ALERT_WINDOW = 24
int mode = (Integer) dispatchMethod.invoke(manager, new Object[] { 24, Binder.getCallingUid(), context.getApplicationContext().getPackageName() });
return AppOpsManager.MODE_ALLOWED == mode;
} catch (Exception e) { return false; }
}
Recently I've also faced the same issue and got the following workaround .
Referenced from
https://code.google.com/p/android/issues/detail?id=198671#c7
public boolean getWindoOverLayAddedOrNot2() {
String sClassName = "android.provider.Settings";
try {
Class classToInvestigate = Class.forName(sClassName);
if (context == null)
context = activity;
Method method = classToInvestigate.getDeclaredMethod("isCallingPackageAllowedToDrawOverlays", Context.class, int.class, String.class, boolean.class);
Object value = method.invoke(null, context, Process.myUid(), context.getPackageName(), false);
Log.i("Tag", value.toString());
// Dynamically do stuff with this class
// List constructors, fields, methods, etc.
} catch (ClassNotFoundException e) {
// Class not found!
} catch (Exception e) {
// Unknown exception
e.printStackTrace();
}
return false;
}
does the check involves the device admin?
I have encountered this problem when disabling device admin, I have checked this permission in the DeviceAdminReceiver->onDisabled() and on some devices, and canDrawOverlays returned false, despite the fact i had the permission.
The above answer helped sometimes but not all the time. the thing that did work is Thread.sleep before the check.
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// some exception here
}
The minimal time that worked for me was 20 millis. than canDrawOverlays returned true
Note: this is not a good practice however this is the only thing that worked for me
Based on BennyP's answer, I've made a Runnable run the required code after 500ms and that worked very well. The feedback is a bit delayed, but the user won't even notice the delay.
This is the code I've added to my onResume()
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if(!Settings.canDrawOverlays(ControllerActivity.this)){
//Handle overlay permission not given here
}
else{
//Handle overlay permission given here
}
}
}, 500);
Hope it helps!
I tried restarting the activity after the user accessed the setting . This is code :
public static void restartActivity(Activity act){
Intent intent = getIntent();
finish();
startActivity(intent);
}
First of all, I am really very surprised with this strange behaviour of
Settings.canDrawOverlays(this);
I also faced the same issue with its usage, it was returning false even if the permission is already assigned.
What I noticed that, I was using this check in my onStart() method, where it was creating this wired behavior. To resolve this, I searched over internet and no result was there that can satisfy me and the one I can use.
Solution
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.e("Overlay Permission", "" + Settings.canDrawOverlays(this));
if (!Settings.canDrawOverlays(this)) {
MyPreferences.saveBoolean(HomeScreen.this, "showOverlayPermissionDialog", true);
} else {
MyPreferences.saveBoolean(HomeScreen.this, "showOverlayPermissionDialog", false);
}
}
I did something lake this, in my onCreate(). Here I saved the values accordingly in my SharedPreferences, and according to these Shared Preference values, I created a check for showing an overlay dialog in my onStart(). This worked fine!
You can try this solution, and mark this answer useful if your problem is solved.
Thanks
I've been developing hybrid apps for many companies with mobile websites.
And as a matter of fact, there are some websites made with using jsp.
I already had the knowledge that iframes and javascripts xhr requests will not fire webViewClient's shouldOverrideUrlLoading override function. I'm fine with that.
But today I learned that SOME actions such as:
JSP Page Redirects
Link Clicks within a JSP page
JSP/JS induced URL Loads
will not ALWAYS fire this function.
Hence, shouldOverrideUrlLoading() does not fire, when the webView is asked to load a page that it cannot load(i.e. "intent://...",) it shows an error page.
Has anyone encountered this kind of behaviour and is there any solution to work around it ?
Below is the code I'm using to invoke activities, where urls with 'intent:' protocol (which will fail because this function never gets called when above actions are performed)
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// ... omitted ...
if ( url.startsWith("intent:") ) {
Intent intent = null;
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
// The following flags launch the app outside the current app
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
try {
getActivity().startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
return true;
}
}
ps. please notice that every other websites' page loads will perfectly call shouldOverrideUrlLoading().
I couldn't find any JSP related bugs on android webViews so I'm asking one.
ps. I am happily willing to provide sample websites that some gracious readers will try on.. but the website's written in Korean so I doubt it will help.
Thank you!
Your problem might not related to JSP, the real problem may be shouldOverrideUrlLoading() itself. In this case, using shouldOverrideUrlLoading() may not be a good idea, so why not try another perspective?
I've encountered many problems when using
shouldOverrideUrlLoading() loading XmlHttpRequest. At the end, I
came up with the idea using onProgressChanged() and it solved all
my problems. I've written a similar answer here.
I tried adding your code into my own webview project and tested it with some JSP sites, and looks like it always work. I also added loadUrl() after other activities are invoked, so after pressing the back button, the loading error page will not be displayed again. So try this one :
First declare a global variable to store last URL.
String strLastUrl = null;
Then override onProgressChanged(WebView view, int progress)
mWebView.setWebChromeClient(new MyWebChromeClient(){
#Override
public void onProgressChanged(WebView view, int progress) {
if (progress == 100) {
//A fully loaded url will come here
String StrNewUrl = view.getUrl();
if(TextUtils.equals(StrNewUrl,strLastUrl)){
//same page was reloaded, not doing anything
}else{
String strOldUrl = null;
//save old url to variable strOldUrl before overwriting it
strOldURL = strLastUrl;
//a new page was loaded,overwrite this new url to variable
strLastUrl = StrNewUrl;
if ( strLastUrl.startsWith("intent:") ) {
Log.d("TAG", "intent triggered");
Intent intent = null;
try {
intent = Intent.parseUri(strLastUrl, Intent.URI_INTENT_SCHEME);
// The following flags launch the app outside the current app
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
//reload the page before invoking other activities
view.loadUrl(strOldURL);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
}
super.onProgressChanged(view, progress);
}
});
I am having problems with the Webview in Android and it's JavascriptInterfaces.
I am passing a string to the JavascriptInterface. When debugging it, I receive the correct string within my Android application. The problem: Sometimes I get an Uncaught Error: Error calling method on NPObject.
Does anybody know why?
The Interface in Java:
public class JSInterfaceGame extends JSInterface {
#JavascriptInterface
public void setShareText(String share){
shareText = share;
if(mJSInterfaceListener != null)
mJSInterfaceListener.onParametersChanged(SHARE_TEXT);
}
The initialization in the onCreateView-Method within the Fragment:
online = (WebView) rootView.findViewById(R.id.online);
online.setWebViewClient(new WISWebviewClient() {
#Override
public void onStatusChanged(final WebView view, int progress, long duration) {
//unrelated
}
});
WebSettings ws = online.getSettings();
ws.setJavaScriptEnabled(true);
ws.setUserAgentString(USER_AGENT);
ws.setCacheMode(WebSettings.LOAD_DEFAULT);
ws.setRenderPriority(WebSettings.RenderPriority.HIGH);
SharedPreferences settings = getActivity().getSharedPreferences(GameActivity.PREFERENCES, Context.MODE_PRIVATE);
mJSInterface = new JSInterfaceGame();
mJSInterface.setJSInterfaceListener(this); // Defined elsewhere in this class.
mJSInterface.setPlayerName(settings.getString(GameActivity.PREFS_PlAYERNAME, null));
online.addJavascriptInterface(mJSInterface, "JSInterface");
online.loadUrl("http://myurl.something");
Call in Javascript:
function makeShareText() {
var text = "Some text";
console.log(typeof text); // Always a string.
JSInterface.setShareText(text);
}
It happens when you try, using method called from javascript interface, to interact with UI. To solved it in this way:
class mJSInterface()
{
public void myFunction()
{
runOnUiThread(new Runnable() {
public void run() {
//Code that interact with UI
}
});
}
}
To highlight the comment from #Leog
The same error occurs if you call the native javascript function with wrong parameters
This was the source of my error
Another reason can be a RuntimeException on a WebViewCoreThread. Any exception occurred after receiving #JavascriptInterface call will be logged as NPObject error if still running on a WebView thread. Overall insufficient trace message with little clue about the problem.
Correct your issue with handling javascript interface call on a suitable thread.
Example A. (NPObject error):
#JavascriptInterface
public void jsCall() {
Log.v(TAG, "Prepared NullPointerException on "+Thread.currentThread());
String s = null;
s.length(); // This will cause NPObject error
}
Example B. (NullPointerException):
#JavascriptInterface
public void jsCall() {
new Thread(new Runnable() {
#Override
public void run() {
Log.v(TAG, "Prepared NullPointerException on " + Thread.currentThread());
String s = null;
s.length(); // This will throw NullPointerException
}
}).start();
}
Take this as an addition to #Nico.S's answer.
Operate iframe in Android 4.4 WebView may cause a similar exception(Uncaught ReferenceError: NPObject deleted), finally I find out the solution:
#Override
public void onPageFinished(final WebView view, String finishUrl) {
super.onPageFinished(view, finishUrl);
// android 4.4 may lost value of 'Android' when operating iframe
view.addJavascriptInterface(Activity.this, "Android");
}
Is there a runtime check for an application to find out if it runs as part of an instrumentation test?
Background: Our application performs a database sync when starting. But that should happen only when started regularly. It especially interferes with the instrumentation tests testing the db sync. Not surprisingly.
And with all the other tests it's just a waste of CPU cycles.
A much simpler solution is check for a class that would only be present in a test classpath, works with JUnit 4 (unlike the solution using ActivityUnitTestCase) and doesn't require to send custom intents to your Activities / Services (which might not even be possible in some cases)
private boolean isTesting() {
try {
Class.forName("com.company.SomeTestClass");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
Since API Level 11, the ActivityManager.isRunningInTestHarness() method is available. This might do what you want.
If you are using Robolectric, you can do something like this:
public boolean isUnitTest() {
String device = Build.DEVICE;
String product = Build.PRODUCT;
if (device == null) {
device = "";
}
if (product == null) {
product = "";
}
return device.equals("robolectric") && product.equals("robolectric");
}
If you're using ActivityUnitTestCase, you could set a custom Application object with setApplication, and have a flag in there to switch database sync on or off? There's an example of using a custom Application object on my blog:
http://www.paulbutcher.com/2011/03/mock-objects-on-android-with-borachio-part-3/
You can pass an intent extra to your activity indicating it's under test.
1) In your test, pass "testMode" extra to your activity:
public void setUp() throws Exception {
super.setUp();
Intent activityIntent = new Intent();
activityIntent.putExtra("testMode", true);
setActivityIntent(activityIntent);
}
2) In your activity, check for testMode:
Bundle extras = getIntent().getExtras();
if (extras != null && extras.getBoolean("testMode")) {
// disable your database sync
}
d= (◕‿↼ ) Great answer, but if some library developer (like me) wants to know if the Host (or App using the library) is being tested, then try:
import android.content.pm.ApplicationInfo;
// ...
private static int wasTestRun = 0xDEAD;
/**
* Should only be used to speed up testing (no behavior change).
* #return true in tests, if Gradle has the right dependencies.
*/
public static boolean isTestRun(#NonNull Context context) {
if (wasTestRun != 0xDEAD) {
return wasTestRun != 0;
}
// Ignore release builds (as App may be using JUnit by mistake).
if (isDebuggable(context)) {
try {
Class.forName("org.junit.runner.Runner");
wasTestRun = 1;
return true;
} catch (ClassNotFoundException ignored) {
}
}
wasTestRun = 0;
return false;
}
public static boolean isDebuggable(#Nullable Context context) {
return context != null && (context.getApplicationContext()
.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
Note that I am not using any AtomicBoolean or other helpers, as it is already pretty fast (and locking may just bring the speed down).
You can try this
if (isRunningTest == null) {
isRunningTest = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
List<StackTraceElement> list = Arrays.asList(stackTrace);
for (StackTraceElement element : list) {
if (element.getClassName().startsWith("androidx.test.runner.MonitoringInstrumentation")) {
isRunningTest = true;
break;
}
}
}
This work for me because no actual device is running
public static boolean isUnitTest() {
return Build.BRAND.startsWith(Build.UNKNOWN) && Build.DEVICE.startsWith(Build.UNKNOWN) && Build.DEVICE.startsWith(Build.UNKNOWN) && Build.PRODUCT.startsWith(Build.UNKNOWN);
}
How can you read data, i.e. convert simple text strings to voice (speech) in Android?
Is there an API where I can do something like this:
TextToVoice speaker = new TextToVoice();
speaker.Speak("Hello World");
Using the TTS is a little bit more complicated than you expect, but it's easy to write a wrapper that gives you the API you desire.
There are a number of issues you must overcome to get it work nicely.
They are:
Always set the UtteranceId (or else
OnUtteranceCompleted will not be
called)
setting OnUtteranceCompleted
listener (only after the speech
system is properly initialized)
public class TextSpeakerDemo implements OnInitListener
{
private TextToSpeech tts;
private Activity activity;
private static HashMap DUMMY_PARAMS = new HashMap();
static
{
DUMMY_PARAMS.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "theUtId");
}
private ReentrantLock waitForInitLock = new ReentrantLock();
public TextSpeakerDemo(Activity parentActivity)
{
activity = parentActivity;
tts = new TextToSpeech(activity, this);
//don't do speak until initing
waitForInitLock.lock();
}
public void onInit(int version)
{ //unlock it so that speech will happen
waitForInitLock.unlock();
}
public void say(WhatToSay say)
{
say(say.toString());
}
public void say(String say)
{
tts.speak(say, TextToSpeech.QUEUE_FLUSH, null);
}
public void say(String say, OnUtteranceCompletedListener whenTextDone)
{
if (waitForInitLock.isLocked())
{
try
{
waitForInitLock.tryLock(180, TimeUnit.SECONDS);
}
catch (InterruptedException e)
{
Log.e("speaker", "interruped");
}
//unlock it here so that it is never locked again
waitForInitLock.unlock();
}
int result = tts.setOnUtteranceCompletedListener(whenTextDone);
if (result == TextToSpeech.ERROR)
{
Log.e("speaker", "failed to add utterance listener");
}
//note: here pass in the dummy params so onUtteranceCompleted gets called
tts.speak(say, TextToSpeech.QUEUE_FLUSH, DUMMY_PARAMS);
}
/**
* make sure to call this at the end
*/
public void done()
{
tts.shutdown();
}
}
Here you go . A tutorial on using the library The big downside is that it requires an SD card to store the voices.
A good working example of tts usage can be found in the "Pro Android 2 book". Have a look at their source code for chapter 15.
There are third-party text-to-speech engines. Rumor has it that Donut contains a text-to-speech engine, suggesting it will be available in future versions of Android. Beyond that, though, there is nothing built into Android for text-to-speech.
Donut has this: see the android.speech.tts package.