From e462cdffe95555a30165b784129fc6040461745f Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 25 Apr 2018 15:41:17 -0700 Subject: tools: Move to Soong. Test: `mmma -j bootable/recovery/` Test: Build and run RecoveryLocalizer on device. Change-Id: I3359223c82bd670c94ad51296cb8b357b04f5349 --- Android.mk | 1 - tools/Android.mk | 1 - tools/dumpkey/Android.bp | 27 +++++++++++++++++++++++++++ tools/dumpkey/Android.mk | 22 ---------------------- tools/recovery_l10n/Android.bp | 23 +++++++++++++++++++++++ tools/recovery_l10n/Android.mk | 13 ------------- 6 files changed, 50 insertions(+), 37 deletions(-) delete mode 100644 tools/Android.mk create mode 100644 tools/dumpkey/Android.bp delete mode 100644 tools/dumpkey/Android.mk create mode 100644 tools/recovery_l10n/Android.bp delete mode 100644 tools/recovery_l10n/Android.mk diff --git a/Android.mk b/Android.mk index 214d028da..257f776cc 100644 --- a/Android.mk +++ b/Android.mk @@ -207,6 +207,5 @@ include \ $(LOCAL_PATH)/boot_control/Android.mk \ $(LOCAL_PATH)/minui/Android.mk \ $(LOCAL_PATH)/tests/Android.mk \ - $(LOCAL_PATH)/tools/Android.mk \ $(LOCAL_PATH)/updater/Android.mk \ $(LOCAL_PATH)/updater_sample/Android.mk \ diff --git a/tools/Android.mk b/tools/Android.mk deleted file mode 100644 index 65711611c..000000000 --- a/tools/Android.mk +++ /dev/null @@ -1 +0,0 @@ -include $(all-subdir-makefiles) diff --git a/tools/dumpkey/Android.bp b/tools/dumpkey/Android.bp new file mode 100644 index 000000000..eb45e3176 --- /dev/null +++ b/tools/dumpkey/Android.bp @@ -0,0 +1,27 @@ +// 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. + +java_library_host { + name: "dumpkey", + + manifest: "DumpPublicKey.mf", + + srcs: [ + "DumpPublicKey.java", + ], + + static_libs: [ + "bouncycastle-host", + ], +} diff --git a/tools/dumpkey/Android.mk b/tools/dumpkey/Android.mk deleted file mode 100644 index 31549146d..000000000 --- a/tools/dumpkey/Android.mk +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (C) 2008 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. - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_MODULE := dumpkey -LOCAL_SRC_FILES := DumpPublicKey.java -LOCAL_JAR_MANIFEST := DumpPublicKey.mf -LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host -include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/recovery_l10n/Android.bp b/tools/recovery_l10n/Android.bp new file mode 100644 index 000000000..d0a6d4b47 --- /dev/null +++ b/tools/recovery_l10n/Android.bp @@ -0,0 +1,23 @@ +// 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. + +android_app { + name: "RecoveryLocalizer", + + sdk_version: "current", + + srcs: [ + "src/**/*.java", + ], +} diff --git a/tools/recovery_l10n/Android.mk b/tools/recovery_l10n/Android.mk deleted file mode 100644 index 7197c5c78..000000000 --- a/tools/recovery_l10n/Android.mk +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2012 Google Inc. All Rights Reserved. - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_PACKAGE_NAME := RecoveryLocalizer -LOCAL_SDK_VERSION := current -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -include $(BUILD_PACKAGE) -- cgit v1.2.3 From 26ea9591bcb90c5ac201fce7ce2f0e1e092c703e Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 9 May 2018 16:32:02 -0700 Subject: ui: Use std::thread to create input/progress threads. Test: Build and boot into recovery on walleye. Check the long press detection; `Run graphics test`. Change-Id: Ic3e9b0652fc3ff6fb3ad118df5ebb9bb4abda2cd --- screen_ui.cpp | 18 ++++++----- screen_ui.h | 6 +++- tests/unit/screen_ui_test.cpp | 12 +++++-- ui.cpp | 74 +++++++++++++++++++------------------------ ui.h | 20 ++++-------- 5 files changed, 64 insertions(+), 66 deletions(-) diff --git a/screen_ui.cpp b/screen_ui.cpp index f1b38781a..0ee0ddcec 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -32,8 +32,10 @@ #include #include +#include #include #include +#include #include #include @@ -172,6 +174,11 @@ ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) rtl_locale_(false), updateMutex(PTHREAD_MUTEX_INITIALIZER) {} +ScreenRecoveryUI::~ScreenRecoveryUI() { + progress_thread_stopped_ = true; + progress_thread_.join(); +} + GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; @@ -613,15 +620,9 @@ void ScreenRecoveryUI::update_progress_locked() { gr_flip(); } -// Keeps the progress bar updated, even when the process is otherwise busy. -void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { - reinterpret_cast(data)->ProgressThreadLoop(); - return nullptr; -} - void ScreenRecoveryUI::ProgressThreadLoop() { double interval = 1.0 / kAnimationFps; - while (true) { + while (!progress_thread_stopped_) { double start = now(); pthread_mutex_lock(&updateMutex); @@ -749,7 +750,8 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { LoadAnimation(); - pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); + // Keep the progress bar updated, even when the process is otherwise busy. + progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this); return true; } diff --git a/screen_ui.h b/screen_ui.h index c90a2cd17..c3605161a 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -20,9 +20,11 @@ #include #include +#include #include #include #include +#include #include #include "ui.h" @@ -112,6 +114,7 @@ class ScreenRecoveryUI : public RecoveryUI { ScreenRecoveryUI(); explicit ScreenRecoveryUI(bool scrollable_menu); + ~ScreenRecoveryUI() override; bool Init(const std::string& locale) override; std::string GetLocale() const override; @@ -275,7 +278,8 @@ class ScreenRecoveryUI : public RecoveryUI { // An alternate text screen, swapped with 'text_' when we're viewing a log file. char** file_viewer_text_; - pthread_t progress_thread_; + std::thread progress_thread_; + std::atomic progress_thread_stopped_{ false }; // Number of intro frames and loop frames in the animation. size_t intro_frames; diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp index 269222faa..25623074c 100644 --- a/tests/unit/screen_ui_test.cpp +++ b/tests/unit/screen_ui_test.cpp @@ -279,8 +279,6 @@ class ScreenRecoveryUITest : public ::testing::Test { testdata_dir_ = from_testdata_base(""); Paths::Get().set_resource_dir(testdata_dir_); res_set_resource_dir(testdata_dir_); - - ASSERT_TRUE(ui_->Init(kTestLocale)); } std::unique_ptr ui_; @@ -288,6 +286,7 @@ class ScreenRecoveryUITest : public ::testing::Test { }; TEST_F(ScreenRecoveryUITest, Init) { + ASSERT_TRUE(ui_->Init(kTestLocale)); ASSERT_EQ(kTestLocale, ui_->GetLocale()); ASSERT_FALSE(ui_->GetRtlLocale()); ASSERT_FALSE(ui_->IsTextVisible()); @@ -295,6 +294,7 @@ TEST_F(ScreenRecoveryUITest, Init) { } TEST_F(ScreenRecoveryUITest, ShowText) { + ASSERT_TRUE(ui_->Init(kTestLocale)); ASSERT_FALSE(ui_->IsTextVisible()); ui_->ShowText(true); ASSERT_TRUE(ui_->IsTextVisible()); @@ -308,12 +308,15 @@ TEST_F(ScreenRecoveryUITest, ShowText) { TEST_F(ScreenRecoveryUITest, RtlLocale) { ASSERT_TRUE(ui_->Init(kTestRtlLocale)); ASSERT_TRUE(ui_->GetRtlLocale()); +} +TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) { ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix)); ASSERT_TRUE(ui_->GetRtlLocale()); } TEST_F(ScreenRecoveryUITest, ShowMenu) { + ASSERT_TRUE(ui_->Init(kTestLocale)); ui_->SetKeyBuffer({ KeyCode::UP, KeyCode::DOWN, @@ -339,6 +342,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu) { } TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) { + ASSERT_TRUE(ui_->Init(kTestLocale)); ui_->SetKeyBuffer({ KeyCode::MAGIC, }); @@ -349,6 +353,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) { } TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) { + ASSERT_TRUE(ui_->Init(kTestLocale)); ui_->SetKeyBuffer({ KeyCode::TIMEOUT, }); @@ -356,6 +361,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) { } TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) { + ASSERT_TRUE(ui_->Init(kTestLocale)); ui_->ShowText(true); ui_->ShowText(false); ASSERT_TRUE(ui_->WasTextEverVisible()); @@ -371,6 +377,7 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) { } TEST_F(ScreenRecoveryUITest, LoadAnimation) { + ASSERT_TRUE(ui_->Init(kTestLocale)); // Make a few copies of loop00000.png from testdata. std::string image_data; ASSERT_TRUE(android::base::ReadFileToString(testdata_dir_ + "/loop00000.png", &image_data)); @@ -398,6 +405,7 @@ TEST_F(ScreenRecoveryUITest, LoadAnimation) { } TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) { + ASSERT_TRUE(ui_->Init(kTestLocale)); TemporaryDir resource_dir; Paths::Get().set_resource_dir(resource_dir.path); ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), ""); diff --git a/ui.cpp b/ui.cpp index 8983d7692..dbe77ae8c 100644 --- a/ui.cpp +++ b/ui.cpp @@ -20,29 +20,30 @@ #include #include #include -#include #include #include #include -#include #include #include #include #include +#include #include #include +#include #include #include #include #include -#include -#include "device.h" +#include "minui/minui.h" #include "otautil/sysutil.h" #include "roots.h" +using namespace std::chrono_literals; + static constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120; static constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness"; static constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness"; @@ -78,6 +79,12 @@ RecoveryUI::RecoveryUI() memset(key_pressed, 0, sizeof(key_pressed)); } +RecoveryUI::~RecoveryUI() { + ev_exit(); + input_thread_stopped_ = true; + input_thread_.join(); +} + void RecoveryUI::OnKeyDetected(int key_code) { if (key_code == KEY_POWER) { has_power_key = true; @@ -90,16 +97,6 @@ void RecoveryUI::OnKeyDetected(int key_code) { } } -// Reads input events, handles special hot keys, and adds to the key queue. -static void* InputThreadLoop(void*) { - while (true) { - if (!ev_wait(-1)) { - ev_dispatch(); - } - } - return nullptr; -} - bool RecoveryUI::InitScreensaver() { // Disabled. if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) { @@ -166,7 +163,15 @@ bool RecoveryUI::Init(const std::string& /* locale */) { LOG(INFO) << "Screensaver disabled"; } - pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); + // Create a separate thread that handles input events. + input_thread_ = std::thread([this]() { + while (!this->input_thread_stopped_) { + if (!ev_wait(500)) { + ev_dispatch(); + } + } + }); + return true; } @@ -323,22 +328,18 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { return 0; } -// Process a key-up or -down event. A key is "registered" when it is -// pressed and then released, with no other keypresses or releases in -// between. Registered keys are passed to CheckKey() to see if it -// should trigger a visibility toggle, an immediate reboot, or be -// queued to be processed next time the foreground thread wants a key -// (eg, for the menu). +// Processes a key-up or -down event. A key is "registered" when it is pressed and then released, +// with no other keypresses or releases in between. Registered keys are passed to CheckKey() to +// see if it should trigger a visibility toggle, an immediate reboot, or be queued to be processed +// next time the foreground thread wants a key (eg, for the menu). // -// We also keep track of which keys are currently down so that -// CheckKey can call IsKeyPressed to see what other keys are held when -// a key is registered. +// We also keep track of which keys are currently down so that CheckKey() can call IsKeyPressed() +// to see what other keys are held when a key is registered. // // updown == 1 for key down events; 0 for key up events void RecoveryUI::ProcessKey(int key_code, int updown) { bool register_key = false; bool long_press = false; - bool reboot_enabled; pthread_mutex_lock(&key_queue_mutex); key_pressed[key_code] = updown; @@ -346,13 +347,9 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { ++key_down_count; key_last_down = key_code; key_long_press = false; - key_timer_t* info = new key_timer_t; - info->ui = this; - info->key_code = key_code; - info->count = key_down_count; - pthread_t thread; - pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info); - pthread_detach(thread); + + std::thread time_key_thread(&RecoveryUI::TimeKey, this, key_code, key_down_count); + time_key_thread.detach(); } else { if (key_last_down == key_code) { long_press = key_long_press; @@ -360,7 +357,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { } key_last_down = -1; } - reboot_enabled = enable_reboot; + bool reboot_enabled = enable_reboot; pthread_mutex_unlock(&key_queue_mutex); if (register_key) { @@ -388,15 +385,8 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { } } -void* RecoveryUI::time_key_helper(void* cookie) { - key_timer_t* info = static_cast(cookie); - info->ui->time_key(info->key_code, info->count); - delete info; - return nullptr; -} - -void RecoveryUI::time_key(int key_code, int count) { - usleep(750000); // 750 ms == "long" +void RecoveryUI::TimeKey(int key_code, int count) { + std::this_thread::sleep_for(750ms); // 750 ms == "long" bool long_press = false; pthread_mutex_lock(&key_queue_mutex); if (key_last_down == key_code && key_down_count == count) { diff --git a/ui.h b/ui.h index a74b14f85..75390d83c 100644 --- a/ui.h +++ b/ui.h @@ -17,12 +17,13 @@ #ifndef RECOVERY_UI_H #define RECOVERY_UI_H -#include +#include // KEY_MAX #include -#include +#include #include #include +#include #include // Abstract class for controlling the user interface during recovery. @@ -51,7 +52,7 @@ class RecoveryUI { RecoveryUI(); - virtual ~RecoveryUI() {} + virtual ~RecoveryUI(); // Initializes the object; called before anything else. UI texts will be initialized according to // the given locale. Returns true on success. @@ -172,12 +173,6 @@ class RecoveryUI { OFF }; - struct key_timer_t { - RecoveryUI* ui; - int key_code; - int count; - }; - // The sensitivity when detecting a swipe. const int kTouchLowThreshold; const int kTouchHighThreshold; @@ -186,12 +181,10 @@ class RecoveryUI { void OnTouchDetected(int dx, int dy); int OnInputEvent(int fd, uint32_t epevents); void ProcessKey(int key_code, int updown); + void TimeKey(int key_code, int count); bool IsUsbConnected(); - static void* time_key_helper(void* cookie); - void time_key(int key_code, int count); - bool InitScreensaver(); // Key event input queue @@ -223,7 +216,8 @@ class RecoveryUI { bool touch_swiping_; bool is_bootreason_recovery_ui_; - pthread_t input_thread_; + std::thread input_thread_; + std::atomic input_thread_stopped_{ false }; ScreensaverState screensaver_state_; -- cgit v1.2.3 From c3901231cec715337ed19de78a1af81ad3289e34 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 21 May 2018 16:05:56 -0700 Subject: updater: Add Commmand class to manage BBOTA commands. Move the commands map parsing out of PerformBlockImageUpdate(), as this can be done more easily by the caller. The goal (not done in this CL) is to decouple command parsing logic from the performers. This allows (a) focusing on the command logic in the performer; and (b) extending BBOTA commands syntax separately. Test: Run recovery_unit_test and recovery_component_test. Change-Id: Ife202398a7660b152d84a3ba17b90f93d19c55f2 --- tests/Android.mk | 1 + tests/unit/commands_test.cpp | 37 ++++++++++++ updater/Android.mk | 1 + updater/blockimg.cpp | 117 +++++++++++++++++-------------------- updater/commands.cpp | 43 ++++++++++++++ updater/include/private/commands.h | 35 +++++++++++ 6 files changed, 171 insertions(+), 63 deletions(-) create mode 100644 tests/unit/commands_test.cpp create mode 100644 updater/commands.cpp create mode 100644 updater/include/private/commands.h diff --git a/tests/Android.mk b/tests/Android.mk index 853ca273b..ff420668a 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -37,6 +37,7 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SRC_FILES := \ unit/asn1_decoder_test.cpp \ + unit/commands_test.cpp \ unit/dirutil_test.cpp \ unit/locale_test.cpp \ unit/rangeset_test.cpp \ diff --git a/tests/unit/commands_test.cpp b/tests/unit/commands_test.cpp new file mode 100644 index 000000000..18aa471ab --- /dev/null +++ b/tests/unit/commands_test.cpp @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#include + +#include + +#include "private/commands.h" + +TEST(CommandsTest, ParseType) { + ASSERT_EQ(Command::Type::ZERO, Command::ParseType("zero")); + ASSERT_EQ(Command::Type::NEW, Command::ParseType("new")); + ASSERT_EQ(Command::Type::ERASE, Command::ParseType("erase")); + ASSERT_EQ(Command::Type::MOVE, Command::ParseType("move")); + ASSERT_EQ(Command::Type::BSDIFF, Command::ParseType("bsdiff")); + ASSERT_EQ(Command::Type::IMGDIFF, Command::ParseType("imgdiff")); + ASSERT_EQ(Command::Type::STASH, Command::ParseType("stash")); + ASSERT_EQ(Command::Type::FREE, Command::ParseType("free")); +} + +TEST(CommandsTest, ParseType_InvalidCommand) { + ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo")); + ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar")); +} diff --git a/updater/Android.mk b/updater/Android.mk index 476266400..46c56f4a0 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -56,6 +56,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := libupdater LOCAL_SRC_FILES := \ + commands.cpp \ install.cpp \ blockimg.cpp diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 4a70b98a1..5d6da6cb3 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -57,6 +57,7 @@ #include "otautil/paths.h" #include "otautil/print_sha1.h" #include "otautil/rangeset.h" +#include "private/commands.h" #include "updater/install.h" #include "updater/updater.h" @@ -546,8 +547,8 @@ static int WriteBlocks(const RangeSet& tgt, const std::vector& buffer, struct CommandParameters { std::vector tokens; size_t cpos; - const char* cmdname; - const char* cmdline; + std::string cmdname; + std::string cmdline; std::string freestash; std::string stashbase; bool canwrite; @@ -1496,23 +1497,13 @@ static int PerformCommandErase(CommandParameters& params) { return 0; } -// Definitions for transfer list command functions -typedef int (*CommandFunction)(CommandParameters&); +using CommandFunction = std::function; -struct Command { - const char* name; - CommandFunction f; -}; - -// args: -// - block device (or file) to modify in-place -// - transfer list (blob) -// - new data stream (filename within package.zip) -// - patch stream (filename within package.zip, must be uncompressed) +using CommandMap = std::unordered_map; static Value* PerformBlockImageUpdate(const char* name, State* state, const std::vector>& argv, - const Command* commands, size_t cmdcount, bool dryrun) { + const CommandMap& command_map, bool dryrun) { CommandParameters params = {}; params.canwrite = !dryrun; @@ -1532,6 +1523,11 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return nullptr; } + // args: + // - block device (or file) to modify in-place + // - transfer list (blob) + // - new data stream (filename within package.zip) + // - patch stream (filename within package.zip, must be uncompressed) const std::unique_ptr& blockdev_filename = args[0]; const std::unique_ptr& transfer_list_value = args[1]; const std::unique_ptr& new_data_fn = args[2]; @@ -1707,16 +1703,6 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, skip_executed_command = false; } - // Build a map of the available commands - std::unordered_map cmd_map; - for (size_t i = 0; i < cmdcount; ++i) { - if (cmd_map.find(commands[i].name) != cmd_map.end()) { - LOG(ERROR) << "Error: command [" << commands[i].name << "] already exists in the cmd map."; - return StringValue(""); - } - cmd_map[commands[i].name] = &commands[i]; - } - int rc = -1; static constexpr size_t kTransferListHeaderLines = 4; @@ -1728,36 +1714,35 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, size_t cmdindex = i - kTransferListHeaderLines; params.tokens = android::base::Split(line, " "); params.cpos = 0; - params.cmdname = params.tokens[params.cpos++].c_str(); - params.cmdline = line.c_str(); + params.cmdname = params.tokens[params.cpos++]; + params.cmdline = line; params.target_verified = false; - if (cmd_map.find(params.cmdname) == cmd_map.end()) { + Command::Type cmd_type = Command::ParseType(params.cmdname); + if (cmd_type == Command::Type::LAST) { LOG(ERROR) << "unexpected command [" << params.cmdname << "]"; goto pbiudone; } - const Command* cmd = cmd_map[params.cmdname]; + const CommandFunction& performer = command_map.at(cmd_type); // Skip the command if we explicitly set the corresponding function pointer to nullptr, e.g. // "erase" during block_image_verify. - if (cmd->f == nullptr) { + if (performer == nullptr) { LOG(DEBUG) << "skip executing command [" << line << "]"; continue; } - std::string cmdname = std::string(params.cmdname); - // Skip all commands before the saved last command index when resuming an update, except for // "new" command. Because new commands read in the data sequentially. if (params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index && - cmdname != "new") { + cmd_type != Command::Type::NEW) { LOG(INFO) << "Skipping already executed command: " << cmdindex << ", last executed command for previous update: " << saved_last_command_index; continue; } - if (cmd->f(params) == -1) { + if (performer(params) == -1) { LOG(ERROR) << "failed to execute command [" << line << "]"; goto pbiudone; } @@ -1767,7 +1752,8 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, // that we will resume the update from the first command in the transfer list. if (!params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index) { // TODO(xunchang) check that the cmdline of the saved index is correct. - if ((cmdname == "move" || cmdname == "bsdiff" || cmdname == "imgdiff") && + if ((cmd_type == Command::Type::MOVE || cmd_type == Command::Type::BSDIFF || + cmd_type == Command::Type::IMGDIFF) && !params.target_verified) { LOG(WARNING) << "Previously executed command " << saved_last_command_index << ": " << params.cmdline << " doesn't produce expected target blocks."; @@ -1775,6 +1761,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, DeleteLastCommandFile(); } } + if (params.canwrite) { if (ota_fsync(params.fd) == -1) { failure_type = kFsyncFailure; @@ -1911,38 +1898,42 @@ pbiudone: */ Value* BlockImageVerifyFn(const char* name, State* state, const std::vector>& argv) { - // Commands which are not tested are set to nullptr to skip them completely - const Command commands[] = { - { "bsdiff", PerformCommandDiff }, - { "erase", nullptr }, - { "free", PerformCommandFree }, - { "imgdiff", PerformCommandDiff }, - { "move", PerformCommandMove }, - { "new", nullptr }, - { "stash", PerformCommandStash }, - { "zero", nullptr } - }; - - // Perform a dry run without writing to test if an update can proceed - return PerformBlockImageUpdate(name, state, argv, commands, - sizeof(commands) / sizeof(commands[0]), true); + // Commands which are not allowed are set to nullptr to skip them completely. + const CommandMap command_map{ + // clang-format off + { Command::Type::BSDIFF, PerformCommandDiff }, + { Command::Type::ERASE, nullptr }, + { Command::Type::FREE, PerformCommandFree }, + { Command::Type::IMGDIFF, PerformCommandDiff }, + { Command::Type::MOVE, PerformCommandMove }, + { Command::Type::NEW, nullptr }, + { Command::Type::STASH, PerformCommandStash }, + { Command::Type::ZERO, nullptr }, + // clang-format on + }; + CHECK_EQ(static_cast(Command::Type::LAST), command_map.size()); + + // Perform a dry run without writing to test if an update can proceed. + return PerformBlockImageUpdate(name, state, argv, command_map, true); } Value* BlockImageUpdateFn(const char* name, State* state, const std::vector>& argv) { - const Command commands[] = { - { "bsdiff", PerformCommandDiff }, - { "erase", PerformCommandErase }, - { "free", PerformCommandFree }, - { "imgdiff", PerformCommandDiff }, - { "move", PerformCommandMove }, - { "new", PerformCommandNew }, - { "stash", PerformCommandStash }, - { "zero", PerformCommandZero } - }; - - return PerformBlockImageUpdate(name, state, argv, commands, - sizeof(commands) / sizeof(commands[0]), false); + const CommandMap command_map{ + // clang-format off + { Command::Type::BSDIFF, PerformCommandDiff }, + { Command::Type::ERASE, PerformCommandErase }, + { Command::Type::FREE, PerformCommandFree }, + { Command::Type::IMGDIFF, PerformCommandDiff }, + { Command::Type::MOVE, PerformCommandMove }, + { Command::Type::NEW, PerformCommandNew }, + { Command::Type::STASH, PerformCommandStash }, + { Command::Type::ZERO, PerformCommandZero }, + // clang-format on + }; + CHECK_EQ(static_cast(Command::Type::LAST), command_map.size()); + + return PerformBlockImageUpdate(name, state, argv, command_map, false); } Value* RangeSha1Fn(const char* name, State* state, const std::vector>& argv) { diff --git a/updater/commands.cpp b/updater/commands.cpp new file mode 100644 index 000000000..f798c6a73 --- /dev/null +++ b/updater/commands.cpp @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include "private/commands.h" + +#include + +#include + +Command::Type Command::ParseType(const std::string& type_str) { + if (type_str == "zero") { + return Type::ZERO; + } else if (type_str == "new") { + return Type::NEW; + } else if (type_str == "erase") { + return Type::ERASE; + } else if (type_str == "move") { + return Type::MOVE; + } else if (type_str == "bsdiff") { + return Type::BSDIFF; + } else if (type_str == "imgdiff") { + return Type::IMGDIFF; + } else if (type_str == "stash") { + return Type::STASH; + } else if (type_str == "free") { + return Type::FREE; + } + LOG(ERROR) << "Invalid type: " << type_str; + return Type::LAST; +}; diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h new file mode 100644 index 000000000..b36000072 --- /dev/null +++ b/updater/include/private/commands.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +#include + +struct Command { + enum class Type { + ZERO, + NEW, + ERASE, + MOVE, + BSDIFF, + IMGDIFF, + STASH, + FREE, + LAST, // Not a valid type. + }; + + static Type ParseType(const std::string& type_str); +}; -- cgit v1.2.3 From b34f7ea9a5318ecb917858e978ef984767f5f775 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 25 May 2018 17:00:11 -0700 Subject: updater_sample: add UpdateData This class allows easily passing update data to apply payload, re-applying the updata data, and in the future persisting. Test: on the device Change-Id: Ie01c5f3384c421bf1180122f27811c644179e3f5 Signed-off-by: Zhomart Mukhamejanov --- .../android/systemupdatersample/UpdateManager.java | 150 +++++++++++++++++---- 1 file changed, 121 insertions(+), 29 deletions(-) diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java index c370a4eb5..bf673c2eb 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java @@ -26,10 +26,13 @@ import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineProperties; import com.example.android.systemupdatersample.util.UpdaterStates; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.AtomicDouble; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -56,15 +59,12 @@ public class UpdateManager { new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE); private AtomicInteger mEngineErrorCode = new AtomicInteger(UpdateEngineErrorCodes.UNKNOWN); private AtomicDouble mProgress = new AtomicDouble(0); - private AtomicInteger mState = new AtomicInteger(UpdaterStates.IDLE); - private final UpdateManager.UpdateEngineCallbackImpl - mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl(); - - private PayloadSpec mLastPayloadSpec; private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true); + private UpdateData mLastUpdateData = null; + private IntConsumer mOnStateChangeCallback = null; private IntConsumer mOnEngineStatusUpdateCallback = null; private DoubleConsumer mOnProgressUpdateCallback = null; @@ -72,6 +72,9 @@ public class UpdateManager { private final Object mLock = new Object(); + private final UpdateManager.UpdateEngineCallbackImpl + mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl(); + public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) { this.mUpdateEngine = updateEngine; this.mPayloadSpecs = payloadSpecs; @@ -240,6 +243,11 @@ public class UpdateManager { mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN); setUpdaterState(UpdaterStates.RUNNING); + synchronized (mLock) { + // Cleaning up previous update data. + mLastUpdateData = null; + } + if (!config.getAbConfig().getForceSwitchSlot()) { mManualSwitchSlotRequired.set(true); } else { @@ -254,30 +262,32 @@ public class UpdateManager { } private void applyAbNonStreamingUpdate(UpdateConfig config) { - List extraProperties = prepareExtraProperties(config); + UpdateData.Builder builder = UpdateData.builder() + .setExtraProperties(prepareExtraProperties(config)); - PayloadSpec payload; try { - payload = mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()); + builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile())); } catch (IOException e) { Log.e(TAG, "Error creating payload spec", e); setUpdaterState(UpdaterStates.ERROR); return; } - updateEngineApplyPayload(payload, extraProperties); + updateEngineApplyPayload(builder.build()); } private void applyAbStreamingUpdate(Context context, UpdateConfig config) { - List extraProperties = prepareExtraProperties(config); + UpdateData.Builder builder = UpdateData.builder() + .setExtraProperties(prepareExtraProperties(config)); Log.d(TAG, "Starting PrepareStreamingService"); PrepareStreamingService.startService(context, config, (code, payloadSpec) -> { if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) { - extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT); + builder.setPayload(payloadSpec); + builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT); config.getStreamingMetadata() .getAuthorization() - .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s)); - updateEngineApplyPayload(payloadSpec, extraProperties); + .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s)); + updateEngineApplyPayload(builder.build()); } else { Log.e(TAG, "PrepareStreamingService failed, result code is " + code); setUpdaterState(UpdaterStates.ERROR); @@ -305,22 +315,20 @@ public class UpdateManager { *

It's possible that the update engine throws a generic error, such as upon seeing invalid * payload properties (which come from OTA packages), or failing to set up the network * with the given id.

- * - * @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, List extraProperties) { - mLastPayloadSpec = payloadSpec; - - ArrayList properties = new ArrayList<>(payloadSpec.getProperties()); - if (extraProperties != null) { - properties.addAll(extraProperties); + private void updateEngineApplyPayload(UpdateData update) { + synchronized (mLock) { + mLastUpdateData = update; } + + ArrayList properties = new ArrayList<>(update.getPayload().getProperties()); + properties.addAll(update.getExtraProperties()); + try { mUpdateEngine.applyPayload( - payloadSpec.getUrl(), - payloadSpec.getOffset(), - payloadSpec.getSize(), + update.getPayload().getUrl(), + update.getPayload().getOffset(), + update.getPayload().getSize(), properties.toArray(new String[0])); } catch (Exception e) { Log.e(TAG, "UpdateEngine failed to apply the update", e); @@ -328,6 +336,19 @@ public class UpdateManager { } } + private void updateEngineReApplyPayload() { + UpdateData lastUpdate; + synchronized (mLock) { + // mLastPayloadSpec might be empty in some cases. + // But to make this sample app simple, we will not handle it. + Preconditions.checkArgument( + mLastUpdateData != null, + "mLastUpdateData must be present."); + lastUpdate = mLastUpdateData; + } + updateEngineApplyPayload(lastUpdate); + } + /** * Sets the new slot that has the updated partitions as the active slot, * which device will boot into next time. @@ -342,12 +363,20 @@ public class UpdateManager { */ public void setSwitchSlotOnReboot() { Log.d(TAG, "setSwitchSlotOnReboot invoked"); - List extraProperties = new ArrayList<>(); + UpdateData.Builder builder; + synchronized (mLock) { + // To make sample app simple, we don't handle it. + Preconditions.checkArgument( + mLastUpdateData != null, + "mLastUpdateData must be present."); + builder = mLastUpdateData.toBuilder(); + } // PROPERTY_SKIP_POST_INSTALL should be passed on to skip post-installation hooks. - extraProperties.add(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL); - // It sets property SWITCH_SLOT_ON_REBOOT=1 by default. + builder.setExtraProperties( + Collections.singletonList(UpdateEngineProperties.PROPERTY_SKIP_POST_INSTALL)); + // UpdateEngine sets property SWITCH_SLOT_ON_REBOOT=1 by default. // HTTP headers are not required, UpdateEngine is not expected to stream payload. - updateEngineApplyPayload(mLastPayloadSpec, extraProperties); + updateEngineApplyPayload(builder.build()); } private void onStatusUpdate(int status, float progress) { @@ -391,4 +420,67 @@ public class UpdateManager { } } + /** + * + * Contains update data - PayloadSpec and extra properties list. + * + *

{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}. + * {@code mExtraProperties} is a list of additional properties to pass to + * {@link UpdateEngine#applyPayload}.

+ */ + private static class UpdateData { + private final PayloadSpec mPayload; + private final ImmutableList mExtraProperties; + + public static Builder builder() { + return new Builder(); + } + + UpdateData(Builder builder) { + this.mPayload = builder.mPayload; + this.mExtraProperties = ImmutableList.copyOf(builder.mExtraProperties); + } + + public PayloadSpec getPayload() { + return mPayload; + } + + public ImmutableList getExtraProperties() { + return mExtraProperties; + } + + public Builder toBuilder() { + return builder() + .setPayload(mPayload) + .setExtraProperties(mExtraProperties); + } + + static class Builder { + private PayloadSpec mPayload; + private List mExtraProperties; + + public Builder setPayload(PayloadSpec payload) { + this.mPayload = payload; + return this; + } + + public Builder setExtraProperties(List extraProperties) { + this.mExtraProperties = new ArrayList<>(extraProperties); + return this; + } + + public Builder addExtraProperty(String property) { + if (this.mExtraProperties == null) { + this.mExtraProperties = new ArrayList<>(); + } + this.mExtraProperties.add(property); + return this; + } + + public UpdateData build() { + return new UpdateData(this); + } + } + } + } -- cgit v1.2.3 From 674aa6c6110717fdeba3f50a7a6d00dd3eb03886 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 25 May 2018 17:00:11 -0700 Subject: updater_sample: add UpdaterState - Add UpdaterState - atomic class, handles proper state changes. - Remove util.UpdaterStates. Test: compiled and ran on the device Change-Id: I7fa87bbf09f8289632e8de1f26654365f4891700 Signed-off-by: Zhomart Mukhamejanov --- .../android/systemupdatersample/UpdateManager.java | 33 ++++--- .../android/systemupdatersample/UpdaterState.java | 103 +++++++++++++++++++++ .../systemupdatersample/ui/MainActivity.java | 10 +- .../systemupdatersample/util/UpdaterStates.java | 50 ---------- 4 files changed, 128 insertions(+), 68 deletions(-) create mode 100644 updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java delete mode 100644 updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java index bf673c2eb..c4c8c9c27 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java @@ -25,7 +25,6 @@ import com.example.android.systemupdatersample.services.PrepareStreamingService; import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineProperties; -import com.example.android.systemupdatersample.util.UpdaterStates; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.AtomicDouble; @@ -59,7 +58,7 @@ public class UpdateManager { new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE); private AtomicInteger mEngineErrorCode = new AtomicInteger(UpdateEngineErrorCodes.UNKNOWN); private AtomicDouble mProgress = new AtomicDouble(0); - private AtomicInteger mState = new AtomicInteger(UpdaterStates.IDLE); + private UpdaterState mUpdaterState = new UpdaterState(UpdaterState.IDLE); private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true); @@ -111,7 +110,7 @@ public class UpdateManager { /** * Sets SystemUpdaterSample app state change callback. Value of {@code state} will be one - * of the values from {@link UpdaterStates}. + * of the values from {@link UpdaterState}. * * @param onStateChangeCallback a callback with parameter {@code state}. */ @@ -193,8 +192,14 @@ public class UpdateManager { * it also notifies {@link this.mOnStateChangeCallback}. */ private void setUpdaterState(int updaterState) { - int previousState = mState.get(); - mState.set(updaterState); + int previousState = mUpdaterState.get(); + try { + mUpdaterState.set(updaterState); + } catch (UpdaterState.InvalidTransitionException e) { + // Note: invalid state transitions should be handled properly, + // but to make sample app simple, we just throw runtime exception. + throw new RuntimeException("Can't set state " + updaterState, e); + } if (previousState != updaterState) { getOnStateChangeCallback().ifPresent(callback -> callback.accept(updaterState)); } @@ -211,7 +216,7 @@ public class UpdateManager { public void cancelRunningUpdate() { try { mUpdateEngine.cancel(); - setUpdaterState(UpdaterStates.IDLE); + setUpdaterState(UpdaterState.IDLE); } catch (Exception e) { Log.w(TAG, "UpdateEngine failed to stop the ongoing update", e); } @@ -227,7 +232,7 @@ public class UpdateManager { public void resetUpdate() { try { mUpdateEngine.resetStatus(); - setUpdaterState(UpdaterStates.IDLE); + setUpdaterState(UpdaterState.IDLE); } catch (Exception e) { Log.w(TAG, "UpdateEngine failed to reset the update", e); } @@ -241,7 +246,7 @@ public class UpdateManager { */ public void applyUpdate(Context context, UpdateConfig config) { mEngineErrorCode.set(UpdateEngineErrorCodes.UNKNOWN); - setUpdaterState(UpdaterStates.RUNNING); + setUpdaterState(UpdaterState.RUNNING); synchronized (mLock) { // Cleaning up previous update data. @@ -269,7 +274,7 @@ public class UpdateManager { builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile())); } catch (IOException e) { Log.e(TAG, "Error creating payload spec", e); - setUpdaterState(UpdaterStates.ERROR); + setUpdaterState(UpdaterState.ERROR); return; } updateEngineApplyPayload(builder.build()); @@ -290,7 +295,7 @@ public class UpdateManager { updateEngineApplyPayload(builder.build()); } else { Log.e(TAG, "PrepareStreamingService failed, result code is " + code); - setUpdaterState(UpdaterStates.ERROR); + setUpdaterState(UpdaterState.ERROR); } }); } @@ -332,7 +337,7 @@ public class UpdateManager { properties.toArray(new String[0])); } catch (Exception e) { Log.e(TAG, "UpdateEngine failed to apply the update", e); - setUpdaterState(UpdaterStates.ERROR); + setUpdaterState(UpdaterState.ERROR); } } @@ -396,9 +401,11 @@ public class UpdateManager { mEngineErrorCode.set(errorCode); if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS || errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) { - setUpdaterState(UpdaterStates.FINISHED); + setUpdaterState(isManualSwitchSlotRequired() + ? UpdaterState.SLOT_SWITCH_REQUIRED + : UpdaterState.REBOOT_REQUIRED); } else if (errorCode != UpdateEngineErrorCodes.USER_CANCELLED) { - setUpdaterState(UpdaterStates.ERROR); + setUpdaterState(UpdaterState.ERROR); } getOnEngineCompleteCallback() diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java b/updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java new file mode 100644 index 000000000..36a90982e --- /dev/null +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdaterState.java @@ -0,0 +1,103 @@ +/* + * 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; + +import android.util.SparseArray; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Controls updater state. + */ +public class UpdaterState { + + public static final int IDLE = 0; + public static final int ERROR = 1; + public static final int RUNNING = 2; + public static final int PAUSED = 3; + public static final int SLOT_SWITCH_REQUIRED = 4; + public static final int REBOOT_REQUIRED = 5; + + private static final SparseArray STATE_MAP = new SparseArray<>(); + + static { + STATE_MAP.put(0, "IDLE"); + STATE_MAP.put(1, "ERROR"); + STATE_MAP.put(2, "RUNNING"); + STATE_MAP.put(3, "PAUSED"); + STATE_MAP.put(4, "SLOT_SWITCH_REQUIRED"); + STATE_MAP.put(5, "REBOOT_REQUIRED"); + } + + /** + * Allowed state transitions. It's a map: key is a state, value is a set of states that + * are allowed to transition to from key. + */ + private static final ImmutableMap> TRANSITIONS = + ImmutableMap.of( + IDLE, ImmutableSet.of(RUNNING), + RUNNING, ImmutableSet.of(ERROR, PAUSED, REBOOT_REQUIRED, SLOT_SWITCH_REQUIRED), + PAUSED, ImmutableSet.of(RUNNING), + SLOT_SWITCH_REQUIRED, ImmutableSet.of(ERROR) + ); + + private AtomicInteger mState; + + public UpdaterState(int state) { + this.mState = new AtomicInteger(state); + } + + /** + * Returns updater state. + */ + public int get() { + return mState.get(); + } + + /** + * Sets the updater state. + * + * @throws InvalidTransitionException if transition is not allowed. + */ + public void set(int newState) throws InvalidTransitionException { + int oldState = mState.get(); + if (!TRANSITIONS.get(oldState).contains(newState)) { + throw new InvalidTransitionException( + "Can't transition from " + oldState + " to " + newState); + } + mState.set(newState); + } + + /** + * Converts status code to status name. + */ + public static String getStateText(int state) { + return STATE_MAP.get(state); + } + + /** + * Defines invalid state transition exception. + */ + public static class InvalidTransitionException extends Exception { + public InvalidTransitionException(String msg) { + super(msg); + } + } +} 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 9983fe316..0b571cc81 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -33,11 +33,11 @@ import android.widget.TextView; import com.example.android.systemupdatersample.R; import com.example.android.systemupdatersample.UpdateConfig; import com.example.android.systemupdatersample.UpdateManager; +import com.example.android.systemupdatersample.UpdaterState; import com.example.android.systemupdatersample.util.PayloadSpecs; import com.example.android.systemupdatersample.util.UpdateConfigs; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineStatuses; -import com.example.android.systemupdatersample.util.UpdaterStates; import java.util.List; @@ -192,7 +192,7 @@ public class MainActivity extends Activity { /** * Invoked when SystemUpdaterSample app state changes. * Value of {@code state} will be one of the - * values from {@link UpdaterStates}. + * values from {@link UpdaterState}. */ private void onUpdaterStateChange(int state) { Log.i(TAG, "onUpdaterStateChange invoked state=" + state); @@ -233,8 +233,8 @@ public class MainActivity extends Activity { runOnUiThread(() -> { Log.i(TAG, "Completed - errorCode=" - + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode - + " " + completionState); + + UpdateEngineErrorCodes.getCodeName(errorCode) + "/" + errorCode + + " " + completionState); setUiEngineErrorCode(errorCode); if (errorCode == UpdateEngineErrorCodes.UPDATED_BUT_NOT_ACTIVE) { // if update was successfully applied. @@ -323,7 +323,7 @@ public class MainActivity extends Activity { * @param state updater sample state */ private void setUiUpdaterState(int state) { - String stateText = UpdaterStates.getStateText(state); + String stateText = UpdaterState.getStateText(state); mTextViewUpdaterState.setText(stateText + "/" + state); } diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java b/updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java deleted file mode 100644 index fc20a7941..000000000 --- a/updater_sample/src/com/example/android/systemupdatersample/util/UpdaterStates.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.util; - -import android.util.SparseArray; - -/** - * SystemUpdaterSample app state. - */ -public class UpdaterStates { - - public static final int IDLE = 0; - public static final int ERROR = 1; - public static final int RUNNING = 2; - public static final int PAUSED = 3; - public static final int FINISHED = 4; - - private static final SparseArray STATE_MAP = new SparseArray<>(); - - static { - STATE_MAP.put(0, "IDLE"); - STATE_MAP.put(1, "ERROR"); - STATE_MAP.put(2, "RUNNING"); - STATE_MAP.put(3, "PAUSED"); - STATE_MAP.put(4, "FINISHED"); - } - - /** - * converts status code to status name - */ - public static String getStateText(int state) { - return STATE_MAP.get(state); - } - - private UpdaterStates() {} -} -- cgit v1.2.3 From b31f9ce6d150a264f584d9b38a54da0723fc249c Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Mon, 21 May 2018 16:04:57 -0700 Subject: recovery: c++ify pthread use in UI Change pthread usage to std::mutex, lock_guard, unique_lock, thread, or condition_variable as appropriate. Test: Recovery works, recovery_component_test pass Bug: 78793464 Change-Id: Ibf0b1bbedcf0b6e32fc4ee6aaadd17f21b4d7077 --- screen_ui.cpp | 110 ++++++++++++++++++++++++---------------------------------- screen_ui.h | 4 +-- ui.cpp | 89 +++++++++++++++++++---------------------------- ui.h | 7 ++-- wear_ui.cpp | 4 +-- 5 files changed, 87 insertions(+), 127 deletions(-) diff --git a/screen_ui.cpp b/screen_ui.cpp index 0ee0ddcec..b9aba807d 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -19,8 +19,6 @@ #include #include #include -#include -#include #include #include #include @@ -171,8 +169,7 @@ ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) stage(-1), max_stage(-1), locale_(""), - rtl_locale_(false), - updateMutex(PTHREAD_MUTEX_INITIALIZER) {} + rtl_locale_(false) {} ScreenRecoveryUI::~ScreenRecoveryUI() { progress_thread_stopped_ = true; @@ -368,7 +365,7 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector(text_image, &free)); } - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); gr_color(0, 0, 0, 255); gr_clear(); @@ -400,7 +397,6 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector lg(updateMutex); + + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { + if (!intro_done) { + if (current_frame == intro_frames - 1) { + intro_done = true; + current_frame = 0; + } else { + ++current_frame; + } } else { - ++current_frame; + current_frame = (current_frame + 1) % loop_frames; } - } else { - current_frame = (current_frame + 1) % loop_frames; - } - - redraw = true; - } - // move the progress bar forward on timed intervals, if configured - int duration = progressScopeDuration; - if (progressBarType == DETERMINATE && duration > 0) { - double elapsed = now() - progressScopeTime; - float p = 1.0 * elapsed / duration; - if (p > 1.0) p = 1.0; - if (p > progress) { - progress = p; redraw = true; } - } - if (redraw) update_progress_locked(); + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = true; + } + } + + if (redraw) update_progress_locked(); + } - pthread_mutex_unlock(&updateMutex); double end = now(); // minimum of 20ms delay between frames double delay = interval - (end - start); @@ -799,16 +795,14 @@ void ScreenRecoveryUI::LoadAnimation() { } void ScreenRecoveryUI::SetBackground(Icon icon) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); currentIcon = icon; update_screen_locked(); - - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetProgressType(ProgressType type) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (progressBarType != type) { progressBarType = type; } @@ -816,11 +810,10 @@ void ScreenRecoveryUI::SetProgressType(ProgressType type) { progressScopeSize = 0; progress = 0; update_progress_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); progressBarType = DETERMINATE; progressScopeStart += progressScopeSize; progressScopeSize = portion; @@ -828,11 +821,10 @@ void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { progressScopeDuration = seconds; progress = 0; update_progress_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetProgress(float fraction) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (fraction < 0.0) fraction = 0.0; if (fraction > 1.0) fraction = 1.0; if (progressBarType == DETERMINATE && fraction > progress) { @@ -844,14 +836,12 @@ void ScreenRecoveryUI::SetProgress(float fraction) { update_progress_locked(); } } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetStage(int current, int max) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); stage = current; max_stage = max; - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { @@ -862,7 +852,7 @@ void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) fputs(str.c_str(), stdout); } - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (text_rows_ > 0 && text_cols_ > 0) { for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { if (*ptr == '\n' || text_col_ >= text_cols_) { @@ -875,7 +865,6 @@ void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) text_[text_row_][text_col_] = '\0'; update_screen_locked(); } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::Print(const char* fmt, ...) { @@ -893,23 +882,21 @@ void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { } void ScreenRecoveryUI::PutChar(char ch) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (ch != '\n') text_[text_row_][text_col_++] = ch; if (ch == '\n' || text_col_ >= text_cols_) { text_col_ = 0; ++text_row_; } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ClearText() { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); text_col_ = 0; text_row_ = 0; for (size_t i = 0; i < text_rows_; ++i) { memset(text_[i], 0, text_cols_ + 1); } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ShowFile(FILE* fp) { @@ -986,17 +973,16 @@ void ScreenRecoveryUI::ShowFile(const std::string& filename) { void ScreenRecoveryUI::StartMenu(const std::vector& headers, const std::vector& items, size_t initial_selection) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (text_rows_ > 0 && text_cols_ > 1) { menu_ = std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1, headers, items, initial_selection); update_screen_locked(); } - pthread_mutex_unlock(&updateMutex); } int ScreenRecoveryUI::SelectMenu(int sel) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (menu_) { int old_sel = menu_->selection(); sel = menu_->Select(sel); @@ -1005,17 +991,15 @@ int ScreenRecoveryUI::SelectMenu(int sel) { update_screen_locked(); } } - pthread_mutex_unlock(&updateMutex); return sel; } void ScreenRecoveryUI::EndMenu() { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (menu_) { menu_.reset(); update_screen_locked(); } - pthread_mutex_unlock(&updateMutex); } size_t ScreenRecoveryUI::ShowMenu(const std::vector& headers, @@ -1067,31 +1051,27 @@ size_t ScreenRecoveryUI::ShowMenu(const std::vector& headers, } bool ScreenRecoveryUI::IsTextVisible() { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); int visible = show_text; - pthread_mutex_unlock(&updateMutex); return visible; } bool ScreenRecoveryUI::WasTextEverVisible() { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); int ever_visible = show_text_ever; - pthread_mutex_unlock(&updateMutex); return ever_visible; } void ScreenRecoveryUI::ShowText(bool visible) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); show_text = visible; if (show_text) show_text_ever = true; update_screen_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::Redraw() { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); update_screen_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::KeyLongPress(int) { diff --git a/screen_ui.h b/screen_ui.h index c3605161a..b76d4706e 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -17,7 +17,6 @@ #ifndef RECOVERY_SCREEN_UI_H #define RECOVERY_SCREEN_UI_H -#include #include #include @@ -192,7 +191,6 @@ class ScreenRecoveryUI : public RecoveryUI { GRSurface* GetCurrentFrame() const; GRSurface* GetCurrentText() const; - static void* ProgressThreadStartRoutine(void* data); void ProgressThreadLoop(); virtual void ShowFile(FILE*); @@ -297,7 +295,7 @@ class ScreenRecoveryUI : public RecoveryUI { std::string locale_; bool rtl_locale_; - pthread_mutex_t updateMutex; + std::mutex updateMutex; private: void SetLocale(const std::string&); diff --git a/ui.cpp b/ui.cpp index dbe77ae8c..51d7f129c 100644 --- a/ui.cpp +++ b/ui.cpp @@ -18,8 +18,6 @@ #include #include -#include -#include #include #include #include @@ -74,8 +72,6 @@ RecoveryUI::RecoveryUI() touch_slot_(0), is_bootreason_recovery_ui_(false), screensaver_state_(ScreensaverState::DISABLED) { - pthread_mutex_init(&key_queue_mutex, nullptr); - pthread_cond_init(&key_queue_cond, nullptr); memset(key_pressed, 0, sizeof(key_pressed)); } @@ -341,25 +337,25 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { bool register_key = false; bool long_press = false; - pthread_mutex_lock(&key_queue_mutex); - key_pressed[key_code] = updown; - if (updown) { - ++key_down_count; - key_last_down = key_code; - key_long_press = false; - - std::thread time_key_thread(&RecoveryUI::TimeKey, this, key_code, key_down_count); - time_key_thread.detach(); - } else { - if (key_last_down == key_code) { - long_press = key_long_press; - register_key = true; + { + std::lock_guard lg(key_queue_mutex); + key_pressed[key_code] = updown; + if (updown) { + ++key_down_count; + key_last_down = key_code; + key_long_press = false; + std::thread time_key_thread(&RecoveryUI::TimeKey, this, key_code, key_down_count); + time_key_thread.detach(); + } else { + if (key_last_down == key_code) { + long_press = key_long_press; + register_key = true; + } + key_last_down = -1; } - key_last_down = -1; } - bool reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); + bool reboot_enabled = enable_reboot; if (register_key) { switch (CheckKey(key_code, long_press)) { case RecoveryUI::IGNORE: @@ -388,44 +384,37 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { void RecoveryUI::TimeKey(int key_code, int count) { std::this_thread::sleep_for(750ms); // 750 ms == "long" bool long_press = false; - pthread_mutex_lock(&key_queue_mutex); - if (key_last_down == key_code && key_down_count == count) { - long_press = key_long_press = true; + { + std::lock_guard lg(key_queue_mutex); + if (key_last_down == key_code && key_down_count == count) { + long_press = key_long_press = true; + } } - pthread_mutex_unlock(&key_queue_mutex); if (long_press) KeyLongPress(key_code); } void RecoveryUI::EnqueueKey(int key_code) { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_queue_mutex); const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); if (key_queue_len < queue_max) { key_queue[key_queue_len++] = key_code; - pthread_cond_signal(&key_queue_cond); + key_queue_cond.notify_one(); } - pthread_mutex_unlock(&key_queue_mutex); } int RecoveryUI::WaitKey() { - pthread_mutex_lock(&key_queue_mutex); + std::unique_lock lk(key_queue_mutex); // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is // plugged in. do { - struct timeval now; - struct timespec timeout; - gettimeofday(&now, nullptr); - timeout.tv_sec = now.tv_sec; - timeout.tv_nsec = now.tv_usec * 1000; - timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; - - int rc = 0; - while (key_queue_len == 0 && rc != ETIMEDOUT) { - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + std::cv_status rc = std::cv_status::no_timeout; + while (key_queue_len == 0 && rc != std::cv_status::timeout) { + rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC)); } if (screensaver_state_ != ScreensaverState::DISABLED) { - if (rc == ETIMEDOUT) { + if (rc == std::cv_status::timeout) { // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF. if (screensaver_state_ == ScreensaverState::NORMAL) { if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), @@ -464,7 +453,6 @@ int RecoveryUI::WaitKey() { key = key_queue[0]; memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); } - pthread_mutex_unlock(&key_queue_mutex); return key; } @@ -485,16 +473,14 @@ bool RecoveryUI::IsUsbConnected() { } bool RecoveryUI::IsKeyPressed(int key) { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_queue_mutex); int pressed = key_pressed[key]; - pthread_mutex_unlock(&key_queue_mutex); return pressed; } bool RecoveryUI::IsLongPress() { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_queue_mutex); bool result = key_long_press; - pthread_mutex_unlock(&key_queue_mutex); return result; } @@ -511,15 +497,15 @@ bool RecoveryUI::HasTouchScreen() const { } void RecoveryUI::FlushKeys() { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_queue_mutex); key_queue_len = 0; - pthread_mutex_unlock(&key_queue_mutex); } RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { - pthread_mutex_lock(&key_queue_mutex); - key_long_press = false; - pthread_mutex_unlock(&key_queue_mutex); + { + std::lock_guard lg(key_queue_mutex); + key_long_press = false; + } // If we have power and volume up keys, that chord is the signal to toggle the text display. if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) { @@ -542,9 +528,7 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { // Press power seven times in a row to reboot. if (key == KEY_POWER) { - pthread_mutex_lock(&key_queue_mutex); bool reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); if (reboot_enabled) { ++consecutive_power_keys; @@ -564,7 +548,6 @@ void RecoveryUI::KeyLongPress(int) { } void RecoveryUI::SetEnableReboot(bool enabled) { - pthread_mutex_lock(&key_queue_mutex); + std::lock_guard lg(key_queue_mutex); enable_reboot = enabled; - pthread_mutex_unlock(&key_queue_mutex); } diff --git a/ui.h b/ui.h index 75390d83c..32e28099e 100644 --- a/ui.h +++ b/ui.h @@ -18,10 +18,11 @@ #define RECOVERY_UI_H #include // KEY_MAX -#include #include +#include #include +#include #include #include #include @@ -188,8 +189,8 @@ class RecoveryUI { bool InitScreensaver(); // Key event input queue - pthread_mutex_t key_queue_mutex; - pthread_cond_t key_queue_cond; + std::mutex key_queue_mutex; + std::condition_variable key_queue_cond; int key_queue[256], key_queue_len; char key_pressed[KEY_MAX + 1]; // under key_queue_mutex int key_last_down; // under key_queue_mutex diff --git a/wear_ui.cpp b/wear_ui.cpp index f4a839923..65c4aeed6 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -16,7 +16,6 @@ #include "wear_ui.h" -#include #include #include @@ -86,11 +85,10 @@ void WearRecoveryUI::SetStage(int /* current */, int /* max */) {} void WearRecoveryUI::StartMenu(const std::vector& headers, const std::vector& items, size_t initial_selection) { - pthread_mutex_lock(&updateMutex); + std::lock_guard lg(updateMutex); if (text_rows_ > 0 && text_cols_ > 0) { menu_ = std::make_unique(scrollable_menu_, text_rows_ - kMenuUnusableRows - 1, text_cols_ - 1, headers, items, initial_selection); update_screen_locked(); } - pthread_mutex_unlock(&updateMutex); } -- cgit v1.2.3 From edee8361d75715b9bc020d76fa8f38b005a6e912 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 16 May 2018 13:43:22 -0700 Subject: recovery: add --fsck_unshare_blocks option for adb remount Allow "adb remount" on deduplicated filesystems to reboot into recovery and run e2fsck to undo deduplication. The e2fsck binary is copied from the system partition into tmpfs, and the system partition is unmounted so e2fsck can run safely. Bug: 64109868 Test: recovery with --fsck_unshare_blocks; adb remount Change-Id: I7558749b018b58f3c4339e51a95831dbd5be1ae3 --- Android.mk | 8 +++ fsck_unshare_blocks.cpp | 163 ++++++++++++++++++++++++++++++++++++++++++++++++ fsck_unshare_blocks.h | 22 +++++++ recovery.cpp | 11 +++- 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 fsck_unshare_blocks.cpp create mode 100644 fsck_unshare_blocks.h diff --git a/Android.mk b/Android.mk index 6aa91ea21..24da8b28a 100644 --- a/Android.mk +++ b/Android.mk @@ -132,6 +132,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ adb_install.cpp \ + fsck_unshare_blocks.cpp \ fuse_sdcard_provider.cpp \ install.cpp \ recovery.cpp \ @@ -192,6 +193,13 @@ LOCAL_REQUIRED_MODULES += \ endif endif +# e2fsck is needed for adb remount -R. +ifeq ($(BOARD_EXT4_SHARE_DUP_BLOCKS),true) +ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) +LOCAL_REQUIRED_MODULES += e2fsck_static +endif +endif + ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),) LOCAL_REQUIRED_MODULES += \ recovery-persist \ diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp new file mode 100644 index 000000000..a100368e7 --- /dev/null +++ b/fsck_unshare_blocks.cpp @@ -0,0 +1,163 @@ +/* + * 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. + */ + +#include "fsck_unshare_blocks.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "roots.h" + +static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static"; +static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin"; + +static bool copy_file(const char* source, const char* dest) { + android::base::unique_fd source_fd(open(source, O_RDONLY)); + if (source_fd < 0) { + PLOG(ERROR) << "open %s failed" << source; + return false; + } + + android::base::unique_fd dest_fd(open(dest, O_CREAT | O_WRONLY, S_IRWXU)); + if (dest_fd < 0) { + PLOG(ERROR) << "open %s failed" << dest; + return false; + } + + for (;;) { + char buf[4096]; + ssize_t rv = read(source_fd, buf, sizeof(buf)); + if (rv < 0) { + PLOG(ERROR) << "read failed"; + return false; + } + if (rv == 0) { + break; + } + if (write(dest_fd, buf, rv) != rv) { + PLOG(ERROR) << "write failed"; + return false; + } + } + return true; +} + +static bool run_e2fsck(const std::string& partition) { + Volume* volume = volume_for_mount_point(partition); + if (!volume) { + LOG(INFO) << "No fstab entry for " << partition << ", skipping."; + return true; + } + + LOG(INFO) << "Running e2fsck on device " << volume->blk_device; + + std::vector args = { TMP_E2FSCK_BIN, "-p", "-E", "unshare_blocks", + volume->blk_device }; + std::vector argv(args.size()); + std::transform(args.cbegin(), args.cend(), argv.begin(), + [](const std::string& arg) { return const_cast(arg.c_str()); }); + argv.push_back(nullptr); + + pid_t child; + char* env[] = { nullptr }; + if (posix_spawn(&child, argv[0], nullptr, nullptr, argv.data(), env)) { + PLOG(ERROR) << "posix_spawn failed"; + return false; + } + + int status = 0; + int ret = TEMP_FAILURE_RETRY(waitpid(child, &status, 0)); + if (ret < 0) { + PLOG(ERROR) << "waitpid failed"; + return false; + } + if (!WIFEXITED(status)) { + LOG(ERROR) << "e2fsck exited abnormally: " << status; + return false; + } + int return_code = WEXITSTATUS(status); + if (return_code >= 8) { + LOG(ERROR) << "e2fsck could not unshare blocks: " << return_code; + return false; + } + + LOG(INFO) << "Successfully unshared blocks on " << partition; + return true; +} + +static const char* get_system_root() { + if (android::base::GetBoolProperty("ro.build.system_root_image", false)) { + return "/system_root"; + } else { + return "/system"; + } +} + +bool do_fsck_unshare_blocks() { + // List of partitions we will try to e2fsck -E unshare_blocks. + std::vector partitions = { "/odm", "/oem", "/product", "/vendor" }; + + // Temporarily mount system so we can copy e2fsck_static. + bool mounted = false; + if (android::base::GetBoolProperty("ro.build.system_root_image", false)) { + mounted = ensure_path_mounted_at("/", "/system_root") != -1; + partitions.push_back("/"); + } else { + mounted = ensure_path_mounted("/system") != -1; + partitions.push_back("/system"); + } + if (!mounted) { + LOG(ERROR) << "Failed to mount system image."; + return false; + } + if (!copy_file(SYSTEM_E2FSCK_BIN, TMP_E2FSCK_BIN)) { + LOG(ERROR) << "Could not copy e2fsck to /tmp."; + return false; + } + if (umount(get_system_root()) < 0) { + PLOG(ERROR) << "umount failed"; + return false; + } + + bool ok = true; + for (const auto& partition : partitions) { + ok &= run_e2fsck(partition); + } + + if (ok) { + LOG(INFO) << "Finished running e2fsck."; + } else { + LOG(ERROR) << "Finished running e2fsck, but not all partitions succceeded."; + } + return ok; +} diff --git a/fsck_unshare_blocks.h b/fsck_unshare_blocks.h new file mode 100644 index 000000000..9de8ef9a3 --- /dev/null +++ b/fsck_unshare_blocks.h @@ -0,0 +1,22 @@ +/* + * 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. + */ + +#ifndef _FILESYSTEM_CMDS_H +#define _FILESYSTEM_CMDS_H + +bool do_fsck_unshare_blocks(); + +#endif // _FILESYSTEM_CMDS_H diff --git a/recovery.cpp b/recovery.cpp index 56b2567d1..98cbfed2f 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -55,6 +55,7 @@ #include "adb_install.h" #include "common.h" #include "device.h" +#include "fsck_unshare_blocks.h" #include "fuse_sdcard_provider.h" #include "fuse_sideload.h" #include "install.h" @@ -969,6 +970,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vector(arg.c_str()); }); static constexpr struct option OPTIONS[] = { + { "fsck_unshare_blocks", no_argument, nullptr, 0 }, { "just_exit", no_argument, nullptr, 'x' }, { "locale", required_argument, nullptr, 0 }, { "prompt_and_wipe_data", no_argument, nullptr, 0 }, @@ -997,6 +999,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorPrint("Rebooting automatically.\n"); } + } else if (fsck_unshare_blocks) { + if (!do_fsck_unshare_blocks()) { + status = INSTALL_ERROR; + } } else if (!just_exit) { // If this is an eng or userdebug build, automatically turn on the text display if no command // is specified. Note that this should be called before setting the background to avoid -- cgit v1.2.3 From 64957ce4b14576071d6e5fd35632c50e5a28a749 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 30 May 2018 16:21:39 -0700 Subject: updater: Drop the 'blocks' parameter in LoadStash(). None of the callers actually uses the value. (Even in the earlier versions, e.g. the one in M, the value wasn't used either.) Test: Run recovery_component_test on marlin. Change-Id: I53e61a1afa211f71a200889ed3aa4046763b46ea --- updater/blockimg.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 5d6da6cb3..4adb974cb 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -751,7 +751,7 @@ static void DeleteStash(const std::string& base) { } } -static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks, +static int LoadStash(CommandParameters& params, const std::string& id, bool verify, std::vector& buffer, bool printnoent) { // In verify mode, if source range_set was saved for the given hash, check contents in the source // blocks first. If the check fails, search for the stashed files on /cache as usual. @@ -773,11 +773,6 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri } } - size_t blockcount = 0; - if (!blocks) { - blocks = &blockcount; - } - std::string fn = GetStashFileName(params.stashbase, id, ""); struct stat sb; @@ -808,9 +803,8 @@ static int LoadStash(CommandParameters& params, const std::string& id, bool veri return -1; } - *blocks = sb.st_size / BLOCKSIZE; - - if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) { + size_t blocks = sb.st_size / BLOCKSIZE; + if (verify && VerifyBlocks(id, buffer, blocks, true) != 0) { LOG(ERROR) << "unexpected contents in " << fn; if (stash_map.find(id) == stash_map.end()) { LOG(ERROR) << "failed to find source blocks number for stash " << id @@ -1056,7 +1050,7 @@ static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size } std::vector stash; - if (LoadStash(params, tokens[0], false, nullptr, stash, true) == -1) { + if (LoadStash(params, tokens[0], false, stash, true) == -1) { // These source blocks will fail verification if used later, but we // will let the caller decide if this is a fatal failure LOG(ERROR) << "failed to load stash " << tokens[0]; @@ -1171,7 +1165,7 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* return 0; } - if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) { + if (*overlap && LoadStash(params, srchash, true, params.buffer, true) == 0) { // Overlapping source blocks were previously stashed, command can proceed. We are recovering // from an interrupted command, so we don't know if the stash can safely be deleted after this // command. @@ -1237,8 +1231,7 @@ static int PerformCommandStash(CommandParameters& params) { } const std::string& id = params.tokens[params.cpos++]; - size_t blocks = 0; - if (LoadStash(params, id, true, &blocks, params.buffer, false) == 0) { + if (LoadStash(params, id, true, params.buffer, false) == 0) { // Stash file already exists and has expected contents. Do not read from source again, as the // source may have been already overwritten during a previous attempt. return 0; @@ -1247,11 +1240,11 @@ static int PerformCommandStash(CommandParameters& params) { RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); CHECK(static_cast(src)); - allocate(src.blocks() * BLOCKSIZE, params.buffer); + size_t blocks = src.blocks(); + allocate(blocks * BLOCKSIZE, params.buffer); if (ReadBlocks(src, params.buffer, params.fd) == -1) { return -1; } - blocks = src.blocks(); stash_map[id] = src; if (VerifyBlocks(id, params.buffer, blocks, true) != 0) { -- cgit v1.2.3 From 7671f68ab858a75f269fbddefb4840b87078a425 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 24 May 2018 09:11:47 -0700 Subject: updater_sample: Improve update completion handling Currently sample app relies on onPayloadApplicationComplete callback. It might not get invoked when app is unbound and update is complete. On the other hand, onStatusUpdate gets invoked always (except when update_engine fails to init). It's good to rely on onStatusUpdate callback to reapply the update if it's IDLE but sample app state is RUNNING. - Add methods to ensure correct updater state. - Update README.md. BUG: 80205922 Test: on the device Change-Id: Ic2f390e85af43556e227362321ab69f0ff146188 Signed-off-by: Zhomart Mukhamejanov --- updater_sample/README.md | 68 +++++++++++++++++++ .../android/systemupdatersample/UpdateManager.java | 76 +++++++++++++++++++++- .../systemupdatersample/ui/MainActivity.java | 4 ++ 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/updater_sample/README.md b/updater_sample/README.md index 3f211ddba..f6c63a7b6 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -65,6 +65,32 @@ purpose only. 6. Push OTA packages to the device. +## Sample App State vs UpdateEngine Status + +UpdateEngine provides status for different stages of update application +process. But it lacks of proper status codes when update fails. + +This creates two problems: + +1. If sample app is unbound from update_engine (MainActivity is paused, destroyed), + app doesn't receive onStatusUpdate and onPayloadApplicationCompleted notifications. + If app binds to update_engine after update is completed, + only onStatusUpdate is called, but status becomes IDLE in most cases. + And there is no way to know if update was successful or not. + +2. This sample app demostrates suspend/resume using update_engins's + `cancel` and `applyPayload` (which picks up from where it left). + When `cancel` is called, status is set to `IDLE`, which doesn't allow + tracking suspended state properly. + +To solve these problems sample app implements its own separate update +state - `UpdaterState`. To solve the first problem, sample app persists +`UpdaterState` on a device. When app is resumed, it checks if `UpdaterState` +matches the update_engine's status (as onStatusUpdate is guaranteed to be called). +If they doesn't match, sample app calls `applyPayload` again with the same +parameters, and handles update completion properly using `onPayloadApplicationCompleted` +callback. The second problem is solved by adding `PAUSED` updater state. + ## Sending HTTP headers from UpdateEngine Sometimes OTA package server might require some HTTP headers to be present, @@ -76,6 +102,44 @@ as of writing this sample app, these headers are `Authorization` and `User-Agent which HTTP headers are supported. +## Used update_engine APIs + +### UpdateEngine#bind + +Binds given callbacks to update_engine. When update_engine successfully +initialized, it's guaranteed to invoke callback onStatusUpdate. + +### UpdateEngine#applyPayload + +Start an update attempt to download an apply the provided `payload_url` if +no other update is running. The extra `key_value_pair_headers` will be +included when fetching the payload. + +### UpdateEngine#cancel + +Cancel the ongoing update. The update could be running or suspended, but it +can't be canceled after it was done. + +### UpdateEngine#resetStatus + +Reset the already applied update back to an idle state. This method can +only be called when no update attempt is going on, and it will reset the +status back to idle, deleting the currently applied update if any. + +### Callback: onStatusUpdate + +Called whenever the value of `status` or `progress` changes. For +`progress` values changes, this method will be called only if it changes significantly. +At this time of writing this doc, delta for `progress` is `0.005`. + +`onStatusUpdate` is always called when app binds to update_engine, +except when update_engine fails to initialize. + +### Callback: onPayloadApplicationComplete + +Called whenever an update attempt is completed. + + ## Development - [x] Create a UI with list of configs, current version, @@ -90,6 +154,10 @@ which HTTP headers are supported. - [x] Add demo for passing HTTP headers to `UpdateEngine#applyPayload` - [x] [Package compatibility check](https://source.android.com/devices/architecture/vintf/match-rules) - [x] Deferred switch slot demo +- [x] Add UpdateManager; extract update logic from MainActivity +- [x] Add Sample app update state (separate from update_engine status) +- [-] Add smart update completion detection using onStatusUpdate +- [ ] Add pause/resume demo - [ ] Add demo for passing NETWORK_ID to `UpdateEngine#applyPayload` - [ ] Verify system partition checksum for package - [?] Add non-A/B updates demo diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java index c4c8c9c27..f5c2ea5ab 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java @@ -384,11 +384,85 @@ public class UpdateManager { updateEngineApplyPayload(builder.build()); } + /** + * Verifies if mUpdaterState matches mUpdateEngineStatus. + * If they don't match, runs applyPayload to trigger onPayloadApplicationComplete + * callback, which updates mUpdaterState. + */ + private void ensureCorrectUpdaterState() { + // When mUpdaterState is one of IDLE, PAUSED, ERROR, SLOT_SWITCH_REQUIRED + // then mUpdateEngineStatus must be IDLE. + // When mUpdaterState is RUNNING, + // then mUpdateEngineStatus must not be IDLE or UPDATED_NEED_REBOOT. + // When mUpdaterState is REBOOT_REQUIRED, + // then mUpdateEngineStatus must be UPDATED_NEED_REBOOT. + int state = mUpdaterState.get(); + int updateEngineStatus = mUpdateEngineStatus.get(); + if (state == UpdaterState.IDLE + || state == UpdaterState.ERROR + || state == UpdaterState.PAUSED + || state == UpdaterState.SLOT_SWITCH_REQUIRED) { + ensureUpdateEngineStatusIdle(state, updateEngineStatus); + } else if (state == UpdaterState.RUNNING) { + ensureUpdateEngineStatusRunning(state, updateEngineStatus); + } else if (state == UpdaterState.REBOOT_REQUIRED) { + ensureUpdateEngineStatusReboot(state, updateEngineStatus); + } + } + + private void ensureUpdateEngineStatusIdle(int state, int updateEngineStatus) { + if (updateEngineStatus == UpdateEngine.UpdateStatusConstants.IDLE) { + return; + } + // It might happen when update is started not from the sample app. + // To make the sample app simple, we won't handle this case. + throw new RuntimeException("When mUpdaterState is " + state + + " mUpdateEngineStatus expected to be " + + UpdateEngine.UpdateStatusConstants.IDLE + + ", but it is " + updateEngineStatus); + } + + private void ensureUpdateEngineStatusRunning(int state, int updateEngineStatus) { + if (updateEngineStatus != UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT + && updateEngineStatus != UpdateEngine.UpdateStatusConstants.IDLE) { + return; + } + // Re-apply latest update. It makes update_engine to invoke + // onPayloadApplicationComplete callback. The callback notifies + // if update was successful or not. + updateEngineReApplyPayload(); + } + + private void ensureUpdateEngineStatusReboot(int state, int updateEngineStatus) { + if (updateEngineStatus == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) { + return; + } + // This might happen when update is installed by other means, + // and sample app is not aware of it. To make the sample app simple, + // we won't handle this case. + throw new RuntimeException("When mUpdaterState is " + state + + " mUpdateEngineStatus expected to be " + + UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT + + ", but it is " + updateEngineStatus); + } + + /** + * Invoked by update_engine whenever update status or progress changes. + * It's also guaranteed to be invoked when app binds to the update_engine, except + * when update_engine fails to initialize (as defined in + * system/update_engine/binder_service_android.cc in + * function BinderUpdateEngineAndroidService::bind). + * + * @param status one of {@link UpdateEngine.UpdateStatusConstants}. + * @param progress a number from 0.0 to 1.0. + */ private void onStatusUpdate(int status, float progress) { int previousStatus = mUpdateEngineStatus.get(); mUpdateEngineStatus.set(status); mProgress.set(progress); + ensureCorrectUpdaterState(); + getOnProgressUpdateCallback().ifPresent(callback -> callback.accept(progress)); if (previousStatus != status) { @@ -413,7 +487,7 @@ public class UpdateManager { } /** - * Helper class to delegate {@code update_engine} callbacks to UpdateManager + * Helper class to delegate {@code update_engine} callback invocations to UpdateManager. */ class UpdateEngineCallbackImpl extends UpdateEngineCallback { @Override 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 0b571cc81..1de72c2d6 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -108,12 +108,16 @@ public class MainActivity extends Activity { @Override protected void onResume() { super.onResume(); + // TODO(zhomart) load saved states + // Binding to UpdateEngine invokes onStatusUpdate callback, + // persisted updater state has to be loaded and prepared beforehand. this.mUpdateManager.bind(); } @Override protected void onPause() { this.mUpdateManager.unbind(); + // TODO(zhomart) save state super.onPause(); } -- cgit v1.2.3 From f6522eba71576776c9a2f9efb2ab0d1edb3eec46 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 24 May 2018 09:11:47 -0700 Subject: updater_sample: Add @GuardedBy Test: on the device Change-Id: I15762dafec1814980e1c2529f5fc2048853c8ff2 Signed-off-by: Zhomart Mukhamejanov --- updater_sample/Android.mk | 2 +- .../src/com/example/android/systemupdatersample/UpdateManager.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/updater_sample/Android.mk b/updater_sample/Android.mk index 056ad66be..7662111b7 100644 --- a/updater_sample/Android.mk +++ b/updater_sample/Android.mk @@ -18,8 +18,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_PACKAGE_NAME := SystemUpdaterSample -LOCAL_SDK_VERSION := system_current LOCAL_MODULE_TAGS := samples +LOCAL_SDK_VERSION := system_current # TODO: enable proguard and use proguard.flags file LOCAL_PROGUARD_ENABLED := disabled diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java index f5c2ea5ab..145cc83b1 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java @@ -39,6 +39,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.DoubleConsumer; import java.util.function.IntConsumer; +import javax.annotation.concurrent.GuardedBy; + /** * Manages the update flow. It has its own state (in memory), separate from * {@link UpdateEngine}'s state. Asynchronously interacts with the {@link UpdateEngine}. @@ -62,11 +64,16 @@ public class UpdateManager { private AtomicBoolean mManualSwitchSlotRequired = new AtomicBoolean(true); + @GuardedBy("mLock") private UpdateData mLastUpdateData = null; + @GuardedBy("mLock") private IntConsumer mOnStateChangeCallback = null; + @GuardedBy("mLock") private IntConsumer mOnEngineStatusUpdateCallback = null; + @GuardedBy("mLock") private DoubleConsumer mOnProgressUpdateCallback = null; + @GuardedBy("mLock") private IntConsumer mOnEngineCompleteCallback = null; private final Object mLock = new Object(); -- cgit v1.2.3