Deploying an app update can be nerve-wracking for developers. No matter how much they test, there’s always a sliver of a chance that something will go wrong. And unfortunately, once that update has been installed, it becomes challenging to do damage control. If they used staged rollouts, a feature of some app stores like Google Play, then hopefully the update hasn’t reached too many users before it’s halted. If not, hopefully the bug is in a feature that was set up to be remotely disableable. If not, another update will need to be pushed, though it could be days before it clears the app store’s review process. Fortunately, users can take matters into their own hands and downgrade the app after receiving the update, but there are some limitations with the approaches typically recommended online.

See, most tutorials you’ll find online on how to roll back an Android app update tell you to uninstall the app and then install an older version usually sourced from a third-party app download site. It’s not a bad solution by any means, but it’s not a convenient one. Uninstalling the app will almost always mean its app data is cleared from your device, unless the user checks a box that only appears when uninstalling the app through Settings and if the app opts in by adding an obscure Manifest flag.

In most cases, that’s a good thing. You shouldn’t attempt to downgrade an app while retaining its data, because it could pose problems. The older version of the app might crash or encounter problems when trying to read data modified by the newer version of the app. Most apps aren’t designed to gracefully handle rollbacks, especially since Android doesn’t expose such a capability to users, so it’s not something that developers really need to test. However, Android does support rolling back an app, though this feature is hidden behind a developer tool.

In this week’s edition of Android Dessert Bites, I’ll be sharing two methods to downgrade an Android app. One of these methods is something veteran power users may be aware of, though they may not be aware of how it’s changed in recent years. The other method is something that was introduced comparatively recently but has very little awareness within the Android community.

Thanks for signing up for The Android Edge newsletter. Every week, my Android Dessert Bites column will share the latest news about the Android platform that matters to system engineers, app developers, and power users.

How to downgrade an Android app (and keep the existing data)

This isn’t something I’d recommend, but if you want to just downgrade an app to an older version without clearing its data, then you have to install the older version of the app on top of the newer version. Normally, Android doesn’t allow that, as the system will reject the installation of any apps if an older version of the app is already installed. Depending on the installer that’s used, the installation will either silently fail or an error will be shown.

Fortunately, the system package manager has a hidden flag that, when passed to the package installer, will tell it to allow an older version of an app to be installed on top of a newer version. This flag was added to Android nearly a decade ago, and it hasn’t gone unnoticed by power users. As far back as 2014 there were apps that took advantage of this flag to downgrade applications, albeit those apps used root access to send the requisite shell command. Root access isn’t needed to send shell commands on-device these days as developers have figured out ways to elevate an app’s privileges to shell-level.

Anyway, the method is basically this:

  1. Download the APK file for the version that you want to downgrade to
  2. Move the APK file to your device
  3. Enter the shell of your device (via either ADB from a PC or on-device)
  4. Send the “pm install” command with the “-d” flag and the path to the APK file

For example:

Downgrading an app on Android using the pm install -d command

This process can be made even easier if you’re doing everything from a PC, since you can skip steps 2-3 by just doing ‘adb install -d app.apk’, since ADB will push the APK file to a temporary location on the device automatically and then pipe the install command to PackageManager.

Downgrade an Android app using adb install -d

Alternatively, if you need to install multiple split APKs, then you need to use a different command:

Downgrade multiple split APKs

I used to not bother with this method, because I thought it didn’t work with all applications. I wasn’t the only one who thought so, as evidenced by the numerous complaints in the comments section of this XDA-Developers article. According to Google’s own documentation, this method shouldn’t work with all applications. The help text for the ‘pm’ command states that the ‘-d’ flag only allows version code downgrades for “debuggable packages only”, but it worked for me even with packages that weren’t marked as debuggable. So what gives?

To find out, I dug into AOSP. I started with where the ‘-d’ flag is read by PackageManager. If the ‘-d’ flag is part of the command, the INSTALL_REQUEST_DOWNGRADE flag is added to the package installer session flags. The documentation for this flag notes that it doesn’t guarantee that a downgrade will be permitted, as that decision depends on whether the app is debuggable, the build is debuggable, or INSTALL_ALLOW_DOWNGRADE is set. While the first two checks will fail for most apps on most builds, the INSTALL_ALLOW_DOWNGRADE flag is set by the package installer service if the installation session was created by one of the system_server user, the root user, or the shell user.

Android used to not apply the INSTALL_ALLOW_DOWNGRADE flag when the installation session was created by the shell user. This change was introduced in early 2019 so it became available in Android 10 onward. This is why the ‘pm install -d’ command was inconsistent for many users in the past and why it became more consistent with Android 10 (though I don’t think anyone noticed this change). However, there’s an even better method to downgrade apps in Android 10+, one that takes a snapshot of both the app and its data files so that downgrading actually restores the app to its previous state.

How to roll back an Android app update (and restore the old data)

When Google was developing Project Mainline for Android 10, they needed a way to ensure that updates to Android’s modular system components could be reversed should anything go wrong. Unlike with app updates, Mainline module updates have the potential to break critical system functionality (such as network connectivity) or worse. That’s why Google developed the RollbackManager service, a system service that enables rolling back a Mainline module update to the previous version installed on the device, and also reverting any data to the state it was previously in before the update.

Android’s rollback feature is designed to be triggered automatically by the system when there are 5 “Application Not Responding” (ANR) errors or process crashes within 1 minute after an update, or if any native service crashes repeatedly after an update. It also automatically triggers a rollback of the network stack if no network connectivity is detected after an update. These rollback triggers are intended to safeguard against botched Mainline module updates. Since Mainline modules are distributed in both APEX and APK format, the rollback feature needed to handle rolling back both formats. And since this feature needed extensive testing, Google added a manual way to trigger a rollback: through (you guessed it) a shell command.

There’s one caveat with manually triggering a rollback, though: You first need to enable a rollback for an app when updating it. There is a flag that can be added to the package installer session to enable rollbacks for a given update, but the API that sets it programmatically is reserved for system apps. The Google Play Store obviously doesn’t support this feature since it doesn’t let you roll back app updates, which means you’d need to manually update your apps using the command line if you want to enable a rollback. 

Manually enabling and then triggering a rollback is quite easy, at least. In order to enable a rollback, all you need to do is add the –enable-rollback option to the ‘pm install’ command when updating an app. Then to trigger a rollback, send the ‘pm rollback-app {package}’ command.

For example:

adb push app.apk /data/local/tmp/
adb shell
pm install --enable-rollback /data/local/tmp/app.apk
pm rollback-app {package}
Downgrading an app on Android using the rollback feature
Step by step:
1) The first command I send dumps information about the ‘com.laurencedawson.reddit_sync’ package (Sync for Reddit) and filters for lines that mention “version”. To start with, I have version 19.0.21 of Sync for Reddit installed.
2) I then send the ‘pm install’ command with the ‘–enable-rollback’ option to upgrade to version 19.0.22 of Sync for Reddit and enable a rollback to the previous version.
3) I again dump the package information to confirm that the new version has been installed.
4) I trigger a rollback using the ‘pm rollback-app’ command followed by the package name for Sync for Reddit: com.laurencedawson.reddit_sync.

5) I verify that the old version of the app has been installed.

When an app rollback is requested, the APK file(s) needed for reinstallation of the previous version are copied to /data/rollback/{sessionID}, while the app’s data files in its private app-specific internal storage directory are backed up to /data/misc_[ce|de]/{user}/rollback/{sessionID]. The app’s data in its private app-specific external storage directory (ie. in /data/media/{user}/Android/{package}) isn’t copied to save space and because it’s assumed that those files aren’t critical to the app’s functionality. Also, keys in the KeyStore aren’t backed up, as pointed out by developer Sergio Castell.

(CE stands for credential-encrypted storage while DE stands for device-encrypted storage, in case you’re wondering. The former is where files that are only decrypted after the device has finished booting are stored, while the latter is where files that are decrypted and made available during Direct Boot mode are stored.)

When a rollback is triggered, the previous snapshot is restored by installing the older version of the app and copying the backed up data files back to their original location. Any changes to the app’s data that was made after the rollback was enabled are overwritten.

By default, rollbacks are made available for 14 days after an update, but this can be configured by changing the value of the rollback_lifetime_in_millis flag. For example, sending the following shell command:

adb shell cmd device_config put rollback_boot rollback_lifetime_in_millis 2592000000

…will make the system keep rollbacks for 30 days. Changing this value won’t have any effect until the system_server process is restarted, though, so a reboot will be needed in most cases.

Through the rollbackDataPolicy attribute, apps can tell the system how to handle its data when a rollback is performed. By default, the system will restore the user data during a rollback. Given how obscure this feature is, very few apps have likely specified a policy, so most apps should be capable of being fully rolled back using this method. Sync for Reddit, the app used for demonstration purposes throughout this article, doesn’t specify a rollback data policy, for example.

In order to interface with all the rollback APIs, an app would need to hold the MANAGE_ROLLBACKS and TEST_MANAGE_ROLLBACKS permissions. The former has a protection level of ‘signature|privileged’ while the latter just ‘signature’, so only system apps can enable and trigger rollbacks. The shell app holds the TEST_MANAGE_ROLLBACKS permission, which is why it’s able to enable and trigger rollbacks for user-installed APKs. Google published a sample system app in AOSP to display and trigger rollbacks, but this app would require system integration in order to use. 

Google could expose app rollback functionality to users through Google Play or the OS, but they choose not to because the feature was designed for testing only and was not meant to be used in production. It’s a shame, but there are likely many good reasons why. Fortunately, since this feature is part of AOSP, any forks of it could expose the feature. If you’d like to build an OS based on AOSP that supports rolling back app updates, come talk to us.

Thanks for reading this week’s edition of Android Dessert Bites. If you want to learn more about Android’s rollback feature, you can start with this readme put together by Google. If you want to learn more about this column as well as read previous editions, you can find them here.