App crashes with ClassCastException on Application class testing M backup - android

I'm following the guide to specify exclusions from full backups but running into a crash when I try and test it.
$ adb shell bmgr fullbackup <PACKAGE>
Works fine - files are excluded as expected.
I clear data then run:
$ adb shell bmgr restore <PACKAGE>
The restore works fine but then the next time I try and run the app I'm getting a ClassCastException:
Caused by: java.lang.ClassCastException: android.app.Application cannot be cast to com.domain.app.MyCustomApplicationClass
It appears that for some reason there is an instance of my application but it's not an instance of the custom application class as specified in the manifest.
Running the app a second time works fine and I can verify that all data was correctly restored.
I am testing this on a debug build and would like to try and resolve this error before pushing the latest changes to production.

The usual cause of this is inducing a manual restore "the wrong way." This is very poorly documented, I'm afraid, but there are different ways of invoking "bmgr restore", one of which will cause exactly the problem you describe.
(The problem, specifically, is that full-data backup/restore operations currently require that the app be launched with neither its content providers nor any app-defined Application subclass instantiated; instead, you run with a base-class Application instance. Trying to cast back to your declared subclass throws ClassCastException as you might imagine.)
In the normal course of things, your app is killed following restore. HOWEVER, if you trigger the restore like this:
adb shell bmgr restore PACKAGE
this does not happen. That particular invocation syntax runs a "my app wants to restore its data 'live' right now; do not kill me before or after" code path, the one that you get via BackupManager.requestRestore(). In this code path the app is intentionally not killed following restore. It's an artifact of the time when key/value was the only backup/restore paradigm, and in that paradigm there are no such Application subclass issues etc.
You need to make sure that when you trigger a restore via bmgr, you are using the full syntax:
adb shell bmgr restore TOKEN PACKAGE
This syntax invokes the complete restore-at-install code path, the one that will tear down your app following the restore specifically to avoid trying subsequent execution with a base-class Application.
'TOKEN' is the identifier of the dataset containing the data you wish to restore. If you are using the local debugging transport, then TOKEN is always "1". If you are using cloud backup, then it will be the device's own current backup dataset identifier if there is one, or the ancestral dataset if the device has not generated one itself. You can see these in the output of
adb shell dumpsys backup | egrep 'Current:|Ancestral:'
The dataset's identifying TOKEN is the hex string given there.

Related

How to obtain activity name through UIAutomator2?

I'm building some general testing tool for Android apps and trying to get the activity name (e.g. com.android.calculator/.Calculator, the com.android.calculator can be obtained through UiDevice.currentPackageName, the pain is from the second part) during the testing. This is to say that my tool may test other apps that are not in the same package as mine. For example, testing the stock calculator app.
Here is a list of attempts I have made:
the UiDevice.currentActivityName is deprecated and it does not provide the accurate information
InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<ActivityManager>(), then access the ActivityManager.getRunningTasks(1).get(0).topActivity, which always returns the same value even though the view has switched (and this is deprecated too)
Similar to 2, I tried ActivityManager.getAppTasks().get(0).getTaskInfo().topActivity, but this keeps giving me null
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED). This gives me an empty collection
Application.ActivityLifecycleCallbacks. I registered it with the application, but no callback is received.
(Kinda works, but not perfect) using adb dumpsys activity then find it there. But this way I have to wait for some arbitrary time until the view is updated, otherwise the result is still inaccurate
So I am currently in a cul-de-sac now, and I appreciate any help on this issue. I try to avoid reflection, because as I know it does not work on newer devices. So any clue will be appreciated.
As there is no answer posted, I wanna share my current strategy, which is not perfect, but somewhat helpful.
So first of all, as UiDevice.getActivityName is deprecated, I can only obtain the activity name through adb. Specifically, I use adb shell dumpsys activity top for API lower than 28 and adb shell dumpsys activity activities otherwise.
Then I subscribed to AccessibilityEventListener callbacks. Specifically, I run the adb command whenever I receive callbacks with type AccessibilityEvent.TYPE_WINDOWS_CHANGED. You can set up AccessibilityEventListener using this method: setOnAccessibilityEventListener

How to get an Application object in a java shell process started using app_process?

On my Android device, I am launching a Java class via app_process so that my Java classes can run as a shell user. This approach is similar to the one described in scrcpy where I use a command like
adb shell CLASSPATH=/data/local/tmp/classes.dex \
app_process / my.package.MainClass
The classes launch just fine and work as expected. However, I also want to get an Application object so that I can use some Android services like VPNService. I tried creating one by calling new Application() but it seems to just be a stub and throws NullPointerException.
The only hint I could find is to run the classes via the activity manager as said here. But I could not figure out how to do that.
Thus, how can I launch my java classes running them as a shell user and get an Application object which can be passed to Android APIs?
You can get an instance of Application with Activity#getApplication.
E.g in your activity just call getApplication() should give you your application instance.

Android 6 auto restore not initializing app

Been testing the new Android 6 auto backup/restore function, and run into a problem with my app crashing immediately after a restore. Further investigation revealed that the Application.onCreate() initialization method was not being called before the main Activity.onCreate() method. This strikes me as a likely bug in the new autorestore logic. But I thought I would ask for advice here before reporting it as an official bug.
The sequence of events I go through is
Run the app, always open a main activity window.
Force a backup of app data by entering
adb shell bmgr fullbackup net.anei.cadpage
Use the app manager to force close the app and to clear all app and cache data
Restore app information with
adb shell bmgr restore
Manually launch the app
Resulting logs show that the Activity.onCreate() method is called before the Application.onCreate() is. The app crashes because some critical initialization was not performed by the Application.onCreate() method.
Is there something obvious that I am missing???
FWIW, launching the app a second time after the crash works perfectly.
It's actually intentional, though intrusive.
For full-data backup and restore operations, the package is launched with a base class Application instance, not your manifest-declared subclass. This is because, unfortunately, many apps open files or databases via Application subclasses, and this blocks the ability of the backup machinery to correctly read/write the underlying files. Similarly, your app's content providers are not automatically instantiated for full-data backup/restore operations. The app process is then destroyed following the operation, because of course your app cannot continue to run normally without its expected Application subclass or content providers.
You also don't say exactly what command you're using to perform a test restore, but I suspect you're using the bmgr command with this syntax:
adb shell bmgr restore PACKAGE
This doesn't do what you expect. In particular, it invokes the code path that happens when your app calls BackupManager.requestRestore(observer). In this specific code path, the app is NOT shut down following the restore operation, because the app has asked to observe the operation itself. This means that you're left with the app process still running but with a base class Application. It's a power-user API that is pretty much only safe when the app uses the original key/value backup API. You need to test instead using the other bmgr syntax:
adb shell bmgr restore TOKEN PACKAGE
where TOKEN is the identifier for which dataset should be used. At least on the most recent versions of the OS you can see the current and ancestral dataset tokens in the output of adb shell dumpsys backup.
This all needs to be better documented and made less surprising.
Subclassing Application is generally discouraged; this is one reason. Try to use your own lazy-init statics instead of subclassing Application.

Testing an Activity which uses a ContentResolver

In my app, I have an Activity, which is basically a form for the user to enter data which is then inserted into a database table via a ContentResolver. How do I test this Activity?
My first attempt was to use ActivityInstrumentationTestCase2 which gives me full instrumentation to simulate entering data. However, the underlying ContentProvider is not closed and destroyed between each test, which leaves the database in an unknown state at the beginning of subsequent tests.
My second attempt was to use ActivityUnitTestCase and inject a mock context that can clean up the database for each test. However, this doesn't allow me to enter text or click on buttons in the activity as it is never actually drawn on the test device.
Does anyone have any suggestions about what else I can try?
it seems that what you've been using is intended for library development
You should look at the monkey binary here , which works great for me.
If you're not satisfied with it you could use monkeyRunner which provides more control over the tests you're running.
Edit :
As far as the database testing goes , cant you use the sqlite3 binary for a simple query after each test?
Edit2:
I am thinking of a .sh script that does the following :
Runs monkey for a while - you can specify the number of events for the monkey to send
Invoke sqlite3 with a query that would check the database integrity into a log file (sqlite3 command can take sql query as a second parameter, and you can use ">" to write the output into some file)
Repeat.
There are tons of examples for .sh scripting on the net so you shouldn't have problem with that.
I am assuming you're doing all this in adb shell, but if you're not, make sure to set all your environment variables correctly. Particularly ANDROID_ROOT, ANDROID_ASSETS and ANDROID_DATA should be set to "/system","/system/app" and "/data" accordingly . Also don't forget to "chmod" the .sh file to be executable ( chmod 777 file.sh ).
Another suggestion is to generate and keep track of the monkey random seeds so you can repeat certain inputs that are causing you problems. You can specify a seed with -s parameter.

Enabling logcat in init.rc

I'm appending init.rc in Android root with:
service logcat /system/bin/logcat -v long -f /mnt/sdcard/logcat.log
This solution doesn't generate any logs. The logcat.log file doesn't exist.
How can i start gathering logcat output through init.rc ?
A couple of things that could be causing problems above:
1. you defined your service to be called logcat. That looks awfully close to what might be a reserved/pre-existing name. I would choose a more distinguished service name.
2. there is no explicit start trigger for the service, hence its entirely dependent on the context in which its defined (i.e. which init phase). Pick the wrong phase (i.e. too early) and /mnt may not even exist.
3. the service will by default be running as root and thus the logcat.log file will be rw only by root. Not good to run processes as root. And not good to force readers to be root in order to read the log file.
Here the approach I've used to achieve what you're looking to do.
Problem: Ordinarily, Android log messages remain in the kernel’s (volatile) memory only and thus doesn’t survive across reboots.
Solution: To retain those log messages across reboots requires them to be written to persistent storage (i.e. the filesystem). The following code defines such a service that is started by Android during init.
Step 1, define a service that the Android init process will spawn to do this activity. This goes in init.rc.
service persistentLogging /system/bin/logcat -r 1024 -n 9 -v threadTime -f /cache/logs/log
user system
group system log
disabled
Notes about the above:
it creates a service called persistentLogging (that will be referred to in the second step below) by the start trigger.
it requests logcat to do a rolling log file (consisting of 10 files / 1Mb each) in directory - /cache/logs (i.e. log, log.1, log.2, … log.9). Adjust to suit your needs.
the service is to run as system user. This means the log file will be read+write only by system. If your app has system privileges then you’ll be able to read the log file. I’ve also defined the service to be in the log group too since that seems appropriate although since the files are not readable by group its a moot point.
the service is initially disabled. It will be started by a trigger defined below
the service is NOT oneshot. Hence, should it die, Android will attempt to restart it.
Step 2, define a trigger for starting the service. This also goes in your init.rc file.
on post-fs
mkdir /cache/logs 0775 system log
start persistentLogging
Notes about the above:
the commands are triggered during the ‘post-fs’ phase so that they occur after filesystem partitions have been mounted and when other system directories are having their permissions changed. Ideally, this service should start as late as possible because its not important or used by any other start-up activity.
the trigger first creates the target directory before starting the service. Remember the mkdir command syntax is defined by the init.rc language. In Android this syntax is not a sh syntax eventhough it looks a lot like it.
eventhough the above logging service doesn’t start until the post-fs phase of init, it will nevertheless dump all logging information since the beginning of kernel's startup as these log messages are already in the kernel buffers and this logging service is merely copying those messages to a file.
although both code fragments above ultimately need to appear in init.rc file, it is more maintainable if those additions are made to the init.${ro.hardware}.rc file defined for the device. e.g. init.freescale.rc which is automatically included by init.rc.
If we need to start the logcat and collect all the log buffers after on post-fs-data/boot completed from init.rc you can use below code in init.rc file.
on property:dev.bootcomplete=1
start logging
on post-fs-data
start logging
service logging /system/bin/logcat -b all -v threadTime -f /data/directory_name/log -r 100000 -n 9
user system
group root system log
disabled

Categories

Resources