Home | History | Annotate | Download | only in updater_sample
      1 # SystemUpdaterSample
      2 
      3 This app demonstrates how to use Android system updates APIs to install
      4 [OTA updates](https://source.android.com/devices/tech/ota/). It contains a
      5 sample client for `update_engine` to install A/B (seamless) updates.
      6 
      7 A/B (seamless) update is available since Android Nougat (API 24), but this sample
      8 targets the latest android.
      9 
     10 
     11 ## Workflow
     12 
     13 SystemUpdaterSample app shows list of available updates on the UI. User is allowed
     14 to select an update and apply it to the device. App shows installation progress,
     15 logs can be found in `adb logcat`. User can stop or reset an update. Resetting
     16 the update requests update engine to cancel any ongoing update, and revert
     17 if the update has been applied. Stopping does not revert the applied update.
     18 
     19 
     20 ## Update Config file
     21 
     22 In this sample updates are defined in JSON update config files.
     23 The structure of a config file is defined in
     24 `com.example.android.systemupdatersample.UpdateConfig`, example file is located
     25 at `res/raw/sample.json`.
     26 
     27 In real-life update system the config files expected to be served from a server
     28 to the app, but in this sample, the config files are stored on the device.
     29 The directory can be found in logs or on the UI. In most cases it should be located at
     30 `/data/user/0/com.example.android.systemupdatersample/files/configs/`.
     31 
     32 SystemUpdaterSample app downloads OTA package from `url`. In this sample app
     33 `url` is expected to point to file system, e.g. `file:///data/my-sample-ota-builds-dir/ota-002.zip`.
     34 
     35 If `ab_install_type` is `NON_STREAMING` then app checks if `url` starts
     36 with `file://` and passes `url` to the `update_engine`.
     37 
     38 If `ab_install_type` is `STREAMING`, app downloads only the entries in need, as
     39 opposed to the entire package, to initiate a streaming update. The `payload.bin`
     40 entry, which takes up the majority of the space in an OTA package, will be
     41 streamed by `update_engine` directly. The ZIP entries in such a package need to be
     42 saved uncompressed (`ZIP_STORED`), so that their data can be downloaded directly
     43 with the offset and length. As `payload.bin` itself is already in compressed
     44 format, the size penalty is marginal.
     45 
     46 if `ab_config.force_switch_slot` set true device will boot to the
     47 updated partition on next reboot; otherwise button "Switch Slot" will
     48 become active, and user can manually set updated partition as the active slot.
     49 
     50 Config files can be generated using `tools/gen_update_config.py`.
     51 Running `./tools/gen_update_config.py --help` shows usage of the script.
     52 
     53 
     54 ## Sample App State vs UpdateEngine Status
     55 
     56 UpdateEngine provides status for different stages of update application
     57 process. But it lacks of proper status codes when update fails.
     58 
     59 This creates two problems:
     60 
     61 1. If sample app is unbound from update_engine (MainActivity is paused, destroyed),
     62    app doesn't receive onStatusUpdate and onPayloadApplicationCompleted notifications.
     63    If app binds to update_engine after update is completed,
     64    only onStatusUpdate is called, but status becomes IDLE in most cases.
     65    And there is no way to know if update was successful or not.
     66 
     67 2. This sample app demostrates suspend/resume using update_engins's
     68    `cancel` and `applyPayload` (which picks up from where it left).
     69    When `cancel` is called, status is set to `IDLE`, which doesn't allow
     70    tracking suspended state properly.
     71 
     72 To solve these problems sample app implements its own separate update
     73 state - `UpdaterState`. To solve the first problem, sample app persists
     74 `UpdaterState` on a device. When app is resumed, it checks if `UpdaterState`
     75 matches the update_engine's status (as onStatusUpdate is guaranteed to be called).
     76 If they doesn't match, sample app calls `applyPayload` again with the same
     77 parameters, and handles update completion properly using `onPayloadApplicationCompleted`
     78 callback. The second problem is solved by adding `PAUSED` updater state.
     79 
     80 
     81 ## Sample App UI
     82 
     83 ### Text fields
     84 
     85 - `Current Build:` - shows current active build.
     86 - `Updater state:` - SystemUpdaterSample app state.
     87 - `Engine status:` - last reported update_engine status.
     88 - `Engine error:` - last reported payload application error.
     89 
     90 ### Buttons
     91 
     92 - `Reload` - reloads update configs from device storage.
     93 - `View config` - shows selected update config.
     94 - `Apply` - applies selected update config.
     95 - `Stop` - cancel running update, calls `UpdateEngine#cancel`.
     96 - `Reset` - reset update, calls `UpdateEngine#resetStatus`, can be called
     97             only when update is not running.
     98 - `Suspend` - suspend running update, uses `UpdateEngine#cancel`.
     99 - `Resume` - resumes suspended update, uses `UpdateEngine#applyPayload`.
    100 - `Switch Slot` - if `ab_config.force_switch_slot` config set true,
    101             this button will be enabled after payload is applied,
    102             to switch A/B slot on next reboot.
    103 
    104 
    105 ## Sending HTTP headers from UpdateEngine
    106 
    107 Sometimes OTA package server might require some HTTP headers to be present,
    108 e.g. `Authorization` header to contain valid auth token. While performing
    109 streaming update, `UpdateEngine` allows passing on certain HTTP headers;
    110 as of writing this sample app, these headers are `Authorization` and `User-Agent`.
    111 
    112 `android.os.UpdateEngine#applyPayload` contains information on
    113 which HTTP headers are supported.
    114 
    115 
    116 ## Used update_engine APIs
    117 
    118 ### UpdateEngine#bind
    119 
    120 Binds given callbacks to update_engine. When update_engine successfully
    121 initialized, it's guaranteed to invoke callback onStatusUpdate.
    122 
    123 ### UpdateEngine#applyPayload
    124 
    125 Start an update attempt to download an apply the provided `payload_url` if
    126 no other update is running. The extra `key_value_pair_headers` will be
    127 included when fetching the payload.
    128 
    129 `key_value_pair_headers` argument also accepts properties other than HTTP Headers.
    130 List of allowed properties can be found in `system/update_engine/common/constants.cc`.
    131 
    132 ### UpdateEngine#cancel
    133 
    134 Cancel the ongoing update. The update could be running or suspended, but it
    135 can't be canceled after it was done.
    136 
    137 ### UpdateEngine#resetStatus
    138 
    139 Reset the already applied update back to an idle state. This method can
    140 only be called when no update attempt is going on, and it will reset the
    141 status back to idle, deleting the currently applied update if any.
    142 
    143 ### Callback: onStatusUpdate
    144 
    145 Called whenever the value of `status` or `progress` changes. For
    146 `progress` values changes, this method will be called only if it changes significantly.
    147 At this time of writing this doc, delta for `progress` is `0.005`.
    148 
    149 `onStatusUpdate` is always called when app binds to update_engine,
    150 except when update_engine fails to initialize.
    151 
    152 ### Callback: onPayloadApplicationComplete
    153 
    154 Called whenever an update attempt is completed or failed.
    155 
    156 
    157 ## Running on a device
    158 
    159 The commands are expected to be run from `$ANDROID_BUILD_TOP` and for demo
    160 purpose only.
    161 
    162 ### Without the privileged system permissions
    163 
    164 1. Compile the app `mmma -j bootable/recovery/updater_sample`.
    165 2. Install the app to the device using `adb install <APK_PATH>`.
    166 3. Change permissions on `/data/ota_package/` to `0777` on the device.
    167 4. Set SELinux mode to permissive. See instructions below.
    168 5. Add update config files; look above at [Update Config file](#Update-Config-file).
    169 6. Push OTA packages to the device.
    170 7. Run the sample app.
    171 
    172 ### With the privileged system permissions
    173 
    174 To run sample app as a privileged system app, it needs to be installed in `/system/priv-app/`.
    175 This directory is expected to be read-only, unless explicitly remounted.
    176 
    177 The recommended way to run the app is to build and install it as a
    178 privileged system app, so it's granted the required permissions to access
    179 `update_engine` service as well as OTA package files. Detailed steps are as follows:
    180 
    181 1. [Prepare to build](https://source.android.com/setup/build/building)
    182 2. Add the module (SystemUpdaterSample) to the `PRODUCT_PACKAGES` list for the
    183    lunch target.
    184    e.g. add a line containing `PRODUCT_PACKAGES += SystemUpdaterSample`
    185    to `device/google/marlin/device-common.mk`.
    186 3. [Whitelist the sample app](https://source.android.com/devices/tech/config/perms-whitelist)
    187    * Add
    188    ```
    189     <privapp-permissions package="com.example.android.systemupdatersample">
    190         <permission name="android.permission.ACCESS_CACHE_FILESYSTEM"/>
    191     </privapp-permissions>
    192    ```
    193    to `frameworks/base/data/etc/privapp-permissions-platform.xml`
    194 5. Build sample app `make -j SystemUpdaterSample`.
    195 6. Build Android `make -j`
    196 7. [Flash the device](https://source.android.com/setup/build/running)
    197 8. Add update config files; look above at `## Update Config file`;
    198    `adb root` might be required.
    199 9. Push OTA packages to the device if there is no server to stream packages from;
    200    changing of SELinux labels of OTA packages directory might be required
    201    `chcon -R u:object_r:ota_package_file:s0 /data/my-sample-ota-builds-dir`
    202 10. Run the sample app.
    203 
    204 
    205 ## Development
    206 
    207 - [x] Create a UI with list of configs, current version,
    208       control buttons, progress bar and log viewer
    209 - [x] Add `PayloadSpec` and `PayloadSpecs` for working with
    210       update zip file
    211 - [x] Add `UpdateConfig` for working with json config files
    212 - [x] Add applying non-streaming update
    213 - [x] Prepare streaming update (partially downloading package)
    214 - [x] Add applying streaming update
    215 - [x] Add stop/reset the update
    216 - [x] Add demo for passing HTTP headers to `UpdateEngine#applyPayload`
    217 - [x] [Package compatibility check](https://source.android.com/devices/architecture/vintf/match-rules)
    218 - [x] Deferred switch slot demo
    219 - [x] Add UpdateManager; extract update logic from MainActivity
    220 - [x] Add Sample app update state (separate from update_engine status)
    221 - [x] Add smart update completion detection using onStatusUpdate
    222 - [x] Add pause/resume demo
    223 - [x] Verify system partition checksum for package
    224 
    225 
    226 ## Running tests
    227 
    228 The commands are expected to be run from `$ANDROID_BUILD_TOP`.
    229 
    230 1. Build `make -j SystemUpdaterSample` and `make -j SystemUpdaterSampleTests`.
    231 2. Install app
    232    `adb install $OUT/system/priv-app/SystemUpdaterSample/SystemUpdaterSample.apk`
    233 3. Install tests
    234    `adb install $OUT/testcases/SystemUpdaterSampleTests/SystemUpdaterSampleTests.apk`
    235 4. Run tests
    236    `adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner`
    237 5. Run a test file
    238    ```
    239    adb shell am instrument \
    240      -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \
    241      com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
    242    ```
    243 
    244 
    245 ## Accessing `android.os.UpdateEngine` API
    246 
    247 `android.os.UpdateEngine` APIs are marked as `@SystemApi`, meaning only system
    248 apps can access them.
    249 
    250 
    251 ## Getting read/write access to `/data/ota_package/`
    252 
    253 Access to cache filesystem is granted only to system apps.
    254 
    255 
    256 ## Setting SELinux mode to permissive (0)
    257 
    258 ```txt
    259 local$ adb root
    260 local$ adb shell
    261 android# setenforce 0
    262 android# getenforce
    263 ```
    264 
    265 
    266 ## License
    267 
    268 SystemUpdaterSample app is released under
    269 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
    270