OTA for non-A/B Devices without Dynamic Partitions

Android 10 supports dynamic partitions, a userspace partitioning system that can create, resize, and destroy partitions during an OTA update. This document describes how OTA clients resize dynamic partitions during an update for non-A/B devices.

For non-A/B devices, the OTA update for dynamic partitions is applied using the updater inside the update package.

Updating launch devices

This section applies to non-A/B devices that launch with dynamic partitions support; these devices upgrade from Android 10 to later releases.

Generating update packages

OTA update packages are generated by the ota_from_target_files script, located under build/make/tools/releasetools. By default, the script generates a package that updates system and vendor partitions. If there are additional dynamic partitions, such as product, product_services, or odm, their updates must be generated in device-specific code.

To generate updates, in the extended Python module, implement FullOTA_GetBlockDifferences() and IncrementalOTA_GetBlockDifferences(). These two functions return a list of BlockDifference objects, each describing the update patch that would be applied on a partition. Partitions returned by these two functions should not be modified manually or verified elsewhere, for example in *_InstallBegin() or *_InstallEnd().

Example of an update generation:

# device/yoyodyne/tardis/releasetools.py

import os
from common import BlockDifference, EmptyImage, GetUserImage

# The joined list of user image partitions of source and target builds.
# - Items should be added to the list if new dynamic partitions are added.
# - Items should not be removed from the list even if dynamic partitions are
#   deleted. When generating an incremental OTA package, this script needs to
#   know that an image is present in source build but not in target build.
USERIMAGE_PARTITIONS = [
    "product",
    "odm",
]

def GetUserImages(input_tmp, input_zip):
  return {partition: GetUserImage(partition, input_tmp, input_zip)
          for partition in USERIMAGE_PARTITIONS
          if os.path.exists(os.path.join(input_tmp,
                                         "IMAGES", partition + ".img"))}

def FullOTA_GetBlockDifferences(info):
  images = GetUserImages(info.input_tmp, info.input_zip)
  return [BlockDifference(partition, image)
          for partition, image in images.items()]

def IncrementalOTA_GetBlockDifferences(info):
  source_images = GetUserImages(info.source_tmp, info.source_zip)
  target_images = GetUserImages(info.target_tmp, info.target_zip)

  # Use EmptyImage() as a placeholder for partitions that will be deleted.
  for partition in source_images:
    target_images.setdefault(partition, EmptyImage())

  # Use source_images.get() because new partitions are not in source_images.
  return [BlockDifference(partition, target_image, source_images.get(partition))
          for partition, target_image in target_images.items()]

Update flow

Behind the scenes, the following functions are added to the edify script:

  • unmap_partition(name)
    • Unmap the partition if mapped, otherwise do nothing.
    • Return the string t on success, or an empty string on failure.
  • map_partition(name)
    • Map the partition if not already mapped.
    • Return the absolute path of the mapped block device on success, or an empty string on failure.
  • update_dynamic_partitions(op_list)
    • Apply the given operation list on dynamic partition metadata, unmapping partitions if necessary.
    • Return t on success, or an empty string on failure.

The op_list argument to update_dynamic_partitions points to a file in the update package. Each line in the file specifies an operation. If any operation fails, update_dynamic_partitions immediately returns an empty string. The operations are:

  • resize partition-name size
    • Unmap the partition, then resize it to size.
  • remove partition_name
    • Unmap the partition, then remove it.
  • add partition-name group-name
    • Add a new partition to the specified group.
    • Abort if the group doesn't exist or if the partition already exists.
  • move partition-name group-name
    • Move the partition to the specified group.
    • Abort if the group does not exist or partition does not exist.
  • add_group group-name maximum-size
    • Add a group with the given name and maximum size.
    • Abort if the group already exists.
    • A maximum_size of 0 means there are no size limits on partitions in the group. Additional testing is required to ensure that partitions in the group don't exceed the available space on the device.
  • resize_group group-name maximum-size
    • Resize the group to the given maximum size.
    • Abort if group doesn't exist.
    • A maximum_size of 0 means there are no size limits on partitions in the group. Additional testing is required to ensure that partitions in the group don't exceed the available space on the device.
  • remove_group group-name
    • Remove a group.
    • Abort if there are partitions in the group.
  • remove_all_groups
    • Unmap all partitions from the device mapper.
    • Remove all partitions and groups.

Incremental OTA

Incremental OTA updates use the following logic:

  1. Shrink partitions / delete partitions / move partitions out of group (so that there is enough space to shrink groups)
  2. Shrink groups (so that we have enough space to grow groups)
  3. Grow groups (so that we have enough space to grow / add partitions)
  4. Grow partitions / add partitions / move partitions to new group

In detail, update-script is generated with this logic:

for each shrinking partition:
    block_image_update(map_partition(name), …)

update_dynamic_partitions(op_list)

for each growing / adding partition:
    block_image_update(map_partition(name), …)

The op_list file for update_dynamic_partitions is generated with this logic:

for each deleting partition:
    remove
for each partition that changes groups:
    move to "default"
for each shrinking partition:
    resize
for each shrinking / removing group:
    resize_group / remove_group
for each growing / adding group:
    resize_group / add_group
for each adding partition:
    add
for each growing / adding partition:
    resize
for each partition that changes groups:
    move to target group

Full OTA

Full OTA updates use the following logic:

  1. Delete all existing groups and partitions
  2. Add groups
  3. Add partitions

In detail, update-script is generated with this logic:

update_dynamic_partitions(op_list)

for each adding partition:
    block_image_update(map_partition(name), …)

The op_list file for update_dynamic_partitions is generated with this logic:

remove_all_groups
for each adding group:
    add_group
for each adding partition:
    add
for each adding partition:
    resize