Add statements for higher version? - android

I am using 1.6 i.e. API 4 to build my application. There are couple of commands that are supported by higher versions. I want to write those commands and make application more compatible for higher versons. Like, I use Tabs. I want to use setLeftStripDrawable and setRightStripDrawable, but they are supported by API 8.
I write something like :
// I want these lines to come into affect only if the device SDK is greater than 7 as SDK of below 7, doesn't support these methods.
if (android.os.Build.VERSION.SDK_INT > 7) {
tw.setLeftStripDrawable(R.drawable.tab_selected_bar_left_v4); // TabWidget
}
EDIT : I want to set setLeftStripDrawable to the tabs used in my app. In my manifest I have uses-sdk android:minSdkVersion="4". If I write the lines as above and compile it in 2.3, it compiles successfully. When I run in 1.6 I get "java.lang.VerifyError". If I remove those liens and again run in 1.6, it works properly.
What should I do to execute those lines only if the device SDK api is > 7, and if it is less than that then those lines should not come under any affect ?
Any clue ?

if (Build.VERSION.SDK_INT > 7) {
...
}

I think you should use something like this. I did this by heart, so there might be some errors.
try {
Method twMethod = TabWidget.class.getMethod("setLeftStripDrawable", new Class[] { int.class });
twMethod.invoke(tw, R.drawable.yourdrawable);
} catch (NoSuchMethodException e) {
/* not supported */
} catch (IllegalArgumentException e) {
/* wrong class provided */
} catch (IllegalAccessException e) {
/* Java access control has denied access */
} catch (InvocationTargetException e) {
/* method has thrown an exception */
}

You can try looking at Android Reflection. I have not used it yet myself, but as far as i understand, you can test for classes and methods that you know the name of. Then you can instantiate and use them.
You can read some of the basics here: http://www.tutorialbin.com/tutorials/85977/java-for-android-developers-reflection-basics

Here's some sample Android code, using reflection, that does something similar. It calls the getRotation() method from the Display class; the method only exists in SDK 8+. I've used it in one of my apps and it works:
//I want to run this: displayrotation = getWindowManager().getDefaultDisplay().getRotation();
//but the getRotation() method only exists in SDK 8+, so I have to call it in a sly way, using Java "reflection"
try{
Method m = Display.class.getMethod("getRotation", (Class[]) null);//grab the getRotation() method if it exists
//second argument above is an array containing input Class types for the method. In this case it takes no inputs.
displayrotation = (Integer) m.invoke(getWindowManager().getDefaultDisplay(),(Object[]) null);
//again, second argument is an array of inputs, in this case empty
}catch(Exception e){//if method doesn't exist, take appropriate alternate actions
Log.w("getRotation","old OS version => Assuming 90 degrees rotation");
displayrotation = Surface.ROTATION_90;
}

Related

How to get selected text in Android WebView using reflection?

I'm trying to get the selected text of my WebView in Android. I know Android does not let us to get this using the right ways.
One solution I've found in the internet is using reflection. This is the code I'm using:
Region result = null;
try {
Object[] params = null;
Method nativeGetSelection = WebView.class.getDeclaredMethod("nativeGetSelection");
nativeGetSelection.setAccessible(true);
result = (Region)nativeGetSelection.invoke(this, params);
} catch (Exception e) {
e.printStackTrace();
}
But I am getting NoSuchMethodException. But the Android WebView has the desired method (nativeGetSelection). How you can see here
So why is this happening?
Don't use reflection to get at private APIs. This will not work on Android 4.4 (KitKat) regardless of your minSdk/targetSdk because that API is simply not there.

How to prevent name caching and detect bluetooth name changes on discovery

I'm writing an Android app which receives information from a Bluetooth device. Our client has suggested that the Bluetooth device (which they produce) will change its name depending on certain conditions - for the simplest example its name will sometimes be "xxx-ON" and sometimes "xxx-OFF". My app is just supposed to seek this BT transmitter (I use BluetoothAdapter.startDiscovery() ) and do different things depending on the name it finds. I am NOT pairing with the Bluetooth device (though I suppose it might be possible, the app is supposed to eventually work with multiple Android devices and multiple BT transmitters so I'm not sure it would be a good idea).
My code works fine to detect BT devices and find their names. Also, if the device goes off, I can detect the next time I seek, that it is not there. But it seems that if it is there and it changes name, I pick up the old name - presumably it is cached somewhere. Even if the bluetooth device goes off, and we notice that, the next time I detect it, I still see the old name.
I found this issue in Google Code: here but it was unclear to me even how to use the workaround given ("try to connect"). Has anyone done this and had any luck? Can you share code?
Is there a simple way to just delete the cached names and search again so I always find the newest names? Even a non-simple way would be good (I am writing for a rooted device).
Thanks
I would suggest 'fetchUuidsWithSdp()'. It's significance is that, unlike the similar getUuids() method, fetchUuidsWithSdp causes the device to update cached information about the remote device. And I believe this includes the remote name as well as the SPD.
Note that both the methods I mentioned are hidden prior to 4.0.3, so your code would look l ike this:
public static void startServiceDiscovery( BluetoothDevice device ) {
// Need to use reflection prior to API 15
Class cl = null;
try {
cl = Class.forName("android.bluetooth.BluetoothDevice");
} catch( ClassNotFoundException exc ) {
Log.e(CTAG, "android.bluetooth.BluetoothDevice not found." );
}
if (null != cl) {
Class[] param = {};
Method method = null;
try {
method = cl.getMethod("fetchUuidsWithSdp", param);
} catch( NoSuchMethodException exc ) {
Log.e(CTAG, "fetchUuidsWithSdp not found." );
}
if (null != method) {
Object[] args = {};
try {
method.invoke(device, args);
} catch (Exception exc) {
Log.e(CTAG, "Failed to invoke fetchUuidsWithSdp method." );
}
}
}
}
You'll then need to listen for the BluetoothDevice.ACTION_NAME_CHANGED intent, and extract BluetoothDevice.EXTRA_NAME from it.
Let me know if that helps.

Android: How do I call a method which is existing in other API Level?

I have application using Android 2.1 which utilize LocationManager to get the altitude. But now, I need to obtain the altitude using SensorManager which requires API Level 9 (2.3).
How can I put the SensorManager.getAltitude(float, float) in my 2.1 android application by putting a condition and calling it by a function name (possible in normal Java)?
Thank you in advance
UPDATE 1
If you have noticed that my application need to be compiled using Android 2.1. That's why I'm looking for a way to call the function by name or in any other way that can be compiled.
You need to build against the highest api you require and then code alternate code paths conditionally for other levels you want to support
To check current API level at execution time, the latest recommendation from the Android docs is to do something like this:
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD)
{
...
Once you introduce this complexity though, you have to be very careful. There isn't currently an automatic way to check all code paths to make sure that all api level calls above the minSdkVersion have alternative calls to support all versions. Maybe someone can chime in if there exists a unit testing tool that might do something like this.
You can call the method using reflection and fail gracefully in case of errors (like missing class or methods). See java.lang.reflect
Other option is to compile code in level 9 but surround with try/catch to catch errors that would arise from execution on lower level. It could be fairly error prone, though, and I'd think twice about doing it.
Update
Here is test code
public void onCreate(Bundle savedInstanceState)
{
try {
// First we try reflection approach.
// Expected result
// in 2.3 we print some value in log but no exception
// in 2.2 we print NoSuchMethodException
// In both levels we get our screen displayed after catch
Method m = SensorManager.class.getMethod("getAltitude",Float.TYPE, Float.TYPE);
Float a = (Float)m.invoke(null, 0.0f, 0.0f);
Log.w("test","Result 1: " + a);
} catch (Throwable e) {
Log.e("test", "error 1",e);
}
try {
// Now we try compiling against 2.3
// Expected result
// in 2.3 we print some value in log but no exception
// in 2.2 we print NoSuchMethodError (Note that it is an error not exception but it's still caught)
// In both levels we get our screen displayed after catch
float b = SensorManager.getAltitude(0.0f, 0.0f);
Log.w("test","Result 2: " + b);
} catch (Throwable e) {
Log.e("test", "error 2",e);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
Results:
2.3
09-14 07:04:50.374: DEBUG/dalvikvm(589): Debugger has detached; object registry had 1 entries
09-14 07:04:50.924: WARN/test(597): Result 1: NaN
09-14 07:04:51.014: WARN/test(597): Result 2: NaN
09-14 07:04:51.384: INFO/ActivityManager(75): Displayed com.example/.MyActivity: +1s65ms
2.2
09-14 07:05:48.220: INFO/dalvikvm(382): Could not find method android.hardware.SensorManager.getAltitude, referenced from method com.example.MyActivity.onCreate
09-14 07:05:48.220: WARN/dalvikvm(382): VFY: unable to resolve static method 2: Landroid/hardware/SensorManager;.getAltitude (FF)F
09-14 07:05:48.220: DEBUG/dalvikvm(382): VFY: replacing opcode 0x71 at 0x0049
09-14 07:05:48.220: DEBUG/dalvikvm(382): VFY: dead code 0x004c-0064 in Lcom/example/MyActivity;.onCreate (Landroid/os/Bundle;)V
09-14 07:05:48.300: ERROR/test(382): error 1
java.lang.NoSuchMethodException: getAltitude
at java.lang.ClassCache.findMethodByName(ClassCache.java:308)
Skipped stack trace
09-14 07:05:48.300: ERROR/test(382): error 2
java.lang.NoSuchMethodError: android.hardware.SensorManager.getAltitude
at com.example.MyActivity.onCreate(MyActivity.java:35)
Skipped more stack trace
09-14 07:05:48.330: DEBUG/dalvikvm(33): GC_EXPLICIT freed 2 objects / 64 bytes in 180ms
09-14 07:05:48.520: INFO/ActivityManager(59): Displayed activity com.example/.MyActivity: 740 ms (total 740 ms)
You can take advantage of how class isn't loaded until it is accessed for an easy work around that doesn't require reflection. You use an inner class with static methods to use your new apis. Here is a simple example.
public static String getEmail(Context context){
try{
if(Build.VERSION.SDK_INT > 4) return COMPATIBILITY_HACK.getEmail(context);
else return "";
}catch(SecurityException e){
Log.w(TAG, "Forgot to ask for account permisisons");
return "";
}
}
//Inner class required so incompatibly phones won't through an error when this class is accessed.
//this is the island of misfit APIs
private static class COMPATIBILITY_HACK{
/**
* This takes api lvl 5+
* find first gmail address in account and return it
* #return
*/
public static String getEmail(Context context){
Account[] accounts = AccountManager.get(context).getAccountsByType("com.google");
if(accounts != null && accounts.length > 0) return accounts[0].name;
else return "";
}
}
When the question is "Do I have this class or method at the current API level?" then use branching like:
class SomeClass {
public void someMethod(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)
{
//use classes and/or methods that were added in GINGERBREAD
}
}
}
For this you need to use an Android library that is Gingerbread or above. Otherwise the code won't compile with the classes added in Gingerbread.
This solution is MUCH more cleaner than the disgusting reflection stuff. Note that the dalvik will log a (not-lethal) error stating that he cannot find the classes added in GINGERBREAD when trying to load SomeClass but the app won't crash. It would only crash if we would try to USE that specific class and enter the IF branch - but we don't do that (unless we are on GINGERBREAD or later).
Note that the solution also works when you have a class that were there forever but a new method was added in Gingerbread. In runtime if you are running on pre-Gingerbread you just don't enter the IF branch and don't call that method thus the app will not crash.
Here how you do it using reflection (Calling StrictMode class from the level where it is not available:
try {
Class<?> strictmode = Class.forName("android.os.StrictMode");
Method enableDefaults = strictmode.getMethod("enableDefaults");
enableDefaults.invoke(null, new Object[] {});
} catch (Exception e) {
Log.i(TAG, e.getMessage());
}
I haven't tried it - but it should be possible, using some code generation, to create a proxy library (per API level) that will wrap the entire native android.jar and whose implementation will try to invoke the methods from android.jar.
This proxy lib will use either the above mentioned internal-static-class way or reflection to make the dalvikvm lazily link to the requested method.
It will let the user access all the API she wants (assuming she'll check for correct API level) and prevent the unpleasant dalvikvm log messages. You could also embed each method's API level and throw a usable exception (BadApiLevelException or something)
(Anyone knows why Google/Android don't already do something like that?)

What if I want to release an update with higher minSDK than the one on the market?

I have released an app on the market with minSDK set to 4 (Android 1.6) but now I want to release an update with features unavailable in 1.6 so I need a higher minSDK.
So, my question is: Will users running 1.6 be notified of this update?...and if yes will they be able to download/install it?
No they shouldn't be notified of the update. The market will filter the application out all together and they will no longer be able to see it or receive updates.
If you want to add features that use a higher api level but not exclude user's of a lower api level you can use some reflection to enable this:
public static Method getExternalFilesDir;
try {
Class<?> partypes[] = new Class[1];
partypes[0] = String.class;
getExternalFilesDir = Context.class.getMethod("getExternalFilesDir", partypes);
} catch (NoSuchMethodException e) {
Log.e(TAG, "getExternalFilesDir isn't available in this devices api");
}
This piece of code is saying:
Within the Context.class have I got this method
getExternalFilesDir (API level 9)
If so instantiate the variable getExternalFilesDir as a reflective call to this method else leave it as null.
Then later on you can simply do
// If the user's device is API9 or above
if(getExternalFilesDir != null){
// Invoke is basically the same as doing Context.getExternalFilesDir(var1, var2);
getExternalFilesDir.invoke(variable1, variable2);
} else {
// User cannot use this method do something else
}
Hope that helps

How to programatically hide Caller ID on Android

on Android phones, under Call -> Additional settings -> Caller ID
it is possible to hide your caller ID. I want to do that programatically from my code, but was not able to find a way to do that.
I searched through
android.provider
android.telephony
for 2.1 release and was not able to find it.
Has anybody successfully solved this issue?
Thanks in advance. Best regards.
Here I will describe two approaches I tried.
1.) It is possible to display Additional Call Settings screen from your application. Although it looks like it is part of the Settings application, that is not true. This Activity is part of the Native Phone Application, and it may be approached with the following intent:
Intent additionalCallSettingsIntent = new Intent("android.intent.action.MAIN");
ComponentName distantActivity = new ComponentName("com.android.phone", "com.android.phone.GsmUmtsAdditionalCallOptions");
additionalCallSettingsIntent.setComponent(distantActivity);
startActivity(additionalCallSettingsIntent);
Then user has to manually press on the CallerID preference and gets radio button with 3 options.
This was not actually what I wanted to achieve when I asked this question. I wanted to avoid step where user has to select any further options.
2.) When approach described under 1.) is executed in the Native Phone Application, function setOutgoingCallerIdDisplay() from com.android.internal.telephony.Phone has been used.
This was the basis for the next approach: use Java Reflection on this class and try to invoke the function with appropriate parameters:
try
{
Class <?> phoneFactoryClass = Class.forName("com.android.internal.telephony.PhoneFactory");
try
{
Method getDefaultPhoneMethod = phoneFactoryClass.getDeclaredMethod("getDefaultPhone");
Method makeDefaultPhoneMethod = phoneFactoryClass.getMethod("makeDefaultPhone" , Context.class);
try
{
makeDefaultPhoneMethod.invoke(null, this);
Object defaultPhone = getDefaultPhoneMethod.invoke(null);
Class <?> phoneInterface = Class.forName("com.android.internal.telephony.Phone");
Method getPhoneServiceMethod = phoneInterface.getMethod("setOutgoingCallerIdDisplay", int.class, Message.class);
getPhoneServiceMethod.invoke(defaultPhone, 1, null);
}
catch (InvocationTargetException ex)
{
ex.printStackTrace();
}
catch (IllegalAccessException ex)
{
ex.printStackTrace();
}
}
catch (NoSuchMethodException ex)
{
ex.printStackTrace();
}
}
catch (ClassNotFoundException ex)
{
ex.printStackTrace();
}
Firstly I tried just to use getDefaultPhone(), but I get RuntimeException
"PhoneFactory.getDefaultPhone must be called from Looper thread"
Obviously, issue lies in the fact that I tried to call this method from the Message Loop that was not the Native Phone App one.
Tried to avoid this by making own default phone, but this was a security violation:
ERROR/AndroidRuntime(2338): java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.provider.Telephony.SPN_STRINGS_UPDATED from pid=2338, uid=10048
The only way to overcome (both of) this would be to sign your app with the same key as the core systems app, as described under
Run secure API calls as root, android
I'm not sure if this is a global feature, but Australian phones can hide their number by prefixing the caller's number with #31# or 1831. This may not be the perfect solution, but a prefix like this could possibly work for your requirements during coding.

Categories

Resources