Android 13’s virtual A/B mandate could bring Seamless Updates to more devices
“Seamless Updates” has been Android’s standard FOTA update mechanism for years now. It’s been around since Android 7.0 Nougat launched all the way back in 2016 and has been used in every Pixel device. Six years after its introduction, though, there are still some notable holdouts that have refused to adopt Seamless Updates in their devices. That could finally change with Android 13, which mandates new devices implement virtual A/B partitions.
Before I dive any deeper into what exactly is changing in Android 13, I want to briefly mention why you should care about this change. I’m sure you’ll agree that keeping your device up-to-date with the latest firmware and software updates from the OEM is crucial in protecting it from the latest security vulnerabilities. If you’re reading this post, then I probably don’t need to tell you that.
However, you may know a few people in your life who don’t like updating their device or wait as long as possible to do so. For them, applying a new software update immediately just isn’t a priority for one reason or another; they might not care about updates unless they bring something new and exciting to the table, they might be afraid of the update breaking things, or they might put it off because the update process temporarily puts their device out of commission. Enticing these users to update is critical to improving ecosystem security, which is why OEMs dangle things like new emojis in changelogs, run beta testing programs to fix bugs before they ship, and reduce as much friction as they can during the update process.
Making OTA updates frictionless is why Google introduced the Seamless Updates mechanism back in 2016. As its name suggests, Seamless Updates makes OTA updates seem seamless from a user’s perspective, nearly eliminating the downtime incurred by the traditional OTA update mechanism. Instead of shutting down Android and booting into a dedicated recovery environment, OTA updates are downloaded and applied in the background while Android is running. After a quick reboot, the OS update is complete. The only downtime is the brief period when Android is offline as the device reboots.
That’s not the only benefit of Android’s Seamless Updates mechanism. Seamless Updates can be paused/resumed by the user at their leisure or by the platform based on whether the device is in use. More critically, it has a built-in failsafe for botched OTA updates, allowing for OS updates to effectively be rolled back if an update causes the OS to stop booting. It also supports streaming updates, which means that OS files can be patched as the update package is being downloaded. Streaming update support significantly reduces how much storage space is needed to apply an update, as the OTA package doesn’t need to be downloaded in full before the updater can start applying changes.
With all these benefits, you’d be excused for thinking there’s no reason for an OEM not to use Seamless Updates in their devices. The truth is, there are some downsides to this feature, which is why some of the largest Android OEMs out there haven’t adopted it yet. Fortunately, Google has been working diligently over the last few years to address these downsides, which is likely why they’re comfortable making it the the only supported OTA mechanism going forward. Starting in Android 13, the traditional OTA update mechanism is being deprecated, paving the way for every Android update to eventually be seamless.
To explain why some OEMs have delayed supporting Seamless Updates and what changes Google made to address their concerns, I’ll first need to explain how these OTA update mechanisms work under-the-hood.
Your typical Android phone or tablet ships with an internal storage device that’s divided into multiple partitions. The exact list of partitions varies by device, brand, and OS version, but a few partitions are common across all devices.
For example, every Android device has a “system” partition which holds the OS framework, system libraries, system apps, and system media files from AOSP. Every device that supports Project Treble has a “vendor” partition, which stores the platform and device-specific hardware abstraction layers (HALs) needed for the OS to communicate with the device’s underlying hardware. The “boot” partition, meanwhile, holds the device’s Linux kernel, generic ramdisk, and recovery environment. The partition that’s most important to the user is the “userdata” partition as it holds the user’s apps and data files as well as shared media files (among other things).
There are many, many more partitions supported by Android and/or implemented by OEMs, and attempting to explain each and every one would take forever. Plus, Google seems to change Android’s supported partitions every release, making it hard to keep track of it all. In Android 13, for example, Google wants device makers to remove the generic ramdisk from the “boot” partition and place it in a separate “init_boot” partition. Also in Android 13, Google is adding a new “system_dlkm” partition to hold Google-signed kernel modules. I also have to put a caveat on the bit about the “boot” partition holding the recovery environment, as non-A/B devices typically have a dedicated “recovery” partition and there’s no reason A/B devices can’t have one as well.
Basically, Android partitions are confusing, even to experts on the platform I regularly speak to. But the good news is that you don’t really need to know what each partition is for. Instead, all you really need to know is that they exist, some of them are really important (some more than others), some of them you’ll never ever touch, one of them holds all your data, and only a few of them are updated during an incremental OTA update.
(If you’re actually interested in learning about Android partitions, my friend and co-author of the previous edition of Android Dessert Bites, Jonathan Levin, painstakingly documented as many partitions as he could find on Android devices in his book, Android Internals: Power User’s View.)
Android OTA update mechanisms: Non-A/B versus A/B versus Virtual A/B
While it’s technically possible to perform a “live” upgrade, that is, a software update that replaces the system software that the OS is using while it’s booted and without needing a reboot, this typically isn’t done for Android devices. Linux supports Kernel Live Patching (KLP) but this is something that mostly only server admins that need to maximize uptime will consider, as it has some limitations and caveats. Most OTA system updates — no matter the device — patch files that aren’t actively being used by the OS.
Devices that use Android’s traditional OTA update mechanism, ie. non-A/B, have to boot into their recovery environment in order to patch OS files. This is because devices with a non-A/B partition scheme only have a single copy of each partition, so Android must be shut down to prevent it from accessing files that need to be patched. Because of this, non-A/B devices typically have a dedicated “recovery” partition from which they process OTA packages, as well as a “cache” partition to temporarily store any downloaded OTA packages.
On the other hand, devices that use Android’s more modern OTA update mechanism, ie. A/B, can keep Android up and running while the OTA update is applied in the background. This is because devices with an A/B partition scheme have an extra copy of key partitions (suffixed with _a or _b), enabling Android’s update_engine (also used by Chrome OS) to apply updates while the system is running. Each set of partitions is known as a “slot”, and there are typically two slots — A and B — that the bootloader manages.
When an OTA update is ready, update_engine runs in the background and patches system files in the “inactive” set of partitions, ie. the partitions that aren’t actively being used by the system. After a reboot, the “inactive” set of partitions is marked as “active” and the “active” set, which contains a working copy of the previous version of the OS, becomes “inactive.” This process repeats with each subsequent OTA update while appearing seamless to the user, hence the marketing term for A/B OTAs: Seamless Updates.
The other benefits to Seamless Updates that I mentioned before — ie. support for pausing/resuming updates, support for rolling back updates, and support for streaming updates directly to the “inactive” partitions — are all made possible by the redundancy built into the design of the A/B partition scheme.
That redundancy, however, is what limited the adoption of the A/B partition scheme among OEMs. Making a redundant copy of a partition requires storage space to be reserved for that partition, which means shrinking the “userdata” partition (ie. reducing the amount of storage space available to the user.) Duplicating tiny partitions like “boot” doesn’t lose the user any storage, because it’s made up by the fact that A/B devices don’t need a dedicated “recovery” or “cache” partition, but duplicating larger partitions like “system”, “product”, “system_ext”, and “vendor” can make a huge difference.
When Google first introduced Seamless Updates on the original Pixel, they managed to nearly negate its storage penalties by moving large .odex (optimized dex) files from the “system” partition to “userdata”. This trick allowed Google to halve the size of the “system” partition in the Pixel compared to the Nexus 6P, as .odex files took up approximately 50% of the space on the Nexus 6P’s “system” partition. As Google explains, this was only possible because shipping .odex files in the “system” partition isn’t strictly necessary, as they’re only used to optimize app performance and can be generated after the app receives its first update anyway.
Because more space-optimizing tricks like this haven’t been implemented, OEMs continue to ship larger and larger apps and more files, and Android itself adds new system files, some OEMs shied away from using A/B partitions and thus Seamless Updates. To solve this, Google introduced the virtual A/B partition scheme in Android 11, which builds upon the dynamic partitions feature introduced in Android 10.
Unlike regular partitions which take up a fixed amount of space set at the factory, dynamic partitions can be dynamically created, resized, or destroyed during OTA updates. This is possible through a new “super” partition, the size of which is set at the factory like with other regular partitions, but the dynamic partitions within it can be shrunk or expanded until the “super” partition is full.
The dynamic partitions within “super” currently consist of “system”, “system_ext”, “product”, “vendor”, “vendor_dlkm”, and “system_dlkm” (as previously mentioned, a new partition on Android 13 launch devices). Some partitions like “boot” can’t be made dynamic since they’re read by the bootloader and the dynamic partitions feature is implemented in userspace. Even so, making “system” et al., the largest partitions that hold system files typically modified during OTA updates, dynamic means that OEMs can grow them as needed to accommodate future OTAs (so long as free space is available in “super”, which is why choosing the initial size of “super” is a very important consideration for OEMs).
Dynamic partitions can also be slotted, as demonstrated in the screenshot above, which is what virtual A/B partitioning entails. In other words, the “super” partition can have not only “system_a” but also “system_b”, “system_ext_a” and “system_ext_b”, and so on and so forth.
To summarize, there are four partition schemes commonly seen on Android devices: non-A/B, non-A/B with dynamic partitions, A/B, and virtual A/B. (GMS requirements mandate that devices launching with Android 10 or later support dynamic partitions, so there aren’t many non-A/B devices without dynamic partitions anymore.) Here’s a graphic I made to illustrate the differences between these four partition schemes:
Comparing non-A/B, non-A/B with dynamic partitions, A/B, and virtual A/B partitions. Note that this doesn’t show a comprehensive list of partitions and does not snapshot what partitions were actually around when each partition scheme was actually implemented.
How Virtual A/B became the best OTA update mechanism
Eagle-eyed readers will have picked up on how virtual A/B solves A/B’s storage requirement problem. In the above screenshot showing the contents of the “super” partition on my Pixel 6 Pro, all of the _b partitions are empty. On an A/B device, those partitions — even when “inactive” — would take up as much space as the “active” partitions. Virtual A/B eliminates the need to have full-size duplicates of every partition. Instead, virtual A/B makes clever use of dynamic partitions and several Linux tools.
The way virtual A/B updates work is quite complex, but it basically involves creating “snapshots” of the dynamic partitions that need to be updated. Those snapshots are stored in a temporary location on the “userdata” partition. Any changes that update_engine writes to the snapshots are logged in what are called “copy-on-write” (COW) devices. Once all the patches have been read from the OTA package, Android will map the “inactive” partitions to the updated snapshots and attempt to boot them. If Android successfully boots, then the changes logged in the COW devices are merged into the dynamic partitions that need updating. Then, the snapshots and COW devices are deleted to free up space for the user.
While virtual A/B requires significantly less storage than A/B, it still requires more storage than non-A/B devices, at least when applying OTA updates. Snapshotting each dynamic partition effectively means cloning the entire “super” partition, which means that multiple gigabytes of free space need to be available in the “userdata” partition to hold those snapshots. This is likely why some of the OEMs that held off on adopting A/B also held off on adopting virtual A/B, and why Google decided to not go forward with requiring that devices launching with Android 11 or later use virtual A/B.
To reduce the amount of space taken up by snapshots, Google in Android 12 introduced support for compressed snapshots. Compressing snapshots significantly reduces how much space they take up in the “userdata” partition and effectively makes virtual A/B updates as space efficient as non-A/B.
Google’s making further improvements to virtual A/B in Android 13. For instance, XOR compression further reduces snapshot size by 25-40%, while switching to dm-user to do userspace merges significantly improves compression merge times. Google’s also experimenting with deploying Linux’s io_uring feature, which according to their benchmarks cuts down on snapshot merge times by ~40%.
Though they’re enabled in AOSP, OEMs aren’t required to ship devices with virtual A/B compression, XOR compression, or io_uring enabled. However, OEMs will be required to support virtual A/B with userspace snapshots on new devices launching with Android 13 or later. This is evidenced by new tests added to the Vendor Test Suite (VTS) making virtual A/B mandatory for Android 13 launch devices. These tests were merged prior to Android 13’s launch, and they remain in place even after its launch, in contrast to what happened during Android 11’s development.
Google did say back in 2020 that, “going forward, virtual A/B will be the only supported OTA mechanism in Android”, so it was only a matter of time before it became mandatory. The Android 13 CDD doesn’t say that A/B system updates are required, but if virtual A/B support is now mandatory* for GMS licensing, then I don’t see how future Android 13 launch devices won’t support Seamless Updates. The only exception is those Android 13 launch devices shipping with older vendor software, thanks to the carve outs in the VTS test as a result of the Google Requirements Freeze (GRF) program.
*For some reason, the AOSP documentation on virtual A/B erroneously says that virtual A/B is a GMS requirement for devices launching with Android 11 or later.
Thanks for reading this week’s edition of Android Dessert Bites. If you liked this post and want to learn more about Android, you can go back and read my previous posts in this column here.