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.
Related
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.
Feel free to improve the title I'm a little uncreative in this special case.
I am implementing a unit test for checking notifications, this is hardly possible I know, but I want to check how far I can automatism it.
I cannot test this simple line of code:
Notification test = new NotificationCompat.Builder(context).build();
The reason is stupid and simple in one. This code here will been executed internally:
public Notification build(Builder b, BuilderExtender extender) {
Notification result = b.mNotification;
result.setLatestEventInfo(b.mContext, b.mContentTitle,
b.mContentText, b.mContentIntent);
[...]
I'm getting this exception:
Caused by: java.lang.NoSuchMethodError: android.app.Notification.setLatestEventInfo(Landroid/content/Context;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Landroid/app/PendingIntent;)V
at android.support.v4.app.NotificationCompat$NotificationCompatImplBase.build(NotificationCompat.java:479)
at android.support.v4.app.NotificationCompat$Builder.build(NotificationCompat.java:1561)
It is not hard to guess that the Google guys call here a method which was removed (or more properly annotated with #hide) in the Android Marshmallow SDK. I have verified it that call is missing the the newest documentation, but it was introduced in API 1 AFIK.
How can I work around that?
Things I tried and got stuck:
Overriding the callback, and mock that method without invoking that call:
I got it managed to get that Class<?> with the callback, but with which method I can override a hole method? I mean I need to patch the call it I cannot just mock it.
Injecting that call, but how? I can just override it and not adding it.
Suppressing the call with:
PowerMockito.spy(Notification.class);
PowerMockito.suppress(PowerMockito.method(Notification.class, "setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class));
Does not work ether since I try to kick a non existing method.
Change the target SDK for this test, but how can I do it?
The solution is easier than expected. I missed that by default Build.VERSION.SDK_INT has the value 0 since it cannot read the real value. So that support library calls it on just that platforms where this method exists.
With the help of this answer. I just had to add this code:
setFinalStatic(Build.VERSION.class.getDeclaredField("SDK_INT"), 23);
And my the codes works.
Well it still crashes somewhere else, but the notification is created. Wohoo!
And the actual function:
public static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException, NoSuchFieldException
{
field.setAccessible(true);
// remove final modifier from field
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
Is there an easy way to crash an app with a native crash, in order to test native crash reporting?
note that I'm looking for a general solution for all devices, and not device specific. I thought about using the Unsafe class (writing illegal addresses into the stuck), but it looks like it's not supported
If you want to cause a crash from Java code, use dalvik.system.VMDebug.crash(). This is not part of the public API, so you will need to access it through reflection. This worked for Dalvik; I don't know if it still works for Art.
Some of the sun.misc.Unsafe methods are supported, so you may be able to cause a crash by selecting an appropriate value for offset in calls like putIntVolatile(). If the offset is the negation of the Object pointer you'll dereference address zero and crash.
The most reliable way is to create a trivial native library with the NDK. I personally favor storing a value in a "named" address, like 0xdeadd00d, because they let you know that it was your code crashing deliberately, but null pointer derefs work too.
As #fadden pointed out the use of dalvik.system.VMDebug.crash(), here is a helper method to access it via reflection.
public void crashNatively() {
try {
Class.forName("dalvik.system.VMDebug")
.getMethod("crash")
.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
like :
public TestBean check(#NonNull TestBean abc) {
System.out.println();
return abc;
}
#NonNull api doc say:
Denotes that a parameter, field or method return value can never be null
I have a question: if abc is null, why it executes this method?
These annotations may used by code analysis tools (and IDEs) and documentation. They aren't enforced by the runtime.
An example is given here in the android support kit documentation: http://tools.android.com/tech-docs/support-annotations
The new Android Studio supports these annotations, screenshot in the link.
Java 8 introduced similar annotations and this documentation goes into a little more detail about how these annotations work with tools and IDEs to help developers detect problems earlier. In plain Java, you can use the Bean Validation API to enforce null checking declaratively at runtime, I don't know if there's any similar capability in Android.
They just make it more easier to check underlying NULL point exception in your code time.
I'm targeting Android 2.2 and newer. This error was generated on a device running 4.x. I am using ORMLite 4.38 libraries.
I need to guarantee every record instance is unique for any number of devices. I was happy to see that ORMLite supports UUIDs as IDs. I've created a UUID - id abstract base class for my database record definitions. allowGeneratedIdInsert is the perfect solution. But this feature seems to cause an 'IllegalStateException: could not create data element in dao'. I tested by removing this annotation, and no issue. Put it back in...same issue. Put the base class stuff in one record definition...same issue.
LogCat also reports:
Caused by: java.sql.SQLException: Unable to run insert stmt on object - objectid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
public abstract class UUIDDaoEnabled<T> extends BaseDaoEnabled<T, UUID> {
//allowGeneratedIdInsert allows us to set UUID when this device db didn't create it
#DatabaseField(generatedId = true, allowGeneratedIdInsert=true)
private UUID id;
...
public void setUUIDFromSerializedSource(SerializedModelBinaryInputStream stream, Dao<T, UUID> dao) throws SQLException { //only place we can set UUIDs
if(id == null)
dao.refresh((T)this);
if(id != null)
throw new SQLException("Trying to set UUID on existing object");
id = stream.getCurrentUUID();
}
}
I'll specialize like so:
#DatabaseTable()
public class Type extends UUIDDaoEnabled<Type> { ... }
I can't explain this from the documentation for allowGeneratedIdInsert and generatedId. In fact the documentation for alloeGeneratedIdInsert says it overrides the default behavior of generatedId. It also says
This only works if the database supports this behavior
Yet, I have read in other posts that ORMLite 4.25 (?) and newer supports this behavior on Android devices. So, either that's not entirely true. Or I'm doing something stupid...anyone???
UPDATE: after thinking about it for a minute, I realized that neither allowGeneratedIdInsert support, nor inheritance can be the root cause, because I instantiate other objects based on the same abstract class. What I can't figure out is why one particular class is causing the issue. The only unique thing about the offending record type (compared to other types that create) is it is a many in a one to many, and it contains several to manies. Could these properties, combined with allowGenereatedIdInsert, be the root issue? Rather, I should ask, has anyone seen this issue in this circumstance?
UPDATE: nevermind the question. I can use updateId(...) instead of allowGeneratedIdInsert.
So I'm not sure about this but it looks to me that you are trying to insert an element twice into a table with the same UUID id. The exception is saying there is a constraints failure:
IllegalStateException: Could not create data element in dao
at BaseForeignCollection.add(BaseForeignCollection.java:57)
...
Caused by: SQLiteConstraintException: error code 19: constraint failed
If you call foreignCollection.add(...); it does the same thing as dao.create(...); -- and you can't do both of these with the same object. If you have an existing object that has already been created by the DAO and you want to associate it with another object, you should do something like:
// associate this object with another
existingObject.setForeignField(...);
// now update it in the db
existingObjectDao.update(existingObject);
You can't add it to the foreignField's foreign collection.
I had a similar problem. But it was caused by using create instead createOrUpdate to save the object.
It is also important to uninstall the application before changing this to ensure that the database has been removed and will not keep the old behavior.
Edit: createOrUpdate is very time expensive. It's better use just create with great amounts of data.
Edit 2:It is also bether to use a TransactionManager.callInTransaction.