Android 12 Wallpaper Changes Recreate Your Activities

Following up on my previous post,and thanks to a crucial tip from cketti,it now appears that we know more about what is going on with wallpaper changeson Android 12: your activities will get recreated, akin to a normal configuration change,but without an opt-out mechanism.

The Original Change

cketti pointed me to this AOSP commit.It provides the engine for what I described above: it forces activities to berecreated:

Activities will be scheduled for restart via the regular life-cycle.This is similar to a configuration change but since ApplicationInfochanges are too low-level we don���t permit apps to opt out.


The catch is those last seven words: ���we don���t permit apps to opt out���. This isnot an actual configuration change that you can opt out of via android:configChanges.

The original rationale for this was for ���runtime resource overlays andsplit APKs���. I am not 100% certain how split APKs come into play here, butruntime resource overlays (RROs)represent a mechanism for system-wide theme changes. So itis not surprising that Google hooked into that mechanism for Android 12 wallpaper changes.

Confirming the Wallpaper Effect

You can determine after the fact that this has occurred by comparing Configurationobjects from before and after the activity recreation. We do not get an onConfigurationChanged()callback in the Activity, because we are not opting out of this recreation process. However, theConfiguration object used by our previous activity instance will differ in one keyway from the Configuration object used by the replacement activity instance.

For example, here is a trivial ViewModel that holds a Configuration?object:

class MainViewModel : ViewModel() { var originalConfiguration: Configuration? = null}

In this activity, I save off the original Configuration object in the viewmodelif originalConfiguration is null. Otherwise, I use diff() on Configurationto examine the difference between the original Configuration and the now-current one:

class MainActivity : AppCompatActivity() { private val vm: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // other good stuff goes here if (vm.originalConfiguration == null) { vm.originalConfiguration = resources.configuration } else { val diff = resources.configuration.diff(vm.originalConfiguration) Log.d("WallpaperCCTest", "matches CONFIG_ASSETS_PATHS? ${(diff.toLong() and 0x80000000) != 0L}") } }}

diff() returns a bitmask of the elements of the configuration that differ betweentwo Configuration objects. The magic 0x80000000 comes fromthe Android source code��� it is ActivityInfo.CONFIG_ASSETS_PATHS, a constant that is marked with @hide:

/** * Bit in {@link #configChanges} that indicates that the activity * can itself handle asset path changes. Set from the {@link android.R.attr#configChanges} * attribute. This is not a core resource configuration, but a higher-level value, so its * constant starts at the high bits. * @hide We do not want apps handling this yet, but we do need some kind of bit for diffs. */ public static final int CONFIG_ASSETS_PATHS = 0x80000000;

On Android 12, after a wallpaper change, usually the else block is run and the bitmask testcomes out true. I say ���usually��� because in my testing, it seems like there is anoccasional hiccup where the wallpaper changes and the activity is left alone and not recreated ��� I havebeen getting this perhaps one time in ten, and I have not identified the pattern.

My code shown above compares the current Configuration with the original one ��� forproduction use, you probably should track the last-seen Configuration and compareit with the current one.

UPDATE: Based on this comment by Nguy���n Ho��i Nam,you can detect this via onConfigurationChanged() in a custom Applicationsubclass. However, that will get called for every configuration change; you would still needto use diff() or similar techniques to determine if the configuration change came fromthis sort of scenario.

Mitigation Approaches

You could use the above diff() approach to detect that your activity was recreateddue to something like a wallpaper change. There are other things thatwill trigger the same effect, as the RRO system was contributedby Sonyback in 2017. Android 12 wallpaper changes may increase the frequency of this occurrence somewhat.

However, there is no obvious way to prevent this destroy-and-recreate cyclefrom occurring. If anything, the various code comments suggest that Google + Sonyspecifically precluded the possibility of apps blocking it. That may be whyGoogle elected to go this route rather than introduce a new configuration change type.

Even if your app tries to opt out of all configuration changes ��� something the Jetpack Composeteam at Google has been espousing ��� your activity can still be destroyed and recreatedwithout process termination. Ideally your app handles that case, even thoughnot all apps do, apparently.

 •  0 comments  •  flag
Share on Twitter
Published on October 31, 2021 06:27
No comments have been added yet.