This app demonstrates how to use Android system updates APIs to install OTA updates. It contains a sample client for
update_engine to install A/B (seamless) updates.
A/B (seamless) update is available since Android Nougat (API 24), but this sample targets the latest android.
SystemUpdaterSample app shows list of available updates on the UI. User is allowed to select an update and apply it to the device. App shows installation progress, logs can be found in
adb logcat. User can stop or reset an update. Resetting the update requests update engine to cancel any ongoing update, and revert if the update has been applied. Stopping does not revert the applied update.
In this sample updates are defined in JSON update config files. The structure of a config file is defined in
com.example.android.systemupdatersample.UpdateConfig, example file is located at
In real-life update system the config files expected to be served from a server to the app, but in this sample, the config files are stored on the device. The directory can be found in logs or on the UI. In most cases it should be located at
SystemUpdaterSample app downloads OTA package from
url. In this sample app
url is expected to point to file system, e.g.
NON_STREAMING then app checks if
url starts with
file:// and passes
url to the
STREAMING, app downloads only the entries in need, as opposed to the entire package, to initiate a streaming update. The
payload.bin entry, which takes up the majority of the space in an OTA package, will be streamed by
update_engine directly. The ZIP entries in such a package need to be saved uncompressed (
ZIP_STORED), so that their data can be downloaded directly with the offset and length. As
payload.bin itself is already in compressed format, the size penalty is marginal.
ab_config.force_switch_slot set true device will boot to the updated partition on next reboot; otherwise button “Switch Slot” will become active, and user can manually set updated partition as the active slot.
Config files can be generated using
./tools/gen_update_config.py --help shows usage of the script.
UpdateEngine provides status for different stages of update application process. But it lacks of proper status codes when update fails.
This creates two problems:
If sample app is unbound from update_engine (MainActivity is paused, destroyed), app doesn't receive onStatusUpdate and onPayloadApplicationCompleted notifications. If app binds to update_engine after update is completed, only onStatusUpdate is called, but status becomes IDLE in most cases. And there is no way to know if update was successful or not.
This sample app demostrates suspend/resume using update_engins‘s
applyPayload (which picks up from where it left). When
cancel is called, status is set to
IDLE, which doesn’t allow tracking suspended state properly.
To solve these problems sample app implements its own separate update state -
UpdaterState. To solve the first problem, sample app persists
UpdaterState on a device. When app is resumed, it checks if
UpdaterState matches the update_engine‘s status (as onStatusUpdate is guaranteed to be called). If they doesn’t match, sample app calls
applyPayload again with the same parameters, and handles update completion properly using
onPayloadApplicationCompleted callback. The second problem is solved by adding
PAUSED updater state.
Current Build:- shows current active build.
Updater state:- SystemUpdaterSample app state.
Engine status:- last reported update_engine status.
Engine error:- last reported payload application error.
Reload- reloads update configs from device storage.
View config- shows selected update config.
Apply- applies selected update config.
Stop- cancel running update, calls
Reset- reset update, calls
UpdateEngine#resetStatus, can be called only when update is not running.
Suspend- suspend running update, uses
Resume- resumes suspended update, uses
Switch Slot- if
ab_config.force_switch_slotconfig set true, this button will be enabled after payload is applied, to switch A/B slot on next reboot.
Sometimes OTA package server might require some HTTP headers to be present, e.g.
Authorization header to contain valid auth token. While performing streaming update,
UpdateEngine allows passing on certain HTTP headers; as of writing this sample app, these headers are
android.os.UpdateEngine#applyPayload contains information on which HTTP headers are supported.
Binds given callbacks to update_engine. When update_engine successfully initialized, it's guaranteed to invoke callback onStatusUpdate.
Start an update attempt to download an apply the provided
payload_url if no other update is running. The extra
key_value_pair_headers will be included when fetching the payload.
key_value_pair_headers argument also accepts properties other than HTTP Headers. List of allowed properties can be found in
Cancel the ongoing update. The update could be running or suspended, but it can't be canceled after it was done.
Reset the already applied update back to an idle state. This method can only be called when no update attempt is going on, and it will reset the status back to idle, deleting the currently applied update if any.
Called whenever the value of
progress changes. For
progress values changes, this method will be called only if it changes significantly. At this time of writing this doc, delta for
onStatusUpdate is always called when app binds to update_engine, except when update_engine fails to initialize.
Called whenever an update attempt is completed or failed.
The commands are expected to be run from
$ANDROID_BUILD_TOP and for demo purpose only.
mmma -j bootable/recovery/updater_sample.
adb install <APK_PATH>.
0777on the device.
To run sample app as a privileged system app, it needs to be installed in
/system/priv-app/. This directory is expected to be read-only, unless explicitly remounted.
The recommended way to run the app is to build and install it as a privileged system app, so it's granted the required permissions to access
update_engine service as well as OTA package files. Detailed steps are as follows:
PRODUCT_PACKAGESlist for the lunch target. e.g. add a line containing
PRODUCT_PACKAGES += SystemUpdaterSampleto
<privapp-permissions package="com.example.android.systemupdatersample"> <permission name="android.permission.ACCESS_CACHE_FILESYSTEM"/> </privapp-permissions>to
mmma -j bootable/recovery/updater_sample.
## Update Config file;
adb rootmight be required.
chcon -R u:object_r:ota_package_file:s0 /data/my-sample-ota-builds-dir
PayloadSpecsfor working with update zip file
UpdateConfigfor working with json config files
adb install $OUT/system/priv-app/SystemUpdaterSample/SystemUpdaterSample.apk
adb install $OUT/testcases/SystemUpdaterSampleTests/SystemUpdaterSampleTests.apk
adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
adb shell am instrument \ -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \ com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
android.os.UpdateEngine APIs are marked as
@SystemApi, meaning only system apps can access them.
Access to cache filesystem is granted only to system apps.
local$ adb root local$ adb shell android# setenforce 0 android# getenforce
SystemUpdaterSample app is released under Apache License 2.0.