Using Java reflection vs checking Build.VERSION.SDK_INT - android

Why should I bother using reflection as discussed here, if I can simply test Android version from Build.VERSION.SDK_INT and conditionally run functions not available on lower API versions?
That article discussed how to get method ID, handle exceptions, etc, which seems more complicated than simply using:
if(Build.VERSION.SDK_INT>=11){
// some Honeycomb code
// example: findViewById(R.id.root).setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
}
This code works fine for me on various devices (2.2 / 3.2 / etc).
Thanks

Your proposal won't work (without reflection) when running on an older Android system, if the code hidden in "// some Honeycomb code" uses Class or Method names that only exist in the Honeycomb API. The root of the problem is that all classes referenced from code are loaded when the class is loaded. You need to use reflection to delay resolution of the code containing Honeycomb references until run-time.
Specifically, if you have a class:
class MyUseOfFeatures {
public void doSomething() {
if (TestIfPhoneHasFancyHoneycombFeature()) {
Object example = android.util.JsonReader(); // JsonReader is new in 3.0
}
}
Then when the JVM (er, DVM?) loads the bytecode for this class it will try and resolve the android.util.JsonReader name when the class is loaded (presumably when your application is loaded).
If you only rely on some behavior of Honeycomb (not any any new classes, methods or fields), then you'd be fine to just test the build number.

Related

Filtering Android Connected Tests

I have a few "connected" tests that are only relevant to run on a specific device model or on a specific brand and should be skipped on other brands/models.
I may be missing something, but this kind of filtering seems not possible out-of-the-box with AndroidJUnitRunner (by using annotation and/or passing appropriate arguments to it).
So, I was thinking to extend the AndroidX test framework to support this kind of filtering. In the end, I would like to be able to filter test with something like this
#TargetDeviceFilter(brand="SAMSUNG",model="XCover3")
#Test
public void myTestToRunOnSamsungXCover3DeviceOnly(){
...
}
My question: is there any way to accomplish this kind of filtering without extending AndroidX test framework? And if writing my own AndroidJUnitRunner and/or my own annotations is required, how should I start ?
I found a few interesting base classes that I may need to extend like :
androidx.test.internal.runner.TestRequestBuilder
androidx.test.internal.runner.TestRequestBuilder.DeviceBuild
but as those classes are in a "internal" package: attempting to extend them is probably not a good idea?
Any advice on how to deal with that problem is welcome.
I think, you may use org.junit.Assume.
Create a helper class DeviceHelper to detect mobile device informations for convenience.
Your test logic will be executed only if the assumption is correct.
#Test
public void myTestToRunOnSamsungXCover3DeviceOnly() {
// adapt this part to your business need
org.junit.Assume.assumeTrue(
DeviceHelper.isBrand("SAMSUNG") &&
DeviceHelper.isModel("XCover3")
);
// i.e. you can filter whatever you want test's according to device sdk_int
assumeTrue(SomeHelper.getDeviceSdk() >= 21);
// your test code
}

How to test ContentProvider with ProviderTestRule in kotlin

I have implemented a ContentProvider that uses a Room database to store the data. The implementation is done in kotlin and it follows the same pattern shown in this Google example.
The ContentProvider works fine when used in an app. Now I want to write some tests and I am relying on ProviderTestRule for doing so. The configuration I have seems fine, but unfortunately I am getting the following exception, which looks like some initialisation is missing and then the context is not available.
java.lang.UnsupportedOperationException
at androidx.test.rule.provider.DelegatingContext.getSystemService(DelegatingContext.java:277)
at androidx.room.RoomDatabase$JournalMode.resolve(RoomDatabase.java:517)
at androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:943)
I wasn't able to find any example of how to test this scenario. Any hint would be really helpful!
ProviderTestRule internally uses DelegatingContext, which is a wrapper around the application context that purposely limits its capabilities.
From the source code you can see that context.getSystemService is stubbed out, throwing UnsupportedOperationException most of the time:
/**
* This method only supports retrieving {#link android.app.AppOpsManager}, which is needed by
* {#link android.content.ContentProvider#attachInfo}.
*/
#Override
public Object getSystemService(#NonNull String name) {
checkArgument(!TextUtils.isEmpty(name), "name cannot be empty or null");
// getSystemService(Context.APP_OPS_SERVICE) is only used in ContentProvider#attachInfo for
// API level >= 19.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& Context.APP_OPS_SERVICE.equals(name)) {
return context.getSystemService(Context.APP_OPS_SERVICE);
}
throw new UnsupportedOperationException();
}
I have no clear explaination why they forbid access to system services for ProviderTestRule in the first place.
Unfortunately, it seems that Room requires access to the ActivityManager in order to find the most appropriate JournalMode.
What you can try to workaround the situation:
Force the JournalMode of you Room database to JournalMode.WRITE_AHEAD_LOGGING (or JournalMode.TRUNCATE), or
If it did not solve the situation, you'd have to write your own ProviderTestRule that uses the real application context to and allow access to the desired system service.

LifecycleObserver produce exception with methods that use newer APIs

My ViewModel class implements LifecycleObserver.
When I call fragment.lifecycle.addObserver(this) it produces exception.
Caused by: java.lang.IllegalArgumentException: The observer class has some methods that use newer APIs which are not available in the current OS version. Lifecycles cannot access even other methods so you should make sure that your observer classes only access framework classes that are available in your min API level OR use lifecycle:compiler annotation processor.
Strange, that firstly it was working fine, but not long ago this exception has appeared. I've found, that audioFocusRequest is cause of this bug.
private val audioFocusRequest by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener(this)
.build() else throw RuntimeException("Can't be done for Android API lower than 26")
}
Does anybody know how it can be fixed?
UPD
Tried to use annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version", but got compilation error:
(decided to paste screenshot, because whole logs are quite big)
UPD 2
At the end I've decided to delete audioFocusRequest field and to use old deprecated method - requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) instead of recommended requestAudioFocus(#NonNull AudioFocusRequest focusRequest)
It helped me to make code working again, so it can be solution. But I didn't find answer - why this problem had appeared. It strange because code used to be working before.
So problem has been solved but question still stays unanswered
Try to use kapt "androidx.lifecycle:lifecycle-compiler:2.0.0"
The class which implements LifecycleObserver has some method, which has parameters with type that only exist for higher APIs.
Your variables (i guess) and function parameters must exist on all APIs even function is not called (maybe this is requirement for classes who implement LifecycleObserver).
A possible solution is to change function parameter type to Any (kotlin) or Object (Java) and cast it to appropriate type inside function.
I have to remove this set method on SpinnerView: lifecycleOwner = viewLifecycleOwner
I was able to fix this by moving the offending methods into another class, but still called from my LifecycleObserver. After reading the error message again:
Caused by: java.lang.IllegalArgumentException: The observer class has some methods that use newer APIs which are not available in the current OS version. Lifecycles cannot access even other methods so you should make sure that your observer classes only access framework classes that are available in your min API level OR use lifecycle:compiler annotation processor.
It seems as though no methods or objects are allowed in the class extending LifecycleObserver if they don't exist in the device's OS, even if they are wrapped in an SDK version check and never accessed.

Has MatrixCursor implementation within Android P changed?

Recently I have observed a large number of crashes for an app that I maintain when the Android P developer preview is used.
Diving (deep) into the project's code, I have found the problem method to be the following:
public static <T> T get(MatrixCursor cursor, int column) {
try {
cursor.moveToFirst();
Method get = MatrixCursor.class.getDeclaredMethod("get", int.class);
get.setAccessible(true);
return (T) get.invoke(cursor, column);
} catch (Exception e) {
throw new IllegalArgumentException("Android has changed the implementation of MatrixCursor?!");
}
}
From what I understand, this code is used to retrieve a custom object from the MatrixCursor directly, rather than a primitive type, byte array or String. There has previously been a private method within MatrixCursor that performs this internally, and it is this method that we access through reflection.
Needless to say, there's a number of issues with this approach. As far as I am aware, reflection to access private APIs is a feature that Android advises heavily against. Nevertheless, until the Android P preview, this seems to have been working as expected.
This leads me to raise the following questions:
Has MatrixCursor's implementation changed or is reflection totally deprecated as of Android P?
Sadly, I am not 100% clued up on what alternatives I have to avoid this issue. Any suggestions for that are greatly appreciated, is there a Cursor that can be used to store custom objects?
Yes, something has changed.
No, the underlying implementation of MatrixCursor has likely not changed.
What has changed is that Android P is introducing restrictions on non-public members of SDK classes. Attempting to use private fields or methods on SDK classes (whether by direct invocation, reflection, or JNI) will result in a crash.
If you run the code in question on a device running P and look at the logcat output, you should see a message similar to this:
Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)
I highly encourage you to fully read the linked documentation on these restrictions for the full context and for more information on how you can handle it.
One option (which you should do ASAP if needed!) is to file a bug so the Android team knows that this is a method you use and does not have a public alternative. If you do this before the release of Android P, there is a much better likelihood that the team will either create a public alternative for this method or allow you to continue to access that method in P.

Android unit test, super deprecated, replacement?

The android unit testing documents say to test with junit like this
public SpinnerActivityTest() {
super("com.android.example.spinner", SpinnerActivity.class);
} // end of SpinnerActivityTest constructor definition
http://developer.android.com/tools/testing/activity_test.html#InstallCompletedTestApp
Their example uses Android 2.1 though.
My app is using Android 4.2 although is backwards compatible.
What has the super class been replaced with? How should my constructor be written
You will need to use the call:
super (SpinnerActivity.class);
It's specified in the Android reference docs.
ActivityInstrumentationTestCase2(String pkg, Class<T> activityClass)
This constructor was deprecated in API level 8. use ActivityInstrumentationTestCase2(Class) instead
Use this instead.
ActivityInstrumentationTestCase2(Class<T> activityClass)
Creates an ActivityInstrumentationTestCase2.
Parameters
activityClass The activity to test. This must be a class in the instrumentation targetPackage specified in the AndroidManifest.xml
The API level 24 recommendations are to use the Testing Support Library. The InstrumentationRegistry.getTargetContext() method is probably what you want if you were using getContext().

Categories

Resources