README.md
1# SystemUpdaterSample
2
3This app demonstrates how to use Android system updates APIs to install
4[OTA updates](https://source.android.com/devices/tech/ota/). It contains a
5sample client for `update_engine` to install A/B (seamless) updates.
6
7A/B (seamless) update is available since Android Nougat (API 24), but this sample
8targets the latest android.
9
10
11## Workflow
12
13SystemUpdaterSample app shows list of available updates on the UI. User is allowed
14to select an update and apply it to the device. App shows installation progress,
15logs can be found in `adb logcat`. User can stop or reset an update. Resetting
16the update requests update engine to cancel any ongoing update, and revert
17if the update has been applied. Stopping does not revert the applied update.
18
19
20## Update Config file
21
22In this sample updates are defined in JSON update config files.
23The structure of a config file is defined in
24`com.example.android.systemupdatersample.UpdateConfig`, example file is located
25at `res/raw/sample.json`.
26
27In real-life update system the config files expected to be served from a server
28to the app, but in this sample, the config files are stored on the device.
29The 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
32SystemUpdaterSample 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
35If `ab_install_type` is `NON_STREAMING` then app checks if `url` starts
36with `file://` and passes `url` to the `update_engine`.
37
38If `ab_install_type` is `STREAMING`, app downloads only the entries in need, as
39opposed to the entire package, to initiate a streaming update. The `payload.bin`
40entry, which takes up the majority of the space in an OTA package, will be
41streamed by `update_engine` directly. The ZIP entries in such a package need to be
42saved uncompressed (`ZIP_STORED`), so that their data can be downloaded directly
43with the offset and length. As `payload.bin` itself is already in compressed
44format, the size penalty is marginal.
45
46if `ab_config.force_switch_slot` set true device will boot to the
47updated partition on next reboot; otherwise button "Switch Slot" will
48become active, and user can manually set updated partition as the active slot.
49
50Config files can be generated using `tools/gen_update_config.py`.
51Running `./tools/gen_update_config.py --help` shows usage of the script.
52
53
54## Sample App State vs UpdateEngine Status
55
56UpdateEngine provides status for different stages of update application
57process. But it lacks of proper status codes when update fails.
58
59This creates two problems:
60
611. 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
672. 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
72To solve these problems sample app implements its own separate update
73state - `UpdaterState`. To solve the first problem, sample app persists
74`UpdaterState` on a device. When app is resumed, it checks if `UpdaterState`
75matches the update_engine's status (as onStatusUpdate is guaranteed to be called).
76If they doesn't match, sample app calls `applyPayload` again with the same
77parameters, and handles update completion properly using `onPayloadApplicationCompleted`
78callback. 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
107Sometimes OTA package server might require some HTTP headers to be present,
108e.g. `Authorization` header to contain valid auth token. While performing
109streaming update, `UpdateEngine` allows passing on certain HTTP headers;
110as of writing this sample app, these headers are `Authorization` and `User-Agent`.
111
112`android.os.UpdateEngine#applyPayload` contains information on
113which HTTP headers are supported.
114
115
116## Used update_engine APIs
117
118### UpdateEngine#bind
119
120Binds given callbacks to update_engine. When update_engine successfully
121initialized, it's guaranteed to invoke callback onStatusUpdate.
122
123### UpdateEngine#applyPayload
124
125Start an update attempt to download an apply the provided `payload_url` if
126no other update is running. The extra `key_value_pair_headers` will be
127included when fetching the payload.
128
129`key_value_pair_headers` argument also accepts properties other than HTTP Headers.
130List of allowed properties can be found in `system/update_engine/common/constants.cc`.
131
132### UpdateEngine#cancel
133
134Cancel the ongoing update. The update could be running or suspended, but it
135can't be canceled after it was done.
136
137### UpdateEngine#resetStatus
138
139Reset the already applied update back to an idle state. This method can
140only be called when no update attempt is going on, and it will reset the
141status back to idle, deleting the currently applied update if any.
142
143### Callback: onStatusUpdate
144
145Called whenever the value of `status` or `progress` changes. For
146`progress` values changes, this method will be called only if it changes significantly.
147At this time of writing this doc, delta for `progress` is `0.005`.
148
149`onStatusUpdate` is always called when app binds to update_engine,
150except when update_engine fails to initialize.
151
152### Callback: onPayloadApplicationComplete
153
154Called whenever an update attempt is completed or failed.
155
156
157## Running on a device
158
159The commands are expected to be run from `$ANDROID_BUILD_TOP` and for demo
160purpose only.
161
162### Without the privileged system permissions
163
1641. Compile the app `mmma -j bootable/recovery/updater_sample`.
1652. Install the app to the device using `adb install <APK_PATH>`.
1663. Change permissions on `/data/ota_package/` to `0777` on the device.
1674. Set SELinux mode to permissive. See instructions below.
1685. Add update config files; look above at [Update Config file](#Update-Config-file).
1696. Push OTA packages to the device.
1707. Run the sample app.
171
172### With the privileged system permissions
173
174To run sample app as a privileged system app, it needs to be installed in `/system/priv-app/`.
175This directory is expected to be read-only, unless explicitly remounted.
176
177The recommended way to run the app is to build and install it as a
178privileged 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
1811. [Prepare to build](https://source.android.com/setup/build/building)
1822. 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`.
1863. [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`
1944. Add `privileged: true` to SystemUpdaterSample
195 [building rule](https://android.googlesource.com/platform/bootable/recovery/+/refs/heads/master/updater_sample/Android.bp).
1965. Build sample app `make -j SystemUpdaterSample`.
1976. Build Android `make -j`
1987. [Flash the device](https://source.android.com/setup/build/running)
1998. Add update config files; look above at `## Update Config file`;
200 `adb root` might be required.
2019. Push OTA packages to the device if there is no server to stream packages from;
202 changing of SELinux labels of OTA packages directory might be required
203 `chcon -R u:object_r:ota_package_file:s0 /data/my-sample-ota-builds-dir`
20410. Run the sample app.
205
206
207## Development
208
209- [x] Create a UI with list of configs, current version,
210 control buttons, progress bar and log viewer
211- [x] Add `PayloadSpec` and `PayloadSpecs` for working with
212 update zip file
213- [x] Add `UpdateConfig` for working with json config files
214- [x] Add applying non-streaming update
215- [x] Prepare streaming update (partially downloading package)
216- [x] Add applying streaming update
217- [x] Add stop/reset the update
218- [x] Add demo for passing HTTP headers to `UpdateEngine#applyPayload`
219- [x] [Package compatibility check](https://source.android.com/devices/architecture/vintf/match-rules)
220- [x] Deferred switch slot demo
221- [x] Add UpdateManager; extract update logic from MainActivity
222- [x] Add Sample app update state (separate from update_engine status)
223- [x] Add smart update completion detection using onStatusUpdate
224- [x] Add pause/resume demo
225- [x] Verify system partition checksum for package
226
227
228## Running tests
229
230The commands are expected to be run from `$ANDROID_BUILD_TOP`.
231
2321. Build `make -j SystemUpdaterSample` and `make -j SystemUpdaterSampleTests`.
2332. Install app
234 `adb install $OUT/system/app/SystemUpdaterSample/SystemUpdaterSample.apk`
2353. Install tests
236 `adb install $OUT/testcases/SystemUpdaterSampleTests/arm64/SystemUpdaterSampleTests.apk`
2374. Run tests
238 `adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner`
2395. Run a test file
240 ```
241 adb shell am instrument \
242 -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \
243 com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
244 ```
245
246
247## Accessing `android.os.UpdateEngine` API
248
249`android.os.UpdateEngine` APIs are marked as `@SystemApi`, meaning only system
250apps can access them.
251
252
253## Getting read/write access to `/data/ota_package/`
254
255Access to cache filesystem is granted only to system apps.
256
257
258## Setting SELinux mode to permissive (0)
259
260```txt
261local$ adb root
262local$ adb shell
263android# setenforce 0
264android# getenforce
265```
266
267
268## License
269
270SystemUpdaterSample app is released under
271[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
272