diff options
Diffstat (limited to 'updater_sample/README.md')
-rw-r--r-- | updater_sample/README.md | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/updater_sample/README.md b/updater_sample/README.md new file mode 100644 index 000000000..f9c3fb8ec --- /dev/null +++ b/updater_sample/README.md @@ -0,0 +1,267 @@ +# SystemUpdaterSample + +This app demonstrates how to use Android system updates APIs to install +[OTA updates](https://source.android.com/devices/tech/ota/). 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. + + +## Workflow + +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. + + +## Update Config file + +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 `res/raw/sample.json`. + +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 +`/data/user/0/com.example.android.systemupdatersample/files/configs/`. + +SystemUpdaterSample app downloads OTA package from `url`. In this sample app +`url` is expected to point to file system, e.g. `file:///data/my-sample-ota-builds-dir/ota-002.zip`. + +If `ab_install_type` is `NON_STREAMING` then app checks if `url` starts +with `file://` and passes `url` to the `update_engine`. + +If `ab_install_type` is `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. + +if `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`. +Running `./tools/gen_update_config.py --help` shows usage of the script. + + +## Sample App State vs UpdateEngine Status + +UpdateEngine provides status for different stages of update application +process. But it lacks of proper status codes when update fails. + +This creates two problems: + +1. 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. + +2. This sample app demostrates suspend/resume using update_engins's + `cancel` and `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. + + +## Sample App UI + +### Text fields + +- `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. + +### Buttons + +- `Reload` - reloads update configs from device storage. +- `View config` - shows selected update config. +- `Apply` - applies selected update config. +- `Stop` - cancel running update, calls `UpdateEngine#cancel`. +- `Reset` - reset update, calls `UpdateEngine#resetStatus`, can be called + only when update is not running. +- `Suspend` - suspend running update, uses `UpdateEngine#cancel`. +- `Resume` - resumes suspended update, uses `UpdateEngine#applyPayload`. +- `Switch Slot` - if `ab_config.force_switch_slot` config set true, + this button will be enabled after payload is applied, + to switch A/B slot on next reboot. + + +## Sending HTTP headers from UpdateEngine + +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 `Authorization` and `User-Agent`. + +`android.os.UpdateEngine#applyPayload` contains information on +which HTTP headers are supported. + + +## Used update_engine APIs + +### UpdateEngine#bind + +Binds given callbacks to update_engine. When update_engine successfully +initialized, it's guaranteed to invoke callback onStatusUpdate. + +### UpdateEngine#applyPayload + +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 `system/update_engine/common/constants.cc`. + +### UpdateEngine#cancel + +Cancel the ongoing update. The update could be running or suspended, but it +can't be canceled after it was done. + +### UpdateEngine#resetStatus + +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. + +### Callback: onStatusUpdate + +Called whenever the value of `status` or `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 `progress` is `0.005`. + +`onStatusUpdate` is always called when app binds to update_engine, +except when update_engine fails to initialize. + +### Callback: onPayloadApplicationComplete + +Called whenever an update attempt is completed or failed. + + +## Running on a device + +The commands are expected to be run from `$ANDROID_BUILD_TOP` and for demo +purpose only. + +### Without the privileged system permissions + +1. Compile the app `mmma -j bootable/recovery/updater_sample`. +2. Install the app to the device using `adb install <APK_PATH>`. +3. Change permissions on `/data/ota_package/` to `0777` on the device. +4. Set SELinux mode to permissive. See instructions below. +5. Add update config files; look above at [Update Config file](#Update-Config-file). +6. Push OTA packages to the device. +7. Run the sample app. + +### With the privileged system permissions + +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: + +1. [Prepare to build](https://source.android.com/setup/build/building) +2. Add the module (SystemUpdaterSample) to the `PRODUCT_PACKAGES` list for the + lunch target. + e.g. add a line containing `PRODUCT_PACKAGES += SystemUpdaterSample` + to `device/google/marlin/device-common.mk`. +3. [Whitelist the sample app](https://source.android.com/devices/tech/config/perms-whitelist) + * Add + ``` + <privapp-permissions package="com.example.android.systemupdatersample"> + <permission name="android.permission.ACCESS_CACHE_FILESYSTEM"/> + </privapp-permissions> + ``` + to `frameworks/base/data/etc/privapp-permissions-platform.xml` +5. Build sample app `mmma -j bootable/recovery/updater_sample`. +6. Build Android `make -j` +7. [Flash the device](https://source.android.com/setup/build/running) +8. Add update config files; look above at `## Update Config file`; + `adb root` might be required. +9. Push OTA packages to the device if there is no server to stream packages from; + changing of SELinux labels of OTA packages directory might be required + `chcon -R u:object_r:ota_package_file:s0 /data/my-sample-ota-builds-dir` +10. Run the sample app. + + +## Development + +- [x] Create a UI with list of configs, current version, + control buttons, progress bar and log viewer +- [x] Add `PayloadSpec` and `PayloadSpecs` for working with + update zip file +- [x] Add `UpdateConfig` for working with json config files +- [x] Add applying non-streaming update +- [x] Prepare streaming update (partially downloading package) +- [x] Add applying streaming update +- [x] Add stop/reset the update +- [x] Add demo for passing HTTP headers to `UpdateEngine#applyPayload` +- [x] [Package compatibility check](https://source.android.com/devices/architecture/vintf/match-rules) +- [x] Deferred switch slot demo +- [x] Add UpdateManager; extract update logic from MainActivity +- [x] Add Sample app update state (separate from update_engine status) +- [x] Add smart update completion detection using onStatusUpdate +- [x] Add pause/resume demo +- [x] Verify system partition checksum for package + + +## Running tests + +1. Build `mmma bootable/recovery/updater_sample/` +2. Install app + `adb install $OUT/system/priv-app/SystemUpdaterSample/SystemUpdaterSample.apk` +3. Install tests + `adb install $OUT/testcases/SystemUpdaterSampleTests/SystemUpdaterSampleTests.apk` +4. Run tests + `adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner` +5. Run a test file + ``` + adb shell am instrument \ + -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner \ + -c com.example.android.systemupdatersample.util.PayloadSpecsTest + ``` + + +## Accessing `android.os.UpdateEngine` API + +`android.os.UpdateEngine` APIs are marked as `@SystemApi`, meaning only system +apps can access them. + + +## Getting read/write access to `/data/ota_package/` + +Access to cache filesystem is granted only to system apps. + + +## Setting SELinux mode to permissive (0) + +```txt +local$ adb root +local$ adb shell +android# setenforce 0 +android# getenforce +``` + + +## License + +SystemUpdaterSample app is released under +[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). |