android compatibility. I am confused when using Build.VERSION_CODES - android

Log.d(TAG, "Build.VERSION_CODES.ICE_CREAM_SANDWICH: " + Build.VERSION_CODES.ICE_CREAM_SANDWICH);
I write code like this, I used the sdk4.0 to compile this android program, so it didn't cause compile error. When I run this program in my phone that running android 2.3.4, it run well.
Why? I am confused that version 2.3.4 (api level 10) has Build.VERSION_CODES.ICE_CREAM_SANDWICH property? And when I used sdk2.3.4 will cause compile error.
More
I test some code like these below,
private ScaleGestureDetector mScaleGestureDetector;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
mScaleGestureDetector = new ScaleGestureDetector(this, new MyOnScaleGestureListener());
}
this code will run well on android 1.6 api level 4, but
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
Log.d(TAG, "getx(0): " + event.getX(0));
}
this program run failed on android 1.6 api level 4.
They both run on android 2.3.4 well.
why? (In ScaleGestureDetector class use the event.getX(0) (since api level 5) too)
I test some code more..
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Fragment f = new Fragment();
}
When I run it on android 1.6 emulator it throw java.lang.VerifyError, but on my phone running android 2.3.4 it throws java.lang.NoClassDefFoundError.
Why??

It's not as strange as it seems. It has to do with how Java treat constant values of primitives. During compilation, the value of the constant is put in the byte code, not a reference to the actual constant.
For example:
Log.d(TAG, "Build.VERSION_CODES.ICE_CREAM_SANDWICH: " + Build.VERSION_CODES.ICE_CREAM_SANDWICH);
will actucally be translated by the compiler to:
Log.d(TAG, "Build.VERSION_CODES.ICE_CREAM_SANDWICH: " + 14);
so the reference to the actual constant (and class) is removed.
Regarding the code that doesn't run for you, it has to do with that the MotionEvent.getX(int n) method wasn't available until api level 5. Before that, multitouch wasn't supported and thus no need for any other method than getX().
It doesn't matter if you actually call the method that doesn't exist. The error is appearing while the class is being loaded and verified by the platform. You most likely get a VerifyError in the log, since it will discover that you're trying to call a non-existent method during the verification.
On the other hand, if you try to use a class that doesn't exist, you will get a ClassNotFoundException instead. Note that sometimes, a class exists in Android even if the documentation doesn't say so. Some classes existed in early versions of Android but weren't exposed until later. Some have even gone the other way.
So:
Trying to use a class that doesn't exist - ClassNotFoundException
Trying to use a method that doesn't exist on a class that exists - VerifyError
(When it comes to using Fragments, they are available for earlier versions with the standalone Android Support Library)

Related

Log.isLoggable problems with android 6.0 (Marshmallow / api 23 )

I'm using android Log.isLoggable api in order to determine whether my custom log tag is on and should I log. (I'm setting property using setprop log.tag. on the device).
As far as Docs says, and as I'm familiar with older version, it should return true if the log level in the property is equal or high than the one I'm checking in my code.
This works fine for Lollipop and below (api 22) it's seems that something was changed in Marshmallow, as I encounter inconsistency and buggy behavior, playing with the values of the tag will result sometimes with the wrong value returned from isLogabble(), for instance a concrete scenario I did a check in code :
boolean shouldLog = Log.isLoggable("mytag", Log.DEBUG);
Log.d("debug", "shouldLog = " + shouldLog);
I set log.tag.mytag value to some arbitrary string
launch my app, and see the isLoggable = false, that's OK
then I change log.tag.mytag to VERBOSE
launch my app, isLoggable still false , that's not OK! should be true now
same scenario is not reproducible at Lollipop and didn't encounter any other misbehaviors.
Any suggestions, do I miss something here?

Why UsageStats.mLaunchCount is not accessible in IDE?

I have all permissions in place and can get the list of apps, and their usage stats as a list of UsageStats instances. UsageStats has a public field called mLaunchCount, added on API 22 (based on git history of the file). Now I want to access this if the phone is running API 22+, but when I try to use it, the IDE complains Cannot resolve symbol mLaunchCount. If I try to access it via reflection, it works.
So basically this does not compile:
Log.d("test", "Count: " + usageStat.mLaunchCount);
While this does:
Field mLaunchCount = UsageStats.class.getDeclaredField("mLaunchCount");
int launchCount = (Integer)mLaunchCount.get(usageStat);
Log.d("Test", "Count: " + launchCount);
Any idea what's happening?
Thanks
Because mLaunchCount is not part of the Android SDK. In the source code, it is marked with the #hide annotation, used for things that are public for Java reasons but are not part of the SDK.

Is it possble to target Android 2.1 and use CalendarProvider on devices with Android 4.0 and higher?

Is it possble to target Android 2.1 and use CalendarProvider on devices with Android 4.0 and higher or is it can only be achieved by creating 2 separate APKs?
That depends on what you mean by "target".
If you mean "set targetSdkVersion" to Android 2.1, you can still use whatever APIs you want, so long as you only try calling them when you are running on a device that has them.
If you mean "set the build target" to Android 2.1, you can still use whatever APIs you want, so long as you use reflection to access the ones that are newer than API Level 7. Since CalendarContract is a content provider, that mostly is a matter of accessing various static data members, such as CONTENT_URI. Here is an example of using reflection to get at a CONTENT_URI value:
private static Uri CONTENT_URI=null;
static {
int sdk=new Integer(Build.VERSION.SDK).intValue();
if (sdk>=5) {
try {
Class<?> clazz=Class.forName("android.provider.ContactsContract$Contacts");
CONTENT_URI=(Uri)clazz.getField("CONTENT_URI").get(clazz);
}
catch (Throwable t) {
Log.e("PickDemo", "Exception when determining CONTENT_URI", t);
}
}
else {
CONTENT_URI=Contacts.People.CONTENT_URI;
}
}
(note: this example is designed to run on Android 1.5 and higher -- depending on your minSdkVersion, you could use Build.VERSION.SDK_INT instead of new Integer(Build.VERSION.SDK).intValue()).
If by "target" you mean something else, then we would need clarification of your use of the verb "target".

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?)

How to call "isInitialStickyBroadcast()" and avoid problems on 1.6?

isInitialStickyBroadcast() is obviously only available after 2.0 (SDK 5).
I'm getting this error:
"Uncaught handler: thread main exiting due to uncaught exception
java.lang.VerifyError"
It's only happening on 1.6. Android 2.0 and up doesn't have any problems, but that's the main point of all.
I can't catch the Error/Exception (java.lang.VerifyError), and I know it's being caused by calling isInitialStickyBroadcast() which is not available in SDK 4, that's why it's wrapped in the SDK check.
I just need this BroadcastReceiver to work on 2.0+ and not break in 1.6, it's an app in the market, the UNDOCK feature is needed for users on 2.0+ but obviously not in 1.6 but there is a fairly substantial number of users still on 1.6.
Here's an easy-to-read version of part of the code I'm using. Notice that it's wrapped in an SDK check to only run on 2.0+, but the VerifyError is still showing up.
private BroadcastReceiver mUndockedReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
//FROM ECLAIR FORWARD, BEFORE DONUT THIS INTENT WAS NOT IMPLEMENTED
if (Build.VERSION.SDK_INT >= 5)
{
if (!isInitialStickyBroadcast()) {
//Using constant instead of Intent.EXTRA_DOCK_STATE to avoid problems in older SDK versions
int dockState = intent.getExtras().getInt("android.intent.extra.DOCK_STATE", 1);
if (dockState == 0)
{
finish();
}
}
}
}
};
Your problem is that while you would not be executing isInitialStickyBroadcast(), the classloader attempts to resolve all methods when the class is loaded, so your SDK 4 devices fail at that point, since there is no isInitialStickyBroadcast().
You have two main options:
Use reflection.
Create two editions of your BroadcastReceiver, as public classes in their own files. One has the SDK 4 logic, one has the SDK 5+ logic. Register the one you want based on an SDK check at the time you call registerReceiver().

Categories

Resources