Android OTA Update Mechanism

Yiğit PIRILDAK
7 min readMar 7, 2020

--

OTA (Over The Air) updates, while not specific to Android, are a popular way of updating remote devices over the internet. Update packages are directly downloaded to your device, but how are these updates actually applied in the Android world? Before we dive in, let’s talk a little bit about different types of OTA updates.

At the moment, Android supports two different update mechanisms:

  • Non-A/B OTA Updates
  • A/B OTA Updates

Non-A/B OTA Updates

Non-A/B systems store only one copy of each partition. For devices with limited flash size, this older update mechanism may still be used. Applying this type of update is quite straightforward. Update package is simply downloaded to /cache or /data partitions and using RecoverySystem API, update is initiated. This is a system API and requires the caller to have android.permission.RECOVERY permission which is not granted to regular applications.

RecoverySystem.installPackage is called for applying the update, if update package is actually under /cache partition, it simply sets up the BCB (Bootloader Control Block) by setting some boot parameters such as the update package file location, locale info (in order to display update text in the correct language) and whether it’s a secure update or not. Next step is to reboot into recovery.

If the update package is under /data, package needs to be Uncrypt-ed. Uncrypt is a binary that simply takes a file and creates a block map of it. This block map can then be used to read the contents of the file without mounting the file system. This is done so recovery can access the update contents without mounting the /data partition, since recovery system is not supposed to change the contents of it (there are SElinux rules to ensure that recovery cannot touch data partition during OTA update).

Once the device boots into recovery, /system and /boot partitions are updated. How about updating the recovery partition? you may ask. It is actually not updated at this point. Instead, when the device boots up using the new /boot and /system partitions, Android looks for a file named recovery-from-boot.p in the new system partition and updates the recovery partition using that patch. There is a way to update the recovery partition first and using it to perform the rest of the update. When creating the update package using ota_from_target_files script, use -2 option in order to create a two-stage update package (Results in a larger update package, since it puts both boot and recovery images directly into the update package, which can be a problem for incremental updates).

There are two types of Non-A/B updates:

  • File-based Updates — Updates each file on a filesystem level. The problem with that approach is that depending on when the update is actually applied, system partition will have files with inconsistent last modified date information. Because of this, file-based OTA updates cannot be used when dm-verity is enabled, since dm-verity attempts calculate SHA256 of each block and compares them to the expected values. Due to variant last modified dates, this check will fail and system will not be able to boot after the update.
  • Block-based Updates — For a more consistent system partition after the update, block-based OTA updates are used. Block-based OTA updates ensure that after the update, everyone has the exact same system image.
Both updates are similar in size.

A/B System Updates (Seamless Updates)

The main shortcoming of the Non-A/B update is that it has a potential to brick the system. Since updates are directly applied to each partition, you are left with an un-bootable system if something goes wrong (Especially for Android TVs that don’t have their own batteries unlike smartphones, you are basically one power outage away from losing your device.

So how do you ensure that you can still have a working system even after system update goes horribly wrong? By storing two of each necessary partition of course!

Starting from Android 7.1, A/B updates are introduced. A/B systems keep redundant copies of /system, /boot, /vendor and other optional/vendor-specific partitions (such as /bootloader or a some other custom partition). /recovery partition is no longer used since it was very similar to /boot partition in the first place, /boot partition now contains recovery ramdisk. Keeping two of each partition effectively doubles the size of storage footprint but it’s worth it if you have the extra space.

A/B systems contain A/B partitions called slots such as system_a/system_b slots. Your current slot is the active and bootable slot, and the update is actually applied to the inactive slot. If anything goes wrong during update, you still have your current system intact.

Updates are applied while your device is still running, since they are applied to the inactive slot. Once they are applied, a reboot is initiated and your inactive slot is marked as the active and bootable slot. If it boots successfully, it’s marked as successful so you keep using it from that point on. If it is not able to boot after some attempts, bootloader marks it as unbootable and marks your old, untouched slot as active/bootable slot again.

Streaming A/B Updates

A/B systems also support streaming updates which can allow your android system to apply the updates as they are downloaded. Since full package doesn’t need to be downloaded, you don’t need the extra space that is allocated for a /cache partition, hence no cache partition is needed at all.

Incremental and Full OTA Updates

There’s not much to talk about full android update, as its name suggests, it simply rewrites entire partitions with the new ones. Incremental updates are more interesting, so let’s talk about them:

Incremental OTA Updates

Incremental update packages are generated by using two target_files (previous version and new version) in order to generate a patch between them. These patches are then applied during the update mechanism. Since incremental updates are specifically generated for certain versions, they can only be used to update from one specific version to another. Because of that, even a slight change in the live partitions on the device would result in incremental updates to fail since partition hash is calculated and compared with the expected value before attempting to update (If you somehow root your device and edit your system partition by remounting it, you can say goodbye to incremental updates).

Generating OTA Packages

OTA packages are all generated from target_files zip files. These packages contain all partitions in them. ota_from_target_files.py is used to generate an OTA package from a target file. --block option is specified in order to generate a block-based OTA update, default is file-based. You can generate incremental updates by specifying -i option and providing the script two target_files zips.

Target files and Full OTA package is generated automatically when you build AOSP with dist option.

Here’s some examples of generating OTA packages from the root of your Android directory:

# Export ota_from_target_files.py path for easier usage.
export GENERATE_OTA=$ANDROID_BUILD_TOP/build/tools/releasetools/ota_from_target_files.py

# Generates regular file-based OTA.
$GENERATE_OTA <TARGET_FILES><OTA_ZIP>

# Generates full block-based OTA.
$GENERATE_OTA--block <TARGET_FILES> <OTA_ZIP>

# Generates incremental OTA between two versions.
$GENERATE_OTA -i <PREV_TARGET_FILES> <NEXT_TARGET_FILES><OTA_ZIP>

OTA Package Contents

OTA packages contain each partition (or patches of them for incremental updates). It also contains a file called otacerts which is used for package verification (compared to /system/etc/security/otacerts.zip). Tip: Even though certificate check is done for you by the recovery system after rebooting, you may still use RecoverySystem.verifyPackage to perform a certificate check before triggering installPackage. If the check fails, you won’t need to reboot into recovery, which saves a lot of time.

Other than the update files themselves, there are two files called updater-script and updater-binary. Updater binary simply executes commands defined in updater-script in order to perform the update.

Updater-script may vary depending on how the OTA package is generated, but generally it contains the following:

  • Build date comparison (For full OTA updates, so an older update cannot be applied over a newer system).
  • Extra property checks such as ro.product.device, ro.build.fingerprint, etc.
  • Cryptographic hash comparisons (For Incremental Updates).
  • Commands for extracting files, applying the update chunks.
  • Prints for debugging (usually logged to serial debug lines).

References:

--

--

Yiğit PIRILDAK

A curious Software Engineer who is interested in Embedded Systems and ML. Wastes time by playing video games, watching TV Shows and reading fantasy novels.