Merge "sample_updater: create tools/gen_update_config.py"
am: 6bd1d9b8d9
Change-Id: I40f5c3650517bcdc20194fbc7abf0cadaccd9d4f
diff --git a/updater_sample/.gitignore b/updater_sample/.gitignore
index 487263f..f846472 100644
--- a/updater_sample/.gitignore
+++ b/updater_sample/.gitignore
@@ -7,3 +7,4 @@
.idea/
gen/
.vscode
+local.properties
diff --git a/updater_sample/README.md b/updater_sample/README.md
index d9864b4..ee1faaf 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -9,6 +9,39 @@
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`. If `ab_install_type`
+is `NON_STREAMING` then app downloads the whole package and
+passes it to the `update_engine`. If `ab_install_type` is `STREAMING`
+then app downloads only some files to prepare the streaming update and
+`update_engine` will stream only `payload.bin`.
+To support streaming A/B (seamless) update, OTA package file must be
+an uncompressed (ZIP_STORED) zip file.
+
+Config files can be generated using `tools/gen_update_config.py`.
+Running `./tools/gen_update_config.py --help` shows usage of the script.
+
+
## Running on a device
The commands expected to be run from `$ANDROID_BUILD_TOP`.
@@ -18,13 +51,6 @@
3. Add update config files.
-## Update Config file
-
-Directory can be found in logs or on UI. Usually json config files are located in
-`/data/user/0/com.example.android.systemupdatersample/files/configs/`. Example file
-is located at `res/raw/sample.json`.
-
-
## Development
- [x] Create a UI with list of configs, current version,
@@ -33,8 +59,8 @@
update zip file
- [x] Add `UpdateConfig` for working with json config files
- [x] Add applying non-streaming update
-- [ ] Add applying streaming update
- [ ] Prepare streaming update (partially downloading package)
+- [ ] Add applying streaming update
- [ ] Add tests for `MainActivity`
- [ ] Add stop/reset the update
- [ ] Verify system partition checksum for package
diff --git a/updater_sample/tools/gen_update_config.py b/updater_sample/tools/gen_update_config.py
new file mode 100755
index 0000000..44e9ac3
--- /dev/null
+++ b/updater_sample/tools/gen_update_config.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Given a OTA package file, produces update config JSON file.
+
+Example: tools/gen_update.config.py \\
+ --ab_install_type=STREAMING \\
+ ota-build-001.zip \\
+ my-config-001.json \\
+ http://foo.bar/ota-builds/ota-build-001.zip
+"""
+
+import argparse
+import json
+import os.path
+import sys
+import zipfile
+
+
+class GenUpdateConfig(object): # pylint: disable=too-few-public-methods
+ """
+ A class that generates update configuration file from an OTA package.
+
+ Currently supports only A/B (seamless) OTA packages.
+ TODO: add non-A/B packages support.
+ """
+
+ AB_INSTALL_TYPE_STREAMING = 'STREAMING'
+ AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING'
+ METADATA_NAME = 'META-INF/com/android/metadata'
+
+ def __init__(self, package, out, url, ab_install_type):
+ self.package = package
+ self.out = out
+ self.url = url
+ self.ab_install_type = ab_install_type
+ self.streaming_required = (
+ # payload.bin and payload_properties.txt must exist.
+ 'payload.bin',
+ 'payload_properties.txt',
+ )
+ self.streaming_optional = (
+ # care_map.txt is available only if dm-verity is enabled.
+ 'care_map.txt',
+ # compatibility.zip is available only if target supports Treble.
+ 'compatibility.zip',
+ )
+
+ def run(self):
+ """generate config"""
+ streaming_metadata = None
+ if self.ab_install_type == GenUpdateConfig.AB_INSTALL_TYPE_STREAMING:
+ streaming_metadata = self._gen_ab_streaming_metadata()
+
+ config = {
+ '__': '*** Generated using tools/gen_update_config.py ***',
+ 'name': self.ab_install_type[0] + ' ' + os.path.basename(self.package)[:-4],
+ 'url': self.url,
+ 'ab_streaming_metadata': streaming_metadata,
+ 'ab_install_type': self.ab_install_type,
+ }
+
+ with open(self.out, 'w') as out:
+ json.dump(config, out, indent=4, separators=(',', ': '), sort_keys=True)
+ print('Config is written to ' + out.name)
+
+ def _gen_ab_streaming_metadata(self):
+ """Open zip file and get metadata for files required for streaming update."""
+ with zipfile.ZipFile(self.package, 'r') as package_zip:
+ property_files = self._get_property_files(package_zip)
+
+ metadata = {
+ 'property_files': property_files
+ }
+
+ return metadata
+
+ def _get_property_files(self, zip_file):
+ """Constructs the property-files list for A/B streaming metadata"""
+
+ def compute_entry_offset_size(name):
+ """Computes the zip entry offset and size."""
+ info = zip_file.getinfo(name)
+ offset = info.header_offset + len(info.FileHeader())
+ size = info.file_size
+ return {
+ 'filename': os.path.basename(name),
+ 'offset': offset,
+ 'size': size,
+ }
+
+ property_files = []
+ for entry in self.streaming_required:
+ property_files.append(compute_entry_offset_size(entry))
+ for entry in self.streaming_optional:
+ if entry in zip_file.namelist():
+ property_files.append(compute_entry_offset_size(entry))
+
+ # 'META-INF/com/android/metadata' is required
+ property_files.append(compute_entry_offset_size(GenUpdateConfig.METADATA_NAME))
+
+ return property_files
+
+
+def main(): # pylint: disable=missing-docstring
+ ab_install_type_choices = [
+ GenUpdateConfig.AB_INSTALL_TYPE_STREAMING,
+ GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING]
+ parser = argparse.ArgumentParser(description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('--ab_install_type',
+ type=str,
+ default=GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING,
+ choices=ab_install_type_choices,
+ help='A/B update installation type')
+ parser.add_argument('package',
+ type=str,
+ help='OTA package zip file')
+ parser.add_argument('out',
+ type=str,
+ help='Update configuration JSON file')
+ parser.add_argument('url',
+ type=str,
+ help='OTA package download url')
+ args = parser.parse_args()
+
+ if not args.out.endswith('.json'):
+ print('out must be a json file')
+ sys.exit(1)
+
+ gen = GenUpdateConfig(
+ package=args.package,
+ out=args.out,
+ url=args.url,
+ ab_install_type=args.ab_install_type)
+ gen.run()
+
+
+if __name__ == '__main__':
+ main()