From 83ff1f0b05afc3659141e964e9f6ad7f59acc330 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 7 May 2018 19:04:03 -0700 Subject: recovery: Remove unneeded include of minui.h. Test: mmma -j bootable/recovery Change-Id: I1a79fa6386d56bf5e20ee074352d287403d2d745 --- recovery.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/recovery.cpp b/recovery.cpp index 2ff7f1d9e..8dc04a852 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -64,7 +64,6 @@ #include "fuse_sideload.h" #include "install.h" #include "logging.h" -#include "minui/minui.h" #include "otautil/dirutil.h" #include "otautil/error_code.h" #include "otautil/paths.h" -- cgit v1.2.3 From 2dea53ef2b96fe09083d9cad42b5f07b6ad83ab5 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Wed, 2 May 2018 17:15:03 -0700 Subject: recovery: Get UI and locale from device. This removes some reliance on the global locale and ui variables. Test: Recovery works Bug: 78793464 Change-Id: I78f1a2b321f5d50aa58b10735a73ae137283353a --- device.h | 6 ++++++ recovery.cpp | 12 +++++++----- screen_ui.cpp | 4 ++++ screen_ui.h | 1 + stub_ui.h | 3 +++ ui.h | 2 ++ 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/device.h b/device.h index 8788b2d14..9510fbedb 100644 --- a/device.h +++ b/device.h @@ -58,6 +58,12 @@ class Device { return ui_; } + // Sets the UI object to the given UI. Used to override the default UI in case initialization + // failed, or we want a stub for some reason. + virtual void SetUI(RecoveryUI* ui) { + ui_ = ui; + } + // Called when recovery starts up (after the UI has been obtained and initialized and after the // arguments have been parsed, but before anything else). virtual void StartRecovery() {}; diff --git a/recovery.cpp b/recovery.cpp index 2ff7f1d9e..01dce0780 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -89,7 +89,6 @@ static constexpr const char* SDCARD_ROOT = "/sdcard"; // into target_files.zip. Assert the version defined in code and in Android.mk are consistent. static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions."); -static std::string locale; static bool has_cache = false; RecoveryUI* ui = nullptr; @@ -232,7 +231,8 @@ static void set_sdcard_update_bootloader_message() { // Clear the recovery command and prepare to boot a (hopefully working) system, // copy our log file to cache as well (for the system to read). This function is // idempotent: call it as many times as you like. -static void finish_recovery() { +static void finish_recovery(Device* device) { + std::string locale = device->GetUI()->GetLocale(); // Save the locale to cache, so if recovery is next started up without a '--locale' argument // (e.g., directly from the bootloader) it will use the last-known locale. if (!locale.empty() && has_cache) { @@ -809,7 +809,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { // which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. static Device::BuiltinAction prompt_and_wait(Device* device, int status) { for (;;) { - finish_recovery(); + finish_recovery(device); switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: @@ -897,7 +897,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case Device::RUN_LOCALE_TEST: { ScreenRecoveryUI* screen_ui = static_cast(ui); - screen_ui->CheckBackgroundTextImages(locale); + screen_ui->CheckBackgroundTextImages(screen_ui->GetLocale()); break; } case Device::MOUNT_SYSTEM: @@ -1105,6 +1105,7 @@ int start_recovery(int argc, char** argv) { bool shutdown_after = false; int retry_count = 0; bool security_update = false; + std::string locale; int arg; int option_index; @@ -1182,6 +1183,7 @@ int start_recovery(int argc, char** argv) { ui = new StubRecoveryUI(); } } + device->SetUI(ui); // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". @@ -1348,7 +1350,7 @@ int start_recovery(int argc, char** argv) { } // Save logs and clean up before rebooting or shutting down. - finish_recovery(); + finish_recovery(device); switch (after) { case Device::SHUTDOWN: diff --git a/screen_ui.cpp b/screen_ui.cpp index 7ae81e55f..4a1a5b99c 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -748,6 +748,10 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { return true; } +std::string ScreenRecoveryUI::GetLocale() { + return locale_; +} + void ScreenRecoveryUI::LoadAnimation() { std::unique_ptr dir(opendir("/res/images"), closedir); dirent* de; diff --git a/screen_ui.h b/screen_ui.h index fb811ce70..3e391beba 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -114,6 +114,7 @@ class ScreenRecoveryUI : public RecoveryUI { explicit ScreenRecoveryUI(bool scrollable_menu); bool Init(const std::string& locale) override; + std::string GetLocale() override; // overall recovery state ("background image") void SetBackground(Icon icon) override; diff --git a/stub_ui.h b/stub_ui.h index 2ccd49115..fddf4e71e 100644 --- a/stub_ui.h +++ b/stub_ui.h @@ -28,6 +28,9 @@ class StubRecoveryUI : public RecoveryUI { public: StubRecoveryUI() = default; + std::string GetLocale() override { + return ""; + } void SetBackground(Icon /* icon */) override {} void SetSystemUpdateText(bool /* security_update */) override {} diff --git a/ui.h b/ui.h index 35cc36e70..f8677902e 100644 --- a/ui.h +++ b/ui.h @@ -57,6 +57,8 @@ class RecoveryUI { // the given locale. Returns true on success. virtual bool Init(const std::string& locale); + virtual std::string GetLocale() = 0; + // Shows a stage indicator. Called immediately after Init(). virtual void SetStage(int current, int max) = 0; -- cgit v1.2.3 From 39c4918a404a9c8119aac5086014df26c2a71664 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 7 May 2018 22:50:33 -0700 Subject: screen_ui: Drop the parameter in CheckBackgroundTextImages. ScreenRecoveryUI already has the info in locale_. Also when showing "Current locale: X/Y" on screen, use 1-based index for X, so that we have 1 <= X <= Y. Test: Build anf flash recovery image on aosp_bullhead-userdebug. Choose `Run locale test` from UI. Change-Id: I5dd4de82e63890ddf755f4e23cd2290ad5d50ece --- recovery.cpp | 2 +- screen_ui.cpp | 5 +++-- screen_ui.h | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/recovery.cpp b/recovery.cpp index 01dce0780..6aceb2d61 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -897,7 +897,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case Device::RUN_LOCALE_TEST: { ScreenRecoveryUI* screen_ui = static_cast(ui); - screen_ui->CheckBackgroundTextImages(screen_ui->GetLocale()); + screen_ui->CheckBackgroundTextImages(); break; } case Device::MOUNT_SYSTEM: diff --git a/screen_ui.cpp b/screen_ui.cpp index 4a1a5b99c..90e0e30af 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -372,7 +372,7 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector instruction = { locale_selection, @@ -395,13 +395,14 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector locales_entries = get_locales_in_png("installing_text"); if (locales_entries.empty()) { Print("Failed to load locales from the resource files\n"); return; } + std::string saved_locale = locale_; size_t selected = 0; SelectAndShowBackgroundText(locales_entries, selected); diff --git a/screen_ui.h b/screen_ui.h index 3e391beba..d4923f566 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -148,9 +148,9 @@ class ScreenRecoveryUI : public RecoveryUI { void SetColor(UIElement e) const; - // Check the background text image. Use volume up/down button to cycle through the locales - // embedded in the png file, and power button to go back to recovery main menu. - void CheckBackgroundTextImages(const std::string& saved_locale); + // Checks the background text image, for debugging purpose. It iterates the locales embedded in + // the on-device resource files and shows the localized text, for manual inspection. + void CheckBackgroundTextImages(); protected: // The margin that we don't want to use for showing texts (e.g. round screen, or screen with -- cgit v1.2.3 From 0dd5a83d0ec4c9d7b51843dd6eebd5ce92a1ad92 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Mon, 23 Apr 2018 11:38:54 -0700 Subject: updater_sample: add streaming support - UpdateConfigs: add helper methods for streaming - add PrepareStreamingService intent service Test: manually and junit4 Bug: 77148143 Change-Id: I61711eb9abe051987e725fbd94f8cd029ff21dd3 --- updater_sample/AndroidManifest.xml | 1 + .../android/systemupdatersample/UpdateConfig.java | 18 +- .../services/PrepareStreamingService.java | 216 +++++++++++++++++++++ .../systemupdatersample/ui/MainActivity.java | 12 ++ .../systemupdatersample/util/FileDownloader.java | 13 +- .../systemupdatersample/util/UpdateConfigs.java | 26 ++- 6 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml index 5bbb21c84..4b4448438 100644 --- a/updater_sample/AndroidManifest.xml +++ b/updater_sample/AndroidManifest.xml @@ -31,6 +31,7 @@ + diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index 23510e426..1851724ed 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -69,11 +69,11 @@ public class UpdateConfig implements Parcelable { if (c.mAbInstallType == AB_INSTALL_TYPE_STREAMING) { JSONObject meta = o.getJSONObject("ab_streaming_metadata"); JSONArray propertyFilesJson = meta.getJSONArray("property_files"); - InnerFile[] propertyFiles = - new InnerFile[propertyFilesJson.length()]; + PackageFile[] propertyFiles = + new PackageFile[propertyFilesJson.length()]; for (int i = 0; i < propertyFilesJson.length(); i++) { JSONObject p = propertyFilesJson.getJSONObject(i); - propertyFiles[i] = new InnerFile( + propertyFiles[i] = new PackageFile( p.getString("filename"), p.getLong("offset"), p.getLong("size")); @@ -176,17 +176,17 @@ public class UpdateConfig implements Parcelable { private static final long serialVersionUID = 31042L; /** defines beginning of update data in archive */ - private InnerFile[] mPropertyFiles; + private PackageFile[] mPropertyFiles; public StreamingMetadata() { - mPropertyFiles = new InnerFile[0]; + mPropertyFiles = new PackageFile[0]; } - public StreamingMetadata(InnerFile[] propertyFiles) { + public StreamingMetadata(PackageFile[] propertyFiles) { this.mPropertyFiles = propertyFiles; } - public InnerFile[] getPropertyFiles() { + public PackageFile[] getPropertyFiles() { return mPropertyFiles; } } @@ -194,7 +194,7 @@ public class UpdateConfig implements Parcelable { /** * Description of a file in an OTA package zip file. */ - public static class InnerFile implements Serializable { + public static class PackageFile implements Serializable { private static final long serialVersionUID = 31043L; @@ -207,7 +207,7 @@ public class UpdateConfig implements Parcelable { /** size of the update data in archive */ private long mSize; - public InnerFile(String filename, long offset, long size) { + public PackageFile(String filename, long offset, long size) { this.mFilename = filename; this.mOffset = offset; this.mSize = size; diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java new file mode 100644 index 000000000..840a6d607 --- /dev/null +++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java @@ -0,0 +1,216 @@ +/* + * 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. + */ + +package com.example.android.systemupdatersample.services; + +import static com.example.android.systemupdatersample.util.PackageFiles.OTA_PACKAGE_DIR; +import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; +import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME; + +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.util.Log; + +import com.example.android.systemupdatersample.PayloadSpec; +import com.example.android.systemupdatersample.UpdateConfig; +import com.example.android.systemupdatersample.util.FileDownloader; +import com.example.android.systemupdatersample.util.PackageFiles; +import com.example.android.systemupdatersample.util.PayloadSpecs; +import com.example.android.systemupdatersample.util.UpdateConfigs; +import com.google.common.collect.ImmutableSet; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Optional; + +/** + * This IntentService will download/extract the necessary files from the package zip + * without downloading the whole package. And it constructs {@link PayloadSpec}. + * All this work required to install streaming A/B updates. + * + * PrepareStreamingService runs on it's own thread. It will notify activity + * using interface {@link UpdateResultCallback} when update is ready to install. + */ +public class PrepareStreamingService extends IntentService { + + /** + * UpdateResultCallback result codes. + */ + public static final int RESULT_CODE_SUCCESS = 0; + public static final int RESULT_CODE_ERROR = 1; + + /** + * This interface is used to send results from {@link PrepareStreamingService} to + * {@code MainActivity}. + */ + public interface UpdateResultCallback { + + /** + * Invoked when files are downloaded and payload spec is constructed. + * + * @param resultCode result code, values are defined in {@link PrepareStreamingService} + * @param payloadSpec prepared payload spec for streaming update + */ + void onReceiveResult(int resultCode, PayloadSpec payloadSpec); + } + + /** + * Starts PrepareStreamingService. + * + * @param context application context + * @param config update config + * @param resultCallback callback that will be called when the update is ready to be installed + */ + public static void startService(Context context, + UpdateConfig config, + UpdateResultCallback resultCallback) { + Log.d(TAG, "Starting PrepareStreamingService"); + ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback); + Intent intent = new Intent(context, PrepareStreamingService.class); + intent.putExtra(EXTRA_PARAM_CONFIG, config); + intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver); + context.startService(intent); + } + + public PrepareStreamingService() { + super(TAG); + } + + private static final String TAG = "PrepareStreamingService"; + + /** + * Extra params that will be sent from Activity to IntentService. + */ + private static final String EXTRA_PARAM_CONFIG = "config"; + private static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver"; + + /** + * The files that should be downloaded before streaming. + */ + private static final ImmutableSet PRE_STREAMING_FILES_SET = + ImmutableSet.of( + PackageFiles.CARE_MAP_FILE_NAME, + PackageFiles.COMPATIBILITY_ZIP_FILE_NAME, + PackageFiles.METADATA_FILE_NAME, + PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME + ); + + @Override + protected void onHandleIntent(Intent intent) { + Log.d(TAG, "On handle intent is called"); + UpdateConfig config = intent.getParcelableExtra(EXTRA_PARAM_CONFIG); + ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER); + + try { + downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); + } catch (IOException e) { + Log.e(TAG, "Failed to download pre-streaming files", e); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + Optional payloadBinary = + UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config); + + if (!payloadBinary.isPresent()) { + Log.e(TAG, "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + Optional properties = + UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config); + + if (!properties.isPresent()) { + Log.e(TAG, "Failed to find " + PAYLOAD_PROPERTIES_FILE_NAME + " in config"); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + PayloadSpec spec; + try { + spec = PayloadSpecs.forStreaming(config.getUrl(), + payloadBinary.get().getOffset(), + payloadBinary.get().getSize(), + Paths.get(OTA_PACKAGE_DIR, properties.get().getFilename()).toFile() + ); + } catch (IOException e) { + Log.e(TAG, "PayloadSpecs failed to create PayloadSpec for streaming", e); + resultReceiver.send(RESULT_CODE_ERROR, null); + return; + } + + resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec)); + } + + /** + * Downloads files defined in {@link UpdateConfig#getStreamingMetadata()} + * and exists in {@code PRE_STREAMING_FILES_SET}, and put them + * in directory {@code dir}. + * @throws IOException when can't download a file + */ + private static void downloadPreStreamingFiles(UpdateConfig config, String dir) + throws IOException { + Log.d(TAG, "Downloading files to " + dir); + for (UpdateConfig.PackageFile file : config.getStreamingMetadata().getPropertyFiles()) { + if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) { + Log.d(TAG, "Downloading file " + file.getFilename()); + FileDownloader downloader = new FileDownloader( + config.getUrl(), + file.getOffset(), + file.getSize(), + Paths.get(dir, file.getFilename()).toFile()); + downloader.download(); + } + } + } + + /** + * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec} + * to {@link UpdateResultCallback#onReceiveResult}. + */ + private static class CallbackResultReceiver extends ResultReceiver { + + static Bundle createBundle(PayloadSpec payloadSpec) { + Bundle b = new Bundle(); + b.putSerializable(BUNDLE_PARAM_PAYLOAD_SPEC, payloadSpec); + return b; + } + + private static final String BUNDLE_PARAM_PAYLOAD_SPEC = "payload-spec"; + + private UpdateResultCallback mUpdateResultCallback; + + CallbackResultReceiver(Handler handler, UpdateResultCallback updateResultCallback) { + super(handler); + this.mUpdateResultCallback = updateResultCallback; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + PayloadSpec payloadSpec = null; + if (resultCode == RESULT_CODE_SUCCESS) { + payloadSpec = (PayloadSpec) resultData.getSerializable(BUNDLE_PARAM_PAYLOAD_SPEC); + } + mUpdateResultCallback.onReceiveResult(resultCode, payloadSpec); + } + } + +} diff --git a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java index d6a6ce3f5..359e2b10c 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -34,6 +34,7 @@ import android.widget.Toast; import com.example.android.systemupdatersample.PayloadSpec; import com.example.android.systemupdatersample.R; import com.example.android.systemupdatersample.UpdateConfig; +import com.example.android.systemupdatersample.services.PrepareStreamingService; import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateConfigs; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; @@ -297,6 +298,17 @@ public class MainActivity extends Activity { updateEngineApplyPayload(payload); } else { Log.d(TAG, "Starting PrepareStreamingService"); + PrepareStreamingService.startService(this, config, (code, payloadSpec) -> { + if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) { + updateEngineApplyPayload(payloadSpec); + } else { + Log.e(TAG, "PrepareStreamingService failed, result code is " + code); + Toast.makeText( + MainActivity.this, + "PrepareStreamingService failed, result code is " + code, + Toast.LENGTH_LONG).show(); + } + }); } } diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java index 5c1d71117..ddd0919b8 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java @@ -38,22 +38,23 @@ public final class FileDownloader { private String mUrl; private long mOffset; private long mSize; - private File mOut; + private File mDestination; - public FileDownloader(String url, long offset, long size, File out) { + public FileDownloader(String url, long offset, long size, File destination) { this.mUrl = url; this.mOffset = offset; this.mSize = size; - this.mOut = out; + this.mDestination = destination; } /** * Downloads the file with given offset and size. + * @throws IOException when can't download the file */ public void download() throws IOException { - Log.d("FileDownloader", "downloading " + mOut.getName() + Log.d("FileDownloader", "downloading " + mDestination.getName() + " from " + mUrl - + " to " + mOut.getAbsolutePath()); + + " to " + mDestination.getAbsolutePath()); URL url = new URL(mUrl); URLConnection connection = url.openConnection(); @@ -61,7 +62,7 @@ public final class FileDownloader { // download the file try (InputStream input = connection.getInputStream()) { - try (OutputStream output = new FileOutputStream(mOut)) { + try (OutputStream output = new FileOutputStream(mDestination)) { long skipped = input.skip(mOffset); if (skipped != mOffset) { throw new IOException("Can't download file " diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java index 71d4df8ab..5080cb6d8 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/UpdateConfigs.java @@ -26,14 +26,16 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; /** * Utility class for working with json update configurations. */ public final class UpdateConfigs { - private static final String UPDATE_CONFIGS_ROOT = "configs/"; + public static final String UPDATE_CONFIGS_ROOT = "configs/"; /** * @param configs update configs @@ -48,13 +50,12 @@ public final class UpdateConfigs { * @return configs root directory */ public static String getConfigsRoot(Context context) { - return Paths.get(context.getFilesDir().toString(), - UPDATE_CONFIGS_ROOT).toString(); + return Paths + .get(context.getFilesDir().toString(), UPDATE_CONFIGS_ROOT) + .toString(); } /** - * It parses only {@code .json} files. - * * @param context application context * @return list of configs from directory {@link UpdateConfigs#getConfigsRoot} */ @@ -80,5 +81,20 @@ public final class UpdateConfigs { return configs; } + /** + * @param filename searches by given filename + * @param config searches in {@link UpdateConfig#getStreamingMetadata()} + * @return offset and size of {@code filename} in the package zip file + * stored as {@link UpdateConfig.PackageFile}. + */ + public static Optional getPropertyFile( + final String filename, + UpdateConfig config) { + return Arrays + .stream(config.getStreamingMetadata().getPropertyFiles()) + .filter(file -> filename.equals(file.getFilename())) + .findFirst(); + } + private UpdateConfigs() {} } -- cgit v1.2.3 From 96eb59e4b13b07a18fc1a6a85786f2c287bd21db Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 4 May 2018 12:17:01 -0700 Subject: updater_sample: update tools - Allow gen_update_config.py to use ota_from_target_files from $ANDROID_BUILD_TOP/build/make/tools/releasetools/ - tests/res/raw/ota_002_package.zip re-generated using functions from $ANDROID_BUILD_TOP/build/make/tools/releasetools/test_ota_from_target_files.py - sample app tests updated Test: ./tools/gen_update_config_test.py Change-Id: I5c492ec22782ba54fe481f592a44e797c695684e Signed-off-by: Zhomart Mukhamejanov --- updater_sample/README.md | 7 ++- updater_sample/tests/res/raw/ota_002_package.zip | Bin 638 -> 2181 bytes .../tests/res/raw/update_config_stream_002.json | 23 +++++--- .../util/FileDownloaderTest.java | 4 +- .../systemupdatersample/util/PayloadSpecsTest.java | 56 ++++++------------- updater_sample/tools/gen_update_config.py | 51 ++++++++--------- updater_sample/tools/gen_update_config_test.py | 55 ------------------- updater_sample/tools/test_gen_update_config.py | 61 +++++++++++++++++++++ 8 files changed, 119 insertions(+), 138 deletions(-) delete mode 100755 updater_sample/tools/gen_update_config_test.py create mode 100755 updater_sample/tools/test_gen_update_config.py diff --git a/updater_sample/README.md b/updater_sample/README.md index 12f803ff6..2c1f0ced5 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -69,14 +69,15 @@ purpose only. update zip file - [x] Add `UpdateConfig` for working with json config files - [x] Add applying non-streaming update -- [ ] Prepare streaming update (partially downloading package) -- [ ] Add applying streaming update +- [x] Prepare streaming update (partially downloading package) +- [x] Add applying streaming update +- [x] Add stop/reset the update - [ ] Add tests for `MainActivity` -- [ ] Add stop/reset the update - [ ] Verify system partition checksum for package - [ ] HAL compatibility check - [ ] Change partition demo - [ ] Add non-A/B updates demo +- [ ] Add docs for passing HTTP headers to `UpdateEngine#applyPayload` ## Running tests diff --git a/updater_sample/tests/res/raw/ota_002_package.zip b/updater_sample/tests/res/raw/ota_002_package.zip index 145c62e6a..6bf2a23b2 100644 Binary files a/updater_sample/tests/res/raw/ota_002_package.zip and b/updater_sample/tests/res/raw/ota_002_package.zip differ diff --git a/updater_sample/tests/res/raw/update_config_stream_002.json b/updater_sample/tests/res/raw/update_config_stream_002.json index f00f19ce6..cf4469b1c 100644 --- a/updater_sample/tests/res/raw/update_config_stream_002.json +++ b/updater_sample/tests/res/raw/update_config_stream_002.json @@ -3,30 +3,35 @@ "ab_install_type": "STREAMING", "ab_streaming_metadata": { "property_files": [ + { + "filename": "payload_metadata.bin", + "offset": 41, + "size": 827 + }, { "filename": "payload.bin", "offset": 41, - "size": 7 + "size": 1392 }, { "filename": "payload_properties.txt", - "offset": 100, - "size": 18 + "offset": 1485, + "size": 147 }, { "filename": "care_map.txt", - "offset": 160, - "size": 8 + "offset": 1674, + "size": 12 }, { "filename": "compatibility.zip", - "offset": 215, - "size": 13 + "offset": 1733, + "size": 17 }, { "filename": "metadata", - "offset": 287, - "size": 8 + "offset": 1809, + "size": 29 } ] }, diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java index 80506ee6d..009610e86 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java @@ -70,11 +70,11 @@ public class FileDownloaderTest { .toFile(); Files.deleteIfExists(outFile.toPath()); // download a chunk of ota.zip - FileDownloader downloader = new FileDownloader(url, 160, 8, outFile); + FileDownloader downloader = new FileDownloader(url, 1674, 12, outFile); downloader.download(); String downloadedContent = String.join("\n", Files.readAllLines(outFile.toPath())); // archive contains text files with uppercase filenames - assertEquals("CARE_MAP", downloadedContent); + assertEquals("CARE_MAP-TXT", downloadedContent); } } diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java index 2912e209e..d9e54652f 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/PayloadSpecsTest.java @@ -17,8 +17,6 @@ package com.example.android.systemupdatersample.util; import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; -import static com.example.android.systemupdatersample.util.PackageFiles - .PAYLOAD_PROPERTIES_FILE_NAME; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -29,6 +27,7 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.PayloadSpec; +import com.example.android.systemupdatersample.tests.R; import com.google.common.base.Charsets; import com.google.common.io.Files; @@ -39,12 +38,8 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; +import java.nio.file.Paths; /** * Tests if PayloadSpecs parses update package zip file correctly. @@ -54,12 +49,11 @@ import java.util.zip.ZipOutputStream; public class PayloadSpecsTest { private static final String PROPERTIES_CONTENTS = "k1=val1\nkey2=val2"; - private static final String PAYLOAD_CONTENTS = "hello\nworld"; - private static final int PAYLOAD_SIZE = PAYLOAD_CONTENTS.length(); private File mTestDir; private Context mTargetContext; + private Context mTestContext; @Rule public final ExpectedException thrown = ExpectedException.none(); @@ -67,21 +61,30 @@ public class PayloadSpecsTest { @Before public void setUp() { mTargetContext = InstrumentationRegistry.getTargetContext(); + mTestContext = InstrumentationRegistry.getContext(); mTestDir = mTargetContext.getFilesDir(); } @Test public void forNonStreaming_works() throws Exception { - File packageFile = createMockZipFile(); + // Prepare the target file + File packageFile = Paths + .get(mTargetContext.getCacheDir().getAbsolutePath(), "ota.zip") + .toFile(); + java.nio.file.Files.deleteIfExists(packageFile.toPath()); + java.nio.file.Files.copy(mTestContext.getResources().openRawResource(R.raw.ota_002_package), + packageFile.toPath()); PayloadSpec spec = PayloadSpecs.forNonStreaming(packageFile); assertEquals("correct url", "file://" + packageFile.getAbsolutePath(), spec.getUrl()); assertEquals("correct payload offset", 30 + PAYLOAD_BINARY_FILE_NAME.length(), spec.getOffset()); - assertEquals("correct payload size", PAYLOAD_SIZE, spec.getSize()); - assertArrayEquals("correct properties", - new String[]{"k1=val1", "key2=val2"}, spec.getProperties().toArray(new String[0])); + assertEquals("correct payload size", 1392, spec.getSize()); + assertEquals(4, spec.getProperties().size()); + assertEquals( + "FILE_HASH=sEAK/NMbU7GGe01xt55FsPafIPk8IYyBOAd6SiDpiMs=", + spec.getProperties().get(0)); } @Test @@ -105,33 +108,6 @@ public class PayloadSpecsTest { new String[]{"k1=val1", "key2=val2"}, spec.getProperties().toArray(new String[0])); } - /** - * Creates package zip file that contains payload.bin and payload_properties.txt - */ - private File createMockZipFile() throws IOException { - File testFile = new File(mTestDir, "test.zip"); - try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(testFile))) { - // Add payload.bin entry. - ZipEntry entry = new ZipEntry(PAYLOAD_BINARY_FILE_NAME); - entry.setMethod(ZipEntry.STORED); - entry.setCompressedSize(PAYLOAD_SIZE); - entry.setSize(PAYLOAD_SIZE); - CRC32 crc = new CRC32(); - crc.update(PAYLOAD_CONTENTS.getBytes(StandardCharsets.UTF_8)); - entry.setCrc(crc.getValue()); - zos.putNextEntry(entry); - zos.write(PAYLOAD_CONTENTS.getBytes(StandardCharsets.UTF_8)); - zos.closeEntry(); - - // Add payload properties entry. - ZipEntry propertiesEntry = new ZipEntry(PAYLOAD_PROPERTIES_FILE_NAME); - zos.putNextEntry(propertiesEntry); - zos.write(PROPERTIES_CONTENTS.getBytes(StandardCharsets.UTF_8)); - zos.closeEntry(); - } - return testFile; - } - private File createMockPropertiesFile() throws IOException { File propertiesFile = new File(mTestDir, PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME); Files.asCharSink(propertiesFile, Charsets.UTF_8).write(PROPERTIES_CONTENTS); diff --git a/updater_sample/tools/gen_update_config.py b/updater_sample/tools/gen_update_config.py index 057812479..4efa9f1c4 100755 --- a/updater_sample/tools/gen_update_config.py +++ b/updater_sample/tools/gen_update_config.py @@ -17,11 +17,13 @@ """ 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 +Example: + $ PYTHONPATH=$ANDROID_BUILD_TOP/build/make/tools/releasetools:$PYTHONPATH \\ + bootable/recovery/updater_sample/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 @@ -30,6 +32,8 @@ import os.path import sys import zipfile +import ota_from_target_files # pylint: disable=import-error + class GenUpdateConfig(object): """ @@ -41,7 +45,6 @@ class GenUpdateConfig(object): AB_INSTALL_TYPE_STREAMING = 'STREAMING' AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING' - METADATA_NAME = 'META-INF/com/android/metadata' def __init__(self, package, url, ab_install_type): self.package = package @@ -82,37 +85,27 @@ class GenUpdateConfig(object): def _gen_ab_streaming_metadata(self): """Builds 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 + 'property_files': self._get_property_files(package_zip) } return metadata - def _get_property_files(self, zip_file): + @staticmethod + def _get_property_files(package_zip): """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, - } - + ab_ota = ota_from_target_files.AbOtaPropertyFiles() + property_str = ab_ota.GetPropertyFilesString(package_zip, False) 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)) + for file in property_str.split(','): + filename, offset, size = file.split(':') + inner_file = { + 'filename': filename, + 'offset': int(offset), + 'size': int(size) + } + property_files.append(inner_file) return property_files diff --git a/updater_sample/tools/gen_update_config_test.py b/updater_sample/tools/gen_update_config_test.py deleted file mode 100755 index 951d4c4a7..000000000 --- a/updater_sample/tools/gen_update_config_test.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/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. - -""" -Tests gen_update_config.py -""" - -import os.path -import unittest -from gen_update_config import GenUpdateConfig - - -class GenUpdateConfigTest(unittest.TestCase): # pylint: disable=missing-docstring - - def test_ab_install_type_streaming(self): - """tests if streaming property files' offset and size are generated properly""" - config, package = self._generate_config() - property_files = config['ab_streaming_metadata']['property_files'] - self.assertEqual(len(property_files), 5) - with open(package, 'rb') as pkg_file: - for prop in property_files: - filename, offset, size = prop['filename'], prop['offset'], prop['size'] - pkg_file.seek(offset) - data = pkg_file.read(size).decode('ascii') - # data in the archive are just uppercase filenames without extension - expected_data = filename.split('.')[0].upper() - self.assertEqual(data, expected_data) - - @staticmethod - def _generate_config(): - """Generates JSON config from ota_002_package.zip.""" - ota_package = os.path.join(os.path.dirname(__file__), - '../tests/res/raw/ota_002_package.zip') - gen = GenUpdateConfig(ota_package, - 'file:///foo.bar', - GenUpdateConfig.AB_INSTALL_TYPE_STREAMING) - gen.run() - return gen.config, ota_package - - -if __name__ == '__main__': - unittest.main() diff --git a/updater_sample/tools/test_gen_update_config.py b/updater_sample/tools/test_gen_update_config.py new file mode 100755 index 000000000..c907cf2f9 --- /dev/null +++ b/updater_sample/tools/test_gen_update_config.py @@ -0,0 +1,61 @@ +#!/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. + +""" +Tests gen_update_config.py. + +Example: + $ PYTHONPATH=$ANDROID_BUILD_TOP/build/make/tools/releasetools:$PYTHONPATH \\ + python3 -m unittest test_gen_update_config +""" + +import os.path +import unittest +from gen_update_config import GenUpdateConfig + + +class GenUpdateConfigTest(unittest.TestCase): # pylint: disable=missing-docstring + + def test_ab_install_type_streaming(self): + """tests if streaming property files' offset and size are generated properly""" + config, package = self._generate_config() + property_files = config['ab_streaming_metadata']['property_files'] + self.assertEqual(len(property_files), 6) + with open(package, 'rb') as pkg_file: + for prop in property_files: + filename, offset, size = prop['filename'], prop['offset'], prop['size'] + pkg_file.seek(offset) + raw_data = pkg_file.read(size) + if filename in ['payload.bin', 'payload_metadata.bin']: + pass + elif filename == 'payload_properties.txt': + pass + elif filename == 'metadata': + self.assertEqual(raw_data.decode('ascii'), 'META-INF/COM/ANDROID/METADATA') + else: + expected_data = filename.replace('.', '-').upper() + self.assertEqual(raw_data.decode('ascii'), expected_data) + + @staticmethod + def _generate_config(): + """Generates JSON config from ota_002_package.zip.""" + ota_package = os.path.join(os.path.dirname(__file__), + '../tests/res/raw/ota_002_package.zip') + gen = GenUpdateConfig(ota_package, + 'file:///foo.bar', + GenUpdateConfig.AB_INSTALL_TYPE_STREAMING) + gen.run() + return gen.config, ota_package -- cgit v1.2.3 From 452b487f31df6457fc5eccd805b044e54c02b097 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 9 May 2018 11:52:09 -0700 Subject: screen_ui: Fix an issue when displaying wrapped text. The last character at EOL is cut when showing the prompt-for-data-wipe message on angler. Address the issue by keeping symmetrical margins based on the given offset. Test: Trigger prompt-and-wipe-data menu. No cutout character at EOL. Change-Id: Id6e8dc7815bf681435bcaf13e7bdd09cf870d95f --- screen_ui.cpp | 12 +++++++----- screen_ui.h | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/screen_ui.cpp b/screen_ui.cpp index 90e0e30af..c0fb2cfba 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -468,20 +468,22 @@ int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const std::vector& lines) const { + // Keep symmetrical margins based on the given offset (i.e. x). + size_t text_cols = (ScreenWidth() - x * 2) / char_width_; int offset = 0; for (const auto& line : lines) { size_t next_start = 0; while (next_start < line.size()) { - std::string sub = line.substr(next_start, text_cols_ + 1); - if (sub.size() <= text_cols_) { + std::string sub = line.substr(next_start, text_cols + 1); + if (sub.size() <= text_cols) { next_start += sub.size(); } else { - // Line too long and must be wrapped to text_cols_ columns. + // Line too long and must be wrapped to text_cols columns. size_t last_space = sub.find_last_of(" \t\n"); if (last_space == std::string::npos) { // No space found, just draw as much as we can. - sub.resize(text_cols_); - next_start += text_cols_; + sub.resize(text_cols); + next_start += text_cols; } else { sub.resize(last_space); next_start += last_space + 1; diff --git a/screen_ui.h b/screen_ui.h index d4923f566..293696d22 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -224,8 +224,9 @@ class ScreenRecoveryUI : public RecoveryUI { virtual void DrawTextIcon(int x, int y, GRSurface* surface) const; // Draws multiple text lines. Returns the offset it should be moving along Y-axis. int DrawTextLines(int x, int y, const std::vector& lines) const; - // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines. - // Returns the offset it should be moving along Y-axis. + // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines. It + // keeps symmetrical margins of 'x' at each end of a line. Returns the offset it should be moving + // along Y-axis. int DrawWrappedTextLines(int x, int y, const std::vector& lines) const; Icon currentIcon; -- cgit v1.2.3 From 6aa5fb0bbe8d074208648593d177553e732e6d9d Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 9 May 2018 14:28:49 -0700 Subject: updater_sample: add http header demo Added demo passing http headers to UpdateEngine#applyPayload. Bug: 79483768 Test: manually Change-Id: I3e9c812dba2066acadbcea8d07c933368806e20c Signed-off-by: Zhomart Mukhamejanov --- updater_sample/README.md | 15 +++++++++++-- updater_sample/res/raw/sample.json | 4 +++- .../android/systemupdatersample/UpdateConfig.java | 20 +++++++++++------ .../systemupdatersample/ui/MainActivity.java | 25 ++++++++++++++++++---- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/updater_sample/README.md b/updater_sample/README.md index 2c1f0ced5..7e25b070c 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -61,6 +61,17 @@ purpose only. 6. Push OTA packages to the device. +## 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. + + ## Development - [x] Create a UI with list of configs, current version, @@ -72,12 +83,12 @@ purpose only. - [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` - [ ] Add tests for `MainActivity` -- [ ] Verify system partition checksum for package - [ ] HAL compatibility check - [ ] Change partition demo +- [ ] Verify system partition checksum for package - [ ] Add non-A/B updates demo -- [ ] Add docs for passing HTTP headers to `UpdateEngine#applyPayload` ## Running tests diff --git a/updater_sample/res/raw/sample.json b/updater_sample/res/raw/sample.json index b6f4cdce6..7ac8ffab7 100644 --- a/updater_sample/res/raw/sample.json +++ b/updater_sample/res/raw/sample.json @@ -8,6 +8,7 @@ "ab_streaming_metadata": { "__": "streaming_metadata is required only for streaming update", "__property_files": "name, offset and size of files", + "__authorization": "it will be sent to OTA package server as value of HTTP header - Authorization", "property_files": [ { "__filename": "name of the file in package", @@ -17,6 +18,7 @@ "offset": 531, "size": 5012323 } - ] + ], + "authorization": "Basic my-secret-token" } } diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index 1851724ed..b08bfd0f6 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -25,6 +25,7 @@ import org.json.JSONObject; import java.io.File; import java.io.Serializable; +import java.util.Optional; /** * An update description. It will be parsed from JSON, which is intended to @@ -78,7 +79,9 @@ public class UpdateConfig implements Parcelable { p.getLong("offset"), p.getLong("size")); } - c.mAbStreamingMetadata = new StreamingMetadata(propertyFiles); + c.mAbStreamingMetadata = new StreamingMetadata( + propertyFiles, + meta.getString("authorization_token")); } c.mRawJson = json; return c; @@ -178,17 +181,23 @@ public class UpdateConfig implements Parcelable { /** defines beginning of update data in archive */ private PackageFile[] mPropertyFiles; - public StreamingMetadata() { - mPropertyFiles = new PackageFile[0]; - } + /** SystemUpdaterSample receives the authorization token from the OTA server, in addition + * to the package URL. It passes on the info to update_engine, so that the latter can + * fetch the data from the package server directly with the token. */ + private String mAuthorization; - public StreamingMetadata(PackageFile[] propertyFiles) { + public StreamingMetadata(PackageFile[] propertyFiles, String authorization) { this.mPropertyFiles = propertyFiles; + this.mAuthorization = authorization; } public PackageFile[] getPropertyFiles() { return mPropertyFiles; } + + public Optional getAuthorization() { + return Optional.of(mAuthorization); + } } /** @@ -224,7 +233,6 @@ public class UpdateConfig implements Parcelable { public long getSize() { return mSize; } - } } diff --git a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java index 359e2b10c..170825635 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -41,6 +41,7 @@ import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineStatuses; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -51,6 +52,10 @@ public class MainActivity extends Activity { private static final String TAG = "MainActivity"; + /** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */ + private static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; + private TextView mTextViewBuild; private Spinner mSpinnerConfigs; private TextView mTextViewConfigsDirHint; @@ -295,12 +300,17 @@ public class MainActivity extends Activity { .show(); return; } - updateEngineApplyPayload(payload); + updateEngineApplyPayload(payload, null); } else { Log.d(TAG, "Starting PrepareStreamingService"); PrepareStreamingService.startService(this, config, (code, payloadSpec) -> { if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) { - updateEngineApplyPayload(payloadSpec); + List extraProperties = new ArrayList<>(); + extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT); + config.getStreamingMetadata() + .getAuthorization() + .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s)); + updateEngineApplyPayload(payloadSpec, extraProperties); } else { Log.e(TAG, "PrepareStreamingService failed, result code is " + code); Toast.makeText( @@ -317,14 +327,21 @@ public class MainActivity extends Activity { * * UpdateEngine works asynchronously. This method doesn't wait until * end of the update. + * + * @param payloadSpec contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME} + * @param extraProperties additional properties to pass to {@link UpdateEngine#applyPayload} */ - private void updateEngineApplyPayload(PayloadSpec payloadSpec) { + private void updateEngineApplyPayload(PayloadSpec payloadSpec, List extraProperties) { + ArrayList properties = new ArrayList<>(payloadSpec.getProperties()); + if (extraProperties != null) { + properties.addAll(extraProperties); + } try { mUpdateEngine.applyPayload( payloadSpec.getUrl(), payloadSpec.getOffset(), payloadSpec.getSize(), - payloadSpec.getProperties().toArray(new String[0])); + properties.toArray(new String[0])); } catch (Exception e) { Log.e(TAG, "UpdateEngine failed to apply the update", e); Toast.makeText( -- cgit v1.2.3 From 551d2c3181b7c481b76a6a20dfadcd213455c9d2 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 9 May 2018 20:53:13 -0700 Subject: Device owns the RecoveryUI instance. Test: mmma -j bootable/recovery Test: Build and boot into recovery, w/ and w/o enabling quiescent mode respectively. Change-Id: I5d9bb945a6c3c9a3b96199fa0c8071a2f91339a0 --- device.h | 27 +++++++++++++-------------- recovery.cpp | 20 +++++++++----------- screen_ui.cpp | 2 +- screen_ui.h | 2 +- stub_ui.h | 2 +- ui.h | 2 +- 6 files changed, 26 insertions(+), 29 deletions(-) diff --git a/device.h b/device.h index 9510fbedb..bf148f576 100644 --- a/device.h +++ b/device.h @@ -19,6 +19,7 @@ #include +#include #include #include @@ -51,17 +52,15 @@ class Device { explicit Device(RecoveryUI* ui) : ui_(ui) {} virtual ~Device() {} - // Called to obtain the UI object that should be used to display the recovery user interface for - // this device. You should not have called Init() on the UI object already, the caller will do - // that after this method returns. + // Returns a raw pointer to the RecoveryUI object. virtual RecoveryUI* GetUI() { - return ui_; + return ui_.get(); } - // Sets the UI object to the given UI. Used to override the default UI in case initialization - // failed, or we want a stub for some reason. - virtual void SetUI(RecoveryUI* ui) { - ui_ = ui; + // Resets the UI object to the given UI. Used to override the default UI in case initialization + // failed, or we want a different UI for some reason. The device object will take the ownership. + virtual void ResetUI(RecoveryUI* ui) { + ui_.reset(ui); } // Called when recovery starts up (after the UI has been obtained and initialized and after the @@ -70,16 +69,15 @@ class Device { // Called from the main thread when recovery is at the main menu and waiting for input, and a key // is pressed. (Note that "at" the main menu does not necessarily mean the menu is visible; - // recovery will be at the main menu with it invisible after an unsuccessful operation [ie OTA - // package failure], or if recovery is started with no command.) + // recovery will be at the main menu with it invisible after an unsuccessful operation, such as + // failed to install an OTA package, or if recovery is started with no command.) // // 'key' is the code of the key just pressed. (You can call IsKeyPressed() on the RecoveryUI - // object you returned from GetUI if you want to find out if other keys are held down.) + // object you returned from GetUI() if you want to find out if other keys are held down.) // // 'visible' is true if the menu is visible. // // Returns one of the defined constants below in order to: - // // - move the menu highlight (kHighlight{Up,Down}: negative value) // - invoke the highlighted item (kInvokeItem: negative value) // - do nothing (kNoAction: negative value) @@ -87,7 +85,7 @@ class Device { virtual int HandleMenuKey(int key, bool visible); // Returns the list of menu items (a vector of strings). The menu_position passed to - // InvokeMenuItem will correspond to the indexes into this array. + // InvokeMenuItem() will correspond to the indexes into this array. virtual const std::vector& GetMenuItems(); // Performs a recovery action selected from the menu. 'menu_position' will be the index of the @@ -112,7 +110,8 @@ class Device { } private: - RecoveryUI* ui_; + // The RecoveryUI object that should be used to display the user interface for this device. + std::unique_ptr ui_; }; // The device-specific library must define this function (or the default one will be used, if there diff --git a/recovery.cpp b/recovery.cpp index 95118ffb6..b11298fb4 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -230,8 +230,8 @@ static void set_sdcard_update_bootloader_message() { // Clear the recovery command and prepare to boot a (hopefully working) system, // copy our log file to cache as well (for the system to read). This function is // idempotent: call it as many times as you like. -static void finish_recovery(Device* device) { - std::string locale = device->GetUI()->GetLocale(); +static void finish_recovery() { + std::string locale = ui->GetLocale(); // Save the locale to cache, so if recovery is next started up without a '--locale' argument // (e.g., directly from the bootloader) it will use the last-known locale. if (!locale.empty() && has_cache) { @@ -808,7 +808,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { // which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. static Device::BuiltinAction prompt_and_wait(Device* device, int status) { for (;;) { - finish_recovery(device); + finish_recovery(); switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: @@ -1173,16 +1173,14 @@ int start_recovery(int argc, char** argv) { Device* device = make_device(); if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { printf("Quiescent recovery mode.\n"); - ui = new StubRecoveryUI(); + device->ResetUI(new StubRecoveryUI()); } else { - ui = device->GetUI(); - - if (!ui->Init(locale)) { - printf("Failed to initialize UI, use stub UI instead.\n"); - ui = new StubRecoveryUI(); + if (!device->GetUI()->Init(locale)) { + printf("Failed to initialize UI; using stub UI instead.\n"); + device->ResetUI(new StubRecoveryUI()); } } - device->SetUI(ui); + ui = device->GetUI(); // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". @@ -1349,7 +1347,7 @@ int start_recovery(int argc, char** argv) { } // Save logs and clean up before rebooting or shutting down. - finish_recovery(device); + finish_recovery(); switch (after) { case Device::SHUTDOWN: diff --git a/screen_ui.cpp b/screen_ui.cpp index c0fb2cfba..f5dadf7f0 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -751,7 +751,7 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { return true; } -std::string ScreenRecoveryUI::GetLocale() { +std::string ScreenRecoveryUI::GetLocale() const { return locale_; } diff --git a/screen_ui.h b/screen_ui.h index 293696d22..2d6b621d5 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -114,7 +114,7 @@ class ScreenRecoveryUI : public RecoveryUI { explicit ScreenRecoveryUI(bool scrollable_menu); bool Init(const std::string& locale) override; - std::string GetLocale() override; + std::string GetLocale() const override; // overall recovery state ("background image") void SetBackground(Icon icon) override; diff --git a/stub_ui.h b/stub_ui.h index fddf4e71e..67c338e99 100644 --- a/stub_ui.h +++ b/stub_ui.h @@ -28,7 +28,7 @@ class StubRecoveryUI : public RecoveryUI { public: StubRecoveryUI() = default; - std::string GetLocale() override { + std::string GetLocale() const override { return ""; } void SetBackground(Icon /* icon */) override {} diff --git a/ui.h b/ui.h index f8677902e..39284268d 100644 --- a/ui.h +++ b/ui.h @@ -57,7 +57,7 @@ class RecoveryUI { // the given locale. Returns true on success. virtual bool Init(const std::string& locale); - virtual std::string GetLocale() = 0; + virtual std::string GetLocale() const = 0; // Shows a stage indicator. Called immediately after Init(). virtual void SetStage(int current, int max) = 0; -- cgit v1.2.3 From bb8a2151387148e66bdd0b0ceb5d70d9db84ba69 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 10 May 2018 12:19:16 -0700 Subject: updater_sample: fix UpdateConfig - fix UpdateConfig - fix smaple.json - fix FileDownloaderTest Test: manually Change-Id: I09d272f77c89a02f54a24cf753fdfda4ce243927 Signed-off-by: Zhomart Mukhamejanov --- updater_sample/res/raw/sample.json | 2 +- .../src/com/example/android/systemupdatersample/UpdateConfig.java | 8 ++++++-- .../android/systemupdatersample/util/FileDownloaderTest.java | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/updater_sample/res/raw/sample.json b/updater_sample/res/raw/sample.json index 7ac8ffab7..46fbfa33e 100644 --- a/updater_sample/res/raw/sample.json +++ b/updater_sample/res/raw/sample.json @@ -1,7 +1,7 @@ { "__name": "name will be visible on UI", "__url": "https:// or file:// uri to update package (zip, xz, ...)", - "__type": "NON_STREAMING (from a local file) OR STREAMING (on the fly)", + "__ab_install_type": "NON_STREAMING (from a local file) OR STREAMING (on the fly)", "name": "SAMPLE-cake-release BUILD-12345", "url": "http://foo.bar/builds/ota-001.zip", "ab_install_type": "NON_STREAMING", diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java index b08bfd0f6..9bdd8b9e8 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateConfig.java @@ -79,9 +79,13 @@ public class UpdateConfig implements Parcelable { p.getLong("offset"), p.getLong("size")); } + String authorization = null; + if (meta.has("authorization")) { + authorization = meta.getString("authorization"); + } c.mAbStreamingMetadata = new StreamingMetadata( propertyFiles, - meta.getString("authorization_token")); + authorization); } c.mRawJson = json; return c; @@ -196,7 +200,7 @@ public class UpdateConfig implements Parcelable { } public Optional getAuthorization() { - return Optional.of(mAuthorization); + return mAuthorization == null ? Optional.empty() : Optional.of(mAuthorization); } } diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java index 009610e86..a136ff0ed 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/FileDownloaderTest.java @@ -16,7 +16,7 @@ package com.example.android.systemupdatersample.util; -import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertEquals; import android.content.Context; import android.support.test.InstrumentationRegistry; -- cgit v1.2.3 From 601493e8c4efd367f1d5229f02b42137f40b8086 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 10 May 2018 12:07:47 -0700 Subject: Document the clang-format usage. `repo upload` and `git clang-format` may give slightly different results, because they may trigger different `clang-format` binaries. Document the setup to get consistent formatting results. Test: N/A Change-Id: I927dd25c7a6dd51be4812d5b3857a4b08c5a3f81 --- .clang-format | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.clang-format b/.clang-format index 0e0f4d143..4a3bd2fc3 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,33 @@ +# bootable/recovery project uses repohook to apply `clang-format` to the changed lines, with the +# local style file in `.clang-format`. This will be triggered automatically with `repo upload`. +# Alternatively, one can stage and format a change with `git clang-format` directly. +# +# $ git add +# $ git clang-format --style file +# +# Or to format a committed change. +# +# $ git clang-format --style file HEAD~1 +# +# `--style file` will pick up the local style file in `.clang-format`. This can be configured as the +# default behavior for bootable/recovery project. +# +# $ git config --local clangFormat.style file +# +# Note that `repo upload` calls the `clang-format` binary in Android repo (i.e. +# `$ANDROID_BUILD_TOP/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format`), which might +# give slightly different results from the one installed in host machine (e.g. +# `/usr/bin/clang-format`). Specifying the file with `--binary` will ensure consistent results. +# +# $ git clang-format --binary \ +# /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format +# +# Or to do one-time setup to make it default. +# +# $ git config --local clangFormat.binary \ +# /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format +# + BasedOnStyle: Google AllowShortBlocksOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty -- cgit v1.2.3 From 46a51ac4b651518e7160d83c8462ab7a556bd344 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 9 May 2018 09:53:45 -0700 Subject: updater_sample: add HAL compatibility check - Refactor PrepareStreamingService#onHandleIntent - Add PrepareStreamingService#verifyPackageCompatibility Test: on the device Bug: 79471299 Signed-off-by: Zhomart Mukhamejanov Change-Id: I1b18502f0638d66810a3f7ada582e4c7cea20cdb --- updater_sample/README.md | 2 +- .../services/PrepareStreamingService.java | 85 +++++++++++++++------- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/updater_sample/README.md b/updater_sample/README.md index 7e25b070c..95e57dbe9 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -84,8 +84,8 @@ which HTTP headers are supported. - [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) - [ ] Add tests for `MainActivity` -- [ ] HAL compatibility check - [ ] Change partition demo - [ ] Verify system partition checksum for package - [ ] Add non-A/B updates demo diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java index 840a6d607..222bb0a58 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java +++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java @@ -16,6 +16,7 @@ package com.example.android.systemupdatersample.services; +import static com.example.android.systemupdatersample.util.PackageFiles.COMPATIBILITY_ZIP_FILE_NAME; import static com.example.android.systemupdatersample.util.PackageFiles.OTA_PACKAGE_DIR; import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME; @@ -25,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; +import android.os.RecoverySystem; import android.os.ResultReceiver; import android.util.Log; @@ -36,7 +38,9 @@ import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateConfigs; import com.google.common.collect.ImmutableSet; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.Optional; @@ -119,45 +123,51 @@ public class PrepareStreamingService extends IntentService { ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER); try { - downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); - } catch (IOException e) { - Log.e(TAG, "Failed to download pre-streaming files", e); + PayloadSpec spec = execute(config); + resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec)); + } catch (Exception e) { + Log.e(TAG, "Failed to prepare streaming update", e); resultReceiver.send(RESULT_CODE_ERROR, null); - return; } + } + + /** + * 1. Downloads files for streaming updates. + * 2. Makes sure required files are present. + * 3. Checks OTA package compatibility with the device. + * 4. Constructs {@link PayloadSpec} for streaming update. + */ + private static PayloadSpec execute(UpdateConfig config) + throws IOException, PreparationFailedException { + + downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); Optional payloadBinary = UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config); if (!payloadBinary.isPresent()) { - Log.e(TAG, "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); - resultReceiver.send(RESULT_CODE_ERROR, null); - return; + throw new PreparationFailedException( + "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); } - Optional properties = - UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config); - - if (!properties.isPresent()) { - Log.e(TAG, "Failed to find " + PAYLOAD_PROPERTIES_FILE_NAME + " in config"); - resultReceiver.send(RESULT_CODE_ERROR, null); - return; + if (!UpdateConfigs.getPropertyFile(PAYLOAD_PROPERTIES_FILE_NAME, config).isPresent() + || !Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile().exists()) { + throw new IOException(PAYLOAD_PROPERTIES_FILE_NAME + " not found"); } - PayloadSpec spec; - try { - spec = PayloadSpecs.forStreaming(config.getUrl(), - payloadBinary.get().getOffset(), - payloadBinary.get().getSize(), - Paths.get(OTA_PACKAGE_DIR, properties.get().getFilename()).toFile() - ); - } catch (IOException e) { - Log.e(TAG, "PayloadSpecs failed to create PayloadSpec for streaming", e); - resultReceiver.send(RESULT_CODE_ERROR, null); - return; + File compatibilityFile = Paths.get(OTA_PACKAGE_DIR, COMPATIBILITY_ZIP_FILE_NAME).toFile(); + if (compatibilityFile.isFile()) { + Log.i(TAG, "Verifying OTA package for compatibility with the device"); + if (!verifyPackageCompatibility(compatibilityFile)) { + throw new PreparationFailedException( + "OTA package is not compatible with this device"); + } } - resultReceiver.send(RESULT_CODE_SUCCESS, CallbackResultReceiver.createBundle(spec)); + return PayloadSpecs.forStreaming(config.getUrl(), + payloadBinary.get().getOffset(), + payloadBinary.get().getSize(), + Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile()); } /** @@ -168,6 +178,10 @@ public class PrepareStreamingService extends IntentService { */ private static void downloadPreStreamingFiles(UpdateConfig config, String dir) throws IOException { + Log.d(TAG, "Deleting existing files from " + dir); + for (String file : PRE_STREAMING_FILES_SET) { + Files.deleteIfExists(Paths.get(OTA_PACKAGE_DIR, file)); + } Log.d(TAG, "Downloading files to " + dir); for (UpdateConfig.PackageFile file : config.getStreamingMetadata().getPropertyFiles()) { if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) { @@ -182,6 +196,19 @@ public class PrepareStreamingService extends IntentService { } } + /** + * @param file physical location of {@link PackageFiles#COMPATIBILITY_ZIP_FILE_NAME} + * @return true if OTA package is compatible with this device + */ + private static boolean verifyPackageCompatibility(File file) { + try { + return RecoverySystem.verifyPackageCompatibility(file); + } catch (IOException e) { + Log.e(TAG, "Failed to verify package compatibility", e); + return false; + } + } + /** * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec} * to {@link UpdateResultCallback#onReceiveResult}. @@ -213,4 +240,10 @@ public class PrepareStreamingService extends IntentService { } } + private static class PreparationFailedException extends Exception { + PreparationFailedException(String message) { + super(message); + } + } + } -- cgit v1.2.3