From 2b3f80068ece1040ba7c923afe1e70b705535ad5 Mon Sep 17 00:00:00 2001 From: Tianjie Xu Date: Tue, 20 Mar 2018 16:07:39 -0700 Subject: Factor out a menu class for screen ui Also consolidate the duplicate codes to draw the menu in ScreenRecoveryUI and WearRecoveryUI. This helps us to support text icons as menu in the future. Bug: 74397117 Test: Check the menu under recovery on bullhead and a wear device. Change-Id: Iba9b646c3828670f0e78a7e07d1a94a44e96bb0b (cherry picked from commit Iba9b646c3828670f0e78a7e07d1a94a44e96bb0b) --- Android.mk | 77 +++++++++------- screen_ui.cpp | 183 +++++++++++++++++++++++++++++--------- screen_ui.h | 73 +++++++++++++++- tests/Android.mk | 4 +- tests/unit/screen_ui_test.cpp | 198 ++++++++++++++++++++++++++++++++++++++++++ wear_ui.cpp | 116 ++----------------------- wear_ui.h | 4 - 7 files changed, 471 insertions(+), 184 deletions(-) create mode 100644 tests/unit/screen_ui_test.cpp diff --git a/Android.mk b/Android.mk index 56d69c03b..0499a6da8 100644 --- a/Android.mk +++ b/Android.mk @@ -53,36 +53,21 @@ LOCAL_STATIC_LIBRARIES := \ include $(BUILD_STATIC_LIBRARY) -# recovery (static executable) +# librecovery_ui (static library) # =============================== include $(CLEAR_VARS) - LOCAL_SRC_FILES := \ - adb_install.cpp \ - device.cpp \ - fuse_sdcard_provider.cpp \ - recovery.cpp \ - roots.cpp \ - rotate_logs.cpp \ screen_ui.cpp \ ui.cpp \ vr_ui.cpp \ - wear_ui.cpp \ + wear_ui.cpp -LOCAL_MODULE := recovery - -LOCAL_FORCE_STATIC_EXECUTABLE := true - -LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf - -ifeq ($(TARGET_USERIMAGES_USE_F2FS),true) -ifeq ($(HOST_OS),linux) -LOCAL_REQUIRED_MODULES += sload.f2fs mkfs.f2fs -endif -endif +LOCAL_CFLAGS := -Wall -Werror -LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) -LOCAL_CFLAGS += -Wall -Werror +LOCAL_MODULE := librecovery_ui +LOCAL_STATIC_LIBRARIES := \ + libminui \ + libbase ifneq ($(TARGET_RECOVERY_UI_MARGIN_HEIGHT),) LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=$(TARGET_RECOVERY_UI_MARGIN_HEIGHT) @@ -132,6 +117,36 @@ else LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=0 endif +include $(BUILD_STATIC_LIBRARY) + +# recovery (static executable) +# =============================== +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + adb_install.cpp \ + device.cpp \ + fuse_sdcard_provider.cpp \ + recovery.cpp \ + roots.cpp \ + rotate_logs.cpp \ + + +LOCAL_MODULE := recovery + +LOCAL_FORCE_STATIC_EXECUTABLE := true + +LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf + +ifeq ($(TARGET_USERIMAGES_USE_F2FS),true) +ifeq ($(HOST_OS),linux) +LOCAL_REQUIRED_MODULES += sload.f2fs mkfs.f2fs +endif +endif + +LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) +LOCAL_CFLAGS += -Wall -Werror + LOCAL_C_INCLUDES += \ system/vold \ @@ -148,8 +163,17 @@ LOCAL_STATIC_LIBRARIES := \ libvndksupport \ libbatterymonitor +LOCAL_STATIC_LIBRARIES += librecovery + +# If $(TARGET_RECOVERY_UI_LIB) is defined, the recovery calls make_device() from the +# $(TARGET_RECOVERY_UI_LIB), which depends on the librecovery_ui. +ifeq ($(TARGET_RECOVERY_UI_LIB),) + LOCAL_SRC_FILES += default_device.cpp +else + LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) +endif + LOCAL_STATIC_LIBRARIES += \ - librecovery \ libverifier \ libbootloader_message \ libfs_mgr \ @@ -161,6 +185,7 @@ LOCAL_STATIC_LIBRARIES += \ libminadbd \ libasyncio \ libfusesideload \ + librecovery_ui \ libminui \ libpng \ libcrypto_utils \ @@ -184,12 +209,6 @@ endif LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin -ifeq ($(TARGET_RECOVERY_UI_LIB),) - LOCAL_SRC_FILES += default_device.cpp -else - LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) -endif - ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),) LOCAL_REQUIRED_MODULES += recovery-persist recovery-refresh endif diff --git a/screen_ui.cpp b/screen_ui.cpp index c8fb5aa75..317e5529c 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -53,7 +53,98 @@ static double now() { return tv.tv_sec + tv.tv_usec / 1000000.0; } -ScreenRecoveryUI::ScreenRecoveryUI() +Menu::Menu(bool scrollable, size_t max_items, size_t max_length) + : scrollable_(scrollable), + max_display_items_(max_items), + max_item_length_(max_length), + text_headers_(nullptr), + menu_start_(0), + selection_(0) { + CHECK_LE(max_items, static_cast(std::numeric_limits::max())); +} + +const char* const* Menu::text_headers() const { + return text_headers_; +} + +std::string Menu::TextItem(size_t index) const { + CHECK_LT(index, text_items_.size()); + + return text_items_[index]; +} + +size_t Menu::MenuStart() const { + return menu_start_; +} + +size_t Menu::MenuEnd() const { + return std::min(ItemsCount(), menu_start_ + max_display_items_); +} + +size_t Menu::ItemsCount() const { + return text_items_.size(); +} + +bool Menu::ItemsOverflow(std::string* cur_selection_str) const { + if (!scrollable_ || static_cast(ItemsCount()) <= max_display_items_) { + return false; + } + + *cur_selection_str = + android::base::StringPrintf("Current item: %d/%zu", selection_ + 1, ItemsCount()); + return true; +} + +void Menu::Start(const char* const* headers, const char* const* items, int initial_selection) { + text_headers_ = headers; + + // It's fine to have more entries than text_rows_ if scrollable menu is supported. + size_t max_items_count = scrollable_ ? std::numeric_limits::max() : max_display_items_; + for (size_t i = 0; i < max_items_count && items[i] != nullptr; ++i) { + text_items_.emplace_back(items[i], strnlen(items[i], max_item_length_)); + } + + CHECK(!text_items_.empty()); + selection_ = initial_selection; +} + +// TODO(xunchang) modify the function parameters to button up & down. +int Menu::Select(int sel) { + CHECK_LE(ItemsCount(), static_cast(std::numeric_limits::max())); + int count = ItemsCount(); + + // Wraps the selection at boundary if the menu is not scrollable. + if (!scrollable_) { + if (sel < 0) { + selection_ = count - 1; + } else if (sel >= count) { + selection_ = 0; + } else { + selection_ = sel; + } + + return selection_; + } + + if (sel < 0) { + selection_ = 0; + } else if (sel >= count) { + selection_ = count - 1; + } else { + if (static_cast(sel) < menu_start_) { + menu_start_--; + } else if (static_cast(sel) >= MenuEnd()) { + menu_start_++; + } + selection_ = sel; + } + + return selection_; +} + +ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {} + +ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH), kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT), kAnimationFps(RECOVERY_UI_ANIMATION_FPS), @@ -71,10 +162,7 @@ ScreenRecoveryUI::ScreenRecoveryUI() text_row_(0), show_text(false), show_text_ever(false), - menu_headers_(nullptr), - show_menu(false), - menu_items(0), - menu_sel(0), + scrollable_menu_(scrollable_menu), file_viewer_text_(nullptr), intro_frames(0), loop_frames(0), @@ -407,13 +495,13 @@ int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* line static const char* REGULAR_HELP[] = { "Use volume up/down and power.", - NULL + nullptr, }; static const char* LONG_PRESS_HELP[] = { "Any button cycles highlight.", "Long-press activates.", - NULL + nullptr, }; // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex @@ -428,8 +516,13 @@ void ScreenRecoveryUI::draw_screen_locked() { gr_color(0, 0, 0, 255); gr_clear(); + draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); +} + +// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_menu_and_text_buffer_locked(const char* const* help_message) { int y = kMarginHeight; - if (show_menu) { + if (menu_) { static constexpr int kMenuIndent = 4; int x = kMarginWidth + kMenuIndent; @@ -440,26 +533,46 @@ void ScreenRecoveryUI::draw_screen_locked() { for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) { y += DrawTextLine(x, y, chunk.c_str(), false); } - y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); + y += DrawTextLines(x, y, help_message); + + // Draw menu header. SetColor(HEADER); - // Ignore kMenuIndent, which is not taken into account by text_cols_. - y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_); + if (!menu_->scrollable()) { + y += DrawWrappedTextLines(x, y, menu_->text_headers()); + } else { + y += DrawTextLines(x, y, menu_->text_headers()); + // Show the current menu item number in relation to total number if items don't fit on the + // screen. + std::string cur_selection_str; + if (menu_->ItemsOverflow(&cur_selection_str)) { + y += DrawTextLine(x, y, cur_selection_str.c_str(), true); + } + } + // Draw menu items. SetColor(MENU); - y += DrawHorizontalRule(y) + 4; - for (int i = 0; i < menu_items; ++i) { - if (i == menu_sel) { + // Do not draw the horizontal rule for wear devices. + if (!menu_->scrollable()) { + y += DrawHorizontalRule(y) + 4; + } + for (size_t i = menu_->MenuStart(); i < menu_->MenuEnd(); ++i) { + bool bold = false; + if (i == static_cast(menu_->selection())) { // Draw the highlight bar. SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); - DrawHighlightBar(0, y - 2, ScreenWidth(), char_height_ + 4); + + int bar_height = char_height_ + 4; + DrawHighlightBar(0, y - 2, ScreenWidth(), bar_height); + // Bold white text for the selected item. SetColor(MENU_SEL_FG); - y += DrawTextLine(x, y, menu_[i].c_str(), true); - SetColor(MENU); - } else { - y += DrawTextLine(x, y, menu_[i].c_str(), false); + bold = true; } + + y += DrawTextLine(x, y, menu_->TextItem(i).c_str(), bold); + + SetColor(MENU); } y += DrawHorizontalRule(y); } @@ -864,15 +977,10 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items, int initial_selection) { pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - menu_headers_ = headers; - menu_.clear(); - for (size_t i = 0; i < text_rows_ && items[i] != nullptr; ++i) { - menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1))); - } - menu_items = static_cast(menu_.size()); - show_menu = true; - menu_sel = initial_selection; + if (text_rows_ > 0 && text_cols_ > 1) { + menu_ = std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1); + menu_->Start(headers, items, initial_selection); + update_screen_locked(); } pthread_mutex_unlock(&updateMutex); @@ -880,16 +988,13 @@ void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* int ScreenRecoveryUI::SelectMenu(int sel) { pthread_mutex_lock(&updateMutex); - if (show_menu) { - int old_sel = menu_sel; - menu_sel = sel; - - // Wrap at top and bottom. - if (menu_sel < 0) menu_sel = menu_items - 1; - if (menu_sel >= menu_items) menu_sel = 0; + if (menu_) { + int old_sel = menu_->selection(); + sel = menu_->Select(sel); - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); + if (sel != old_sel) { + update_screen_locked(); + } } pthread_mutex_unlock(&updateMutex); return sel; @@ -897,8 +1002,8 @@ int ScreenRecoveryUI::SelectMenu(int sel) { void ScreenRecoveryUI::EndMenu() { pthread_mutex_lock(&updateMutex); - if (show_menu && text_rows_ > 0 && text_cols_ > 0) { - show_menu = false; + if (menu_) { + menu_.reset(); update_screen_locked(); } pthread_mutex_unlock(&updateMutex); diff --git a/screen_ui.h b/screen_ui.h index f05761c42..c1222a576 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -28,6 +29,70 @@ // From minui/minui.h. struct GRSurface; +// This class maintains the menu selection and display of the screen ui. +class Menu { + public: + Menu(bool scrollable, size_t max_items, size_t max_length); + + bool scrollable() const { + return scrollable_; + } + + int selection() const { + return selection_; + } + + // Returns count of menu items. + size_t ItemsCount() const; + // Returns the index of the first menu item. + size_t MenuStart() const; + // Returns the index of the last menu item + 1. + size_t MenuEnd() const; + + // Menu example: + // info: Android Recovery + // .... + // help messages: Swipe up/down to move + // Swipe left/right to select + // empty line (horizontal rule): + // menu headers: Select file to view + // menu items: /cache/recovery/last_log + // /cache/recovery/last_log.1 + // /cache/recovery/last_log.2 + // ... + const char* const* text_headers() const; + std::string TextItem(size_t index) const; + + // Checks if the menu items fit vertically on the screen. Returns true and set the + // |cur_selection_str| if the items exceed the screen limit. + bool ItemsOverflow(std::string* cur_selection_str) const; + + // Starts the menu with |headers| and |items| in text. Sets the default selection to + // |initial_selection|. + void Start(const char* const* headers, const char* const* items, int initial_selection); + + // Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is + // scrollable. + int Select(int sel); + + private: + // The menu is scrollable to display more items. Used on wear devices who have smaller screens. + const bool scrollable_; + // The max number of menu items to fit vertically on a screen. + const size_t max_display_items_; + // The length of each item to fit horizontally on a screen. + const size_t max_item_length_; + + // Internal storage for the menu headers and items in text. + const char* const* text_headers_; + std::vector text_items_; + + // The first item to display on the screen. + size_t menu_start_; + // Current menu selection. + int selection_; +}; + // Implementation of RecoveryUI appropriate for devices with a screen // (shows an icon + a progress bar, text logging, menu, etc.) class ScreenRecoveryUI : public RecoveryUI { @@ -44,6 +109,7 @@ class ScreenRecoveryUI : public RecoveryUI { }; ScreenRecoveryUI(); + explicit ScreenRecoveryUI(bool scrollable_menu); bool Init(const std::string& locale) override; @@ -101,6 +167,7 @@ class ScreenRecoveryUI : public RecoveryUI { virtual void draw_background_locked(); virtual void draw_foreground_locked(); virtual void draw_screen_locked(); + virtual void draw_menu_and_text_buffer_locked(const char* const* help_message); virtual void update_screen_locked(); virtual void update_progress_locked(); @@ -184,10 +251,8 @@ class ScreenRecoveryUI : public RecoveryUI { bool show_text; bool show_text_ever; // has show_text ever been true? - std::vector menu_; - const char* const* menu_headers_; - bool show_menu; - int menu_items, menu_sel; + bool scrollable_menu_; + std::unique_ptr menu_; // An alternate text screen, swapped with 'text_' when we're viewing a log file. char** file_viewer_text_; diff --git a/tests/Android.mk b/tests/Android.mk index b3584fe87..9a71371fa 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -23,6 +23,7 @@ LOCAL_MODULE := recovery_unit_test LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_STATIC_LIBRARIES := \ libverifier \ + librecovery_ui \ libminui \ libotautil \ libupdater \ @@ -38,8 +39,9 @@ LOCAL_SRC_FILES := \ unit/dirutil_test.cpp \ unit/locale_test.cpp \ unit/rangeset_test.cpp \ + unit/screen_ui_test.cpp \ unit/sysutil_test.cpp \ - unit/zip_test.cpp \ + unit/zip_test.cpp LOCAL_C_INCLUDES := bootable/recovery LOCAL_SHARED_LIBRARIES := liblog diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp new file mode 100644 index 000000000..be6799f2e --- /dev/null +++ b/tests/unit/screen_ui_test.cpp @@ -0,0 +1,198 @@ +/* + * 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 "screen_ui.h" + +#include + +#include + +constexpr const char* HEADER[] = { "header", nullptr }; +constexpr const char* ITEMS[] = { "items1", "items2", "items3", "items4", "1234567890", nullptr }; + +TEST(ScreenUITest, StartPhoneMenuSmoke) { + Menu menu(false, 10, 20); + ASSERT_FALSE(menu.scrollable()); + + menu.Start(HEADER, ITEMS, 0); + ASSERT_EQ(HEADER[0], menu.text_headers()[0]); + ASSERT_EQ(5u, menu.ItemsCount()); + + std::string message; + ASSERT_FALSE(menu.ItemsOverflow(&message)); + for (size_t i = 0; i < menu.ItemsCount(); i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + + ASSERT_EQ(0, menu.selection()); +} + +TEST(ScreenUITest, StartWearMenuSmoke) { + Menu menu(true, 10, 8); + ASSERT_TRUE(menu.scrollable()); + + menu.Start(HEADER, ITEMS, 1); + ASSERT_EQ(HEADER[0], menu.text_headers()[0]); + ASSERT_EQ(5u, menu.ItemsCount()); + + std::string message; + ASSERT_FALSE(menu.ItemsOverflow(&message)); + for (size_t i = 0; i < menu.ItemsCount() - 1; i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + // Test of the last item is truncated + ASSERT_EQ("12345678", menu.TextItem(4)); + ASSERT_EQ(1, menu.selection()); +} + +TEST(ScreenUITest, StartPhoneMenuItemsOverflow) { + Menu menu(false, 1, 20); + ASSERT_FALSE(menu.scrollable()); + + menu.Start(HEADER, ITEMS, 0); + ASSERT_EQ(1u, menu.ItemsCount()); + + std::string message; + ASSERT_FALSE(menu.ItemsOverflow(&message)); + for (size_t i = 0; i < menu.ItemsCount(); i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(1u, menu.MenuEnd()); +} + +TEST(ScreenUITest, StartWearMenuItemsOverflow) { + Menu menu(true, 1, 20); + ASSERT_TRUE(menu.scrollable()); + + menu.Start(HEADER, ITEMS, 0); + ASSERT_EQ(5u, menu.ItemsCount()); + + std::string message; + ASSERT_TRUE(menu.ItemsOverflow(&message)); + ASSERT_EQ("Current item: 1/5", message); + + for (size_t i = 0; i < menu.ItemsCount(); i++) { + ASSERT_EQ(ITEMS[i], menu.TextItem(i)); + } + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(1u, menu.MenuEnd()); +} + +TEST(ScreenUITest, PhoneMenuSelectSmoke) { + Menu menu(false, 10, 20); + + int sel = 0; + menu.Start(HEADER, ITEMS, sel); + // Mimic down button 10 times (2 * items size) + for (int i = 0; i < 10; i++) { + sel = menu.Select(++sel); + ASSERT_EQ(sel, menu.selection()); + + // Wraps the selection for unscrollable menu when it reaches the boundary. + int expected = (i + 1) % 5; + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } + + // Mimic up button 10 times + for (int i = 0; i < 10; i++) { + sel = menu.Select(--sel); + ASSERT_EQ(sel, menu.selection()); + + int expected = (9 - i) % 5; + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } +} + +TEST(ScreenUITest, WearMenuSelectSmoke) { + Menu menu(true, 10, 20); + + int sel = 0; + menu.Start(HEADER, ITEMS, sel); + // Mimic pressing down button 10 times (2 * items size) + for (int i = 0; i < 10; i++) { + sel = menu.Select(++sel); + ASSERT_EQ(sel, menu.selection()); + + // Stops the selection at the boundary if the menu is scrollable. + int expected = std::min(i + 1, 4); + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } + + // Mimic pressing up button 10 times + for (int i = 0; i < 10; i++) { + sel = menu.Select(--sel); + ASSERT_EQ(sel, menu.selection()); + + int expected = std::max(3 - i, 0); + ASSERT_EQ(expected, menu.selection()); + + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + } +} + +TEST(ScreenUITest, WearMenuSelectItemsOverflow) { + Menu menu(true, 3, 20); + + int sel = 1; + menu.Start(HEADER, ITEMS, sel); + ASSERT_EQ(5u, menu.ItemsCount()); + + // Scroll the menu to the end, and check the start & end of menu. + for (int i = 0; i < 3; i++) { + sel = menu.Select(++sel); + ASSERT_EQ(i + 2, sel); + ASSERT_EQ(static_cast(i), menu.MenuStart()); + ASSERT_EQ(static_cast(i + 3), menu.MenuEnd()); + } + + // Press down button one more time won't change the MenuStart() and MenuEnd(). + sel = menu.Select(++sel); + ASSERT_EQ(4, sel); + ASSERT_EQ(2u, menu.MenuStart()); + ASSERT_EQ(5u, menu.MenuEnd()); + + // Scroll the menu to the top. + // The expected menu sel, start & ends are: + // sel 3, start 2, end 5 + // sel 2, start 2, end 5 + // sel 1, start 1, end 4 + // sel 0, start 0, end 3 + for (int i = 0; i < 4; i++) { + sel = menu.Select(--sel); + ASSERT_EQ(3 - i, sel); + ASSERT_EQ(static_cast(std::min(3 - i, 2)), menu.MenuStart()); + ASSERT_EQ(static_cast(std::min(6 - i, 5)), menu.MenuEnd()); + } + + // Press up button one more time won't change the MenuStart() and MenuEnd(). + sel = menu.Select(--sel); + ASSERT_EQ(0, sel); + ASSERT_EQ(0u, menu.MenuStart()); + ASSERT_EQ(3u, menu.MenuEnd()); +} diff --git a/wear_ui.cpp b/wear_ui.cpp index ca6b1b102..118e43508 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -17,7 +17,6 @@ #include "wear_ui.h" #include -#include // TODO: Remove after killing the call to sprintf(). #include #include @@ -27,7 +26,8 @@ #include WearRecoveryUI::WearRecoveryUI() - : kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE), + : ScreenRecoveryUI(true), + kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE), kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) { // TODO: kMenuUnusableRows should be computed based on the lines in draw_screen_locked(). @@ -65,13 +65,10 @@ static const char* SWIPE_HELP[] = { "Swipe up/down to move.", "Swipe left/right to select.", "", - NULL + nullptr, }; -// TODO merge drawing routines with screen_ui void WearRecoveryUI::draw_screen_locked() { - char cur_selection_str[50]; - draw_background_locked(); if (!show_text) { draw_foreground_locked(); @@ -79,68 +76,7 @@ void WearRecoveryUI::draw_screen_locked() { SetColor(TEXT_FILL); gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - int y = kMarginHeight; - int x = kMarginWidth; - if (show_menu) { - std::string recovery_fingerprint = - android::base::GetProperty("ro.bootimage.build.fingerprint", ""); - SetColor(HEADER); - y += DrawTextLine(x + 4, y, "Android Recovery", true); - for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - y += DrawTextLine(x + 4, y, chunk.c_str(), false); - } - - // This is actually the help strings. - y += DrawTextLines(x + 4, y, SWIPE_HELP); - SetColor(HEADER); - y += DrawTextLines(x + 4, y, menu_headers_); - - // Show the current menu item number in relation to total number if - // items don't fit on the screen. - if (menu_items > menu_end - menu_start) { - sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); - gr_text(gr_sys_font(), x + 4, y, cur_selection_str, 1); - y += char_height_ + 4; - } - - // Menu begins here - SetColor(MENU); - - for (int i = menu_start; i < menu_end; ++i) { - if (i == menu_sel) { - // draw the highlight bar - SetColor(MENU_SEL_BG); - gr_fill(x, y - 2, gr_fb_width() - x, y + char_height_ + 2); - // white text of selected item - SetColor(MENU_SEL_FG); - if (menu_[i][0]) { - gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 1); - } - SetColor(MENU); - } else if (menu_[i][0]) { - gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 0); - } - y += char_height_ + 4; - } - SetColor(MENU); - y += 4; - gr_fill(0, y, gr_fb_width(), y + 2); - y += 4; - } - - SetColor(LOG); - - // display from the bottom up, until we hit the top of the - // screen, the bottom of the menu, or we've displayed the - // entire text buffer. - int row = text_row_; - size_t count = 0; - for (int ty = gr_fb_height() - char_height_ - kMarginHeight; ty > y + 2 && count < text_rows_; - ty -= char_height_, ++count) { - gr_text(gr_sys_font(), x + 4, ty, text_[row], 0); - --row; - if (row < 0) row = text_rows_ - 1; - } + draw_menu_and_text_buffer_locked(SWIPE_HELP); } } @@ -156,45 +92,11 @@ void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* it int initial_selection) { pthread_mutex_lock(&updateMutex); if (text_rows_ > 0 && text_cols_ > 0) { - menu_headers_ = headers; - menu_.clear(); - // "i < text_rows_" is removed from the loop termination condition, - // which is different from the one in ScreenRecoveryUI::StartMenu(). - // Because WearRecoveryUI supports scrollable menu, it's fine to have - // more entries than text_rows_. The menu may be truncated otherwise. - // Bug: 23752519 - for (size_t i = 0; items[i] != nullptr; i++) { - menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1))); - } - menu_items = static_cast(menu_.size()); - show_menu = true; - menu_sel = initial_selection; - menu_start = 0; - menu_end = text_rows_ - 1 - kMenuUnusableRows; - if (menu_items <= menu_end) menu_end = menu_items; - update_screen_locked(); - } - pthread_mutex_unlock(&updateMutex); -} + menu_ = std::make_unique(scrollable_menu_, text_rows_ - kMenuUnusableRows - 1, + text_cols_ - 1); + menu_->Start(headers, items, initial_selection); -int WearRecoveryUI::SelectMenu(int sel) { - int old_sel; - pthread_mutex_lock(&updateMutex); - if (show_menu) { - old_sel = menu_sel; - menu_sel = sel; - if (menu_sel < 0) menu_sel = 0; - if (menu_sel >= menu_items) menu_sel = menu_items - 1; - if (menu_sel < menu_start) { - menu_start--; - menu_end--; - } else if (menu_sel >= menu_end && menu_sel < menu_items) { - menu_end++; - menu_start++; - } - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); + update_screen_locked(); } pthread_mutex_unlock(&updateMutex); - return sel; -} +} \ No newline at end of file diff --git a/wear_ui.h b/wear_ui.h index 739b4cb1d..8b24cb73e 100644 --- a/wear_ui.h +++ b/wear_ui.h @@ -25,10 +25,8 @@ class WearRecoveryUI : public ScreenRecoveryUI { void SetStage(int current, int max) override; - // menu display void StartMenu(const char* const* headers, const char* const* items, int initial_selection) override; - int SelectMenu(int sel) override; protected: // progress bar vertical position, it's centered horizontally @@ -45,8 +43,6 @@ class WearRecoveryUI : public ScreenRecoveryUI { private: void draw_background_locked() override; void draw_screen_locked() override; - - int menu_start, menu_end; }; #endif // RECOVERY_WEAR_UI_H -- cgit v1.2.3 From 6ec9ddddda7f280adebf0006e2546b3a54678aa2 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 26 Apr 2018 10:24:03 -0700 Subject: Mark ui_print with __printflike. And fix an issue as a result of the change. Test: mmma -j bootable/recovery Change-Id: I94e6384a1f39e9c37a8ed029d235142738d6e5d3 (cherry picked from commit 8af89c3a02c1bf358fc3c3b4e0a7cedc8f48631e) --- common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.h b/common.h index a85130698..4228e71de 100644 --- a/common.h +++ b/common.h @@ -40,7 +40,7 @@ extern const char* reason; // fopen a file, mounting volumes and making parent dirs as necessary. FILE* fopen_path(const std::string& path, const char* mode); -void ui_print(const char* format, ...); +void ui_print(const char* format, ...) __printflike(1, 2); bool is_ro_debuggable(); -- cgit v1.2.3 From 3c3f211d1e5698da6eea9e83584acb2dee4ca46e Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Wed, 2 May 2018 16:56:00 -0700 Subject: recovery: Refactor logging code into logging.cpp Move common logging related functions to rotate_logs.cpp, and rename that to logging.cpp. Test: Recovery works Bug: 78793464 Change-Id: I00f20a79a296680122b8437d54a87897c5cb2fc7 --- Android.bp | 12 ++- Android.mk | 2 +- common.h | 9 +- logging.cpp | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++ logging.h | 50 +++++++++++ recovery-persist.cpp | 2 +- recovery-refresh.cpp | 2 +- recovery.cpp | 134 ++-------------------------- recovery_main.cpp | 2 +- rotate_logs.cpp | 107 ----------------------- rotate_logs.h | 38 -------- 11 files changed, 315 insertions(+), 286 deletions(-) create mode 100644 logging.cpp create mode 100644 logging.h delete mode 100644 rotate_logs.cpp delete mode 100644 rotate_logs.h diff --git a/Android.bp b/Android.bp index 9ad961d5b..22c90bd3e 100644 --- a/Android.bp +++ b/Android.bp @@ -89,8 +89,8 @@ cc_binary { ], srcs: [ + "logging.cpp", "recovery-persist.cpp", - "rotate_logs.cpp", ], shared_libs: [ @@ -98,6 +98,10 @@ cc_binary { "liblog", ], + static_libs: [ + "libotautil", + ], + init_rc: [ "recovery-persist.rc", ], @@ -112,8 +116,8 @@ cc_binary { ], srcs: [ + "logging.cpp", "recovery-refresh.cpp", - "rotate_logs.cpp", ], shared_libs: [ @@ -121,6 +125,10 @@ cc_binary { "liblog", ], + static_libs: [ + "libotautil", + ], + init_rc: [ "recovery-refresh.rc", ], diff --git a/Android.mk b/Android.mk index eff4b015d..dbd2eb788 100644 --- a/Android.mk +++ b/Android.mk @@ -126,10 +126,10 @@ LOCAL_SRC_FILES := \ adb_install.cpp \ device.cpp \ fuse_sdcard_provider.cpp \ + logging.cpp \ recovery.cpp \ recovery_main.cpp \ roots.cpp \ - rotate_logs.cpp \ LOCAL_MODULE := recovery diff --git a/common.h b/common.h index de536fdb4..3dc36a960 100644 --- a/common.h +++ b/common.h @@ -27,7 +27,9 @@ static constexpr int kRecoveryApiVersion = 3; class RecoveryUI; +struct selabel_handle; +extern struct selabel_handle* sehandle; extern RecoveryUI* ui; extern bool modified_flash; @@ -37,13 +39,6 @@ extern std::string stage; // The reason argument provided in "--reason=". extern const char* reason; -// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the -// file pointer, or nullptr on error. -FILE* fopen_path(const std::string& path, const char* mode); - -// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream. -void check_and_fclose(FILE* fp, const std::string& name); - void ui_print(const char* format, ...) __printflike(1, 2); bool is_ro_debuggable(); diff --git a/logging.cpp b/logging.cpp new file mode 100644 index 000000000..d5af72aad --- /dev/null +++ b/logging.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2016 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 "logging.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include /* for AID_SYSTEM */ +#include /* private pmsg functions */ + +#include "common.h" +#include "otautil/dirutil.h" +#include "otautil/paths.h" +#include "roots.h" + +static constexpr const char* LOG_FILE = "/cache/recovery/log"; +static constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; +static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; +static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; + +static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg"; +static const std::string LAST_LOG_FILTER = "recovery/last_log"; + +// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the +// file pointer, or nullptr on error. +static FILE* fopen_path(const std::string& path, const char* mode) { + if (ensure_path_mounted(path.c_str()) != 0) { + LOG(ERROR) << "Can't mount " << path; + return nullptr; + } + + // When writing, try to create the containing directory, if necessary. Use generous permissions, + // the system (init.rc) will reset them. + if (strchr("wa", mode[0])) { + mkdir_recursively(path, 0777, true, sehandle); + } + return fopen(path.c_str(), mode); +} + +void check_and_fclose(FILE* fp, const std::string& name) { + fflush(fp); + if (fsync(fileno(fp)) == -1) { + PLOG(ERROR) << "Failed to fsync " << name; + } + if (ferror(fp)) { + PLOG(ERROR) << "Error in " << name; + } + fclose(fp); +} + +// close a file, log an error if the error indicator is set +ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */, + size_t len, void* arg) { + bool* do_rotate = static_cast(arg); + if (LAST_KMSG_FILTER.find(filename) != std::string::npos || + LAST_LOG_FILTER.find(filename) != std::string::npos) { + *do_rotate = true; + } + return len; +} + +ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, + void* arg) { + bool* do_rotate = static_cast(arg); + if (!*do_rotate) { + return __android_log_pmsg_file_write(id, prio, filename, buf, len); + } + + std::string name(filename); + size_t dot = name.find_last_of('.'); + std::string sub = name.substr(0, dot); + + if (LAST_KMSG_FILTER.find(sub) == std::string::npos && + LAST_LOG_FILTER.find(sub) == std::string::npos) { + return __android_log_pmsg_file_write(id, prio, filename, buf, len); + } + + // filename rotation + if (dot == std::string::npos) { + name += ".1"; + } else { + std::string number = name.substr(dot + 1); + if (!isdigit(number[0])) { + name += ".1"; + } else { + size_t i; + if (!android::base::ParseUint(number, &i)) { + LOG(ERROR) << "failed to parse uint in " << number; + return -1; + } + name = sub + "." + std::to_string(i + 1); + } + } + + return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len); +} + +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. +// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. +// Overwrite any existing last_log.$max and last_kmsg.$max. +void rotate_logs(const char* last_log_file, const char* last_kmsg_file) { + // Logs should only be rotated once. + static bool rotated = false; + if (rotated) { + return; + } + rotated = true; + + for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) { + std::string old_log = android::base::StringPrintf("%s", last_log_file); + if (i > 0) { + old_log += "." + std::to_string(i); + } + std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1); + // Ignore errors if old_log doesn't exist. + rename(old_log.c_str(), new_log.c_str()); + + std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file); + if (i > 0) { + old_kmsg += "." + std::to_string(i); + } + std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1); + rename(old_kmsg.c_str(), new_kmsg.c_str()); + } +} + +// Writes content to the current pmsg session. +static ssize_t __pmsg_write(const std::string& filename, const std::string& buf) { + return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filename.c_str(), + buf.data(), buf.size()); +} + +void copy_log_file_to_pmsg(const std::string& source, const std::string& destination) { + std::string content; + android::base::ReadFileToString(source, &content); + __pmsg_write(destination, content); +} + +// How much of the temp log we have copied to the copy in cache. +static off_t tmplog_offset = 0; + +void reset_tmplog_offset() { + tmplog_offset = 0; +} + +void copy_log_file(const std::string& source, const std::string& destination, bool append) { + FILE* dest_fp = fopen_path(destination, append ? "ae" : "we"); + if (dest_fp == nullptr) { + PLOG(ERROR) << "Can't open " << destination; + } else { + FILE* source_fp = fopen(source.c_str(), "re"); + if (source_fp != nullptr) { + if (append) { + fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write + } + char buf[4096]; + size_t bytes; + while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { + fwrite(buf, 1, bytes, dest_fp); + } + if (append) { + tmplog_offset = ftello(source_fp); + } + check_and_fclose(source_fp, source); + } + check_and_fclose(dest_fp, destination); + } +} + +void copy_logs(bool modified_flash, bool has_cache) { + // We only rotate and record the log of the current session if there are actual attempts to modify + // the flash, such as wipes, installs from BCB or menu selections. This is to avoid unnecessary + // rotation (and possible deletion) of log files, if it does not do anything loggable. + if (!modified_flash) { + return; + } + + // Always write to pmsg, this allows the OTA logs to be caught in `logcat -L`. + copy_log_file_to_pmsg(Paths::Get().temporary_log_file(), LAST_LOG_FILE); + copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE); + + // We can do nothing for now if there's no /cache partition. + if (!has_cache) { + return; + } + + ensure_path_mounted(LAST_LOG_FILE); + ensure_path_mounted(LAST_KMSG_FILE); + rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); + + // Copy logs to cache so the system can find out what happened. + copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true); + copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false); + copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false); + save_kernel_log(LAST_KMSG_FILE); + chmod(LOG_FILE, 0600); + chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); + chmod(LAST_KMSG_FILE, 0600); + chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); + chmod(LAST_LOG_FILE, 0640); + chmod(LAST_INSTALL_FILE, 0644); + sync(); +} + +// Read from kernel log into buffer and write out to file. +void save_kernel_log(const char* destination) { + int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); + if (klog_buf_len <= 0) { + PLOG(ERROR) << "Error getting klog size"; + return; + } + + std::string buffer(klog_buf_len, 0); + int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); + if (n == -1) { + PLOG(ERROR) << "Error in reading klog"; + return; + } + buffer.resize(n); + android::base::WriteStringToFile(buffer, destination); +} diff --git a/logging.h b/logging.h new file mode 100644 index 000000000..3cfbc7af6 --- /dev/null +++ b/logging.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 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 _LOGGING_H +#define _LOGGING_H + +#include +#include + +#include + +#include + +static constexpr int KEEP_LOG_COUNT = 10; + +ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len, + void* arg); + +ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, + void* arg); + +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. +// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. +// Overwrite any existing last_log.$max and last_kmsg.$max. +void rotate_logs(const char* last_log_file, const char* last_kmsg_file); + +// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream. +void check_and_fclose(FILE* fp, const std::string& name); + +void copy_log_file_to_pmsg(const std::string& source, const std::string& destination); +void copy_log_file(const std::string& source, const std::string& destination, bool append); +void copy_logs(bool modified_flash, bool has_cache); +void reset_tmplog_offset(); + +void save_kernel_log(const char* destination); + +#endif //_LOGGING_H diff --git a/recovery-persist.cpp b/recovery-persist.cpp index dbce7ff74..d3ade6260 100644 --- a/recovery-persist.cpp +++ b/recovery-persist.cpp @@ -41,7 +41,7 @@ #include #include /* private pmsg functions */ -#include "rotate_logs.h" +#include "logging.h" static const char *LAST_LOG_FILE = "/data/misc/recovery/last_log"; static const char *LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0"; diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp index 14565d3f4..aee1ca592 100644 --- a/recovery-refresh.cpp +++ b/recovery-refresh.cpp @@ -42,7 +42,7 @@ #include /* private pmsg functions */ -#include "rotate_logs.h" +#include "logging.h" int main(int argc, char **argv) { static const char filter[] = "recovery/"; diff --git a/recovery.cpp b/recovery.cpp index 00f38596c..0ab34197f 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -53,8 +52,6 @@ #include #include /* for property_list */ #include -#include /* for AID_SYSTEM */ -#include /* private pmsg functions */ #include #include #include @@ -66,21 +63,19 @@ #include "fuse_sdcard_provider.h" #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" #include "otautil/sysutil.h" #include "roots.h" -#include "rotate_logs.h" #include "screen_ui.h" #include "stub_ui.h" #include "ui.h" static constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; -static constexpr const char* LOG_FILE = "/cache/recovery/log"; -static constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; @@ -149,31 +144,6 @@ struct selabel_handle* sehandle; * 7b. the user reboots (pulling the battery, etc) into the main system */ -FILE* fopen_path(const std::string& path, const char* mode) { - if (ensure_path_mounted(path.c_str()) != 0) { - LOG(ERROR) << "Can't mount " << path; - return nullptr; - } - - // When writing, try to create the containing directory, if necessary. Use generous permissions, - // the system (init.rc) will reset them. - if (strchr("wa", mode[0])) { - mkdir_recursively(path, 0777, true, sehandle); - } - return fopen(path.c_str(), mode); -} - -void check_and_fclose(FILE* fp, const std::string& name) { - fflush(fp); - if (fsync(fileno(fp)) == -1) { - PLOG(ERROR) << "Failed to fsync " << name; - } - if (ferror(fp)) { - PLOG(ERROR) << "Error in " << name; - } - fclose(fp); -} - bool is_ro_debuggable() { return android::base::GetBoolProperty("ro.debuggable", false); } @@ -259,98 +229,6 @@ static void set_sdcard_update_bootloader_message() { } } -// Read from kernel log into buffer and write out to file. -static void save_kernel_log(const char* destination) { - int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); - if (klog_buf_len <= 0) { - PLOG(ERROR) << "Error getting klog size"; - return; - } - - std::string buffer(klog_buf_len, 0); - int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); - if (n == -1) { - PLOG(ERROR) << "Error in reading klog"; - return; - } - buffer.resize(n); - android::base::WriteStringToFile(buffer, destination); -} - -// Writes content to the current pmsg session. -static ssize_t __pmsg_write(const std::string& filename, const std::string& buf) { - return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filename.c_str(), - buf.data(), buf.size()); -} - -static void copy_log_file_to_pmsg(const std::string& source, const std::string& destination) { - std::string content; - android::base::ReadFileToString(source, &content); - __pmsg_write(destination, content); -} - -// How much of the temp log we have copied to the copy in cache. -static off_t tmplog_offset = 0; - -static void copy_log_file(const std::string& source, const std::string& destination, bool append) { - FILE* dest_fp = fopen_path(destination, append ? "ae" : "we"); - if (dest_fp == nullptr) { - PLOG(ERROR) << "Can't open " << destination; - } else { - FILE* source_fp = fopen(source.c_str(), "re"); - if (source_fp != nullptr) { - if (append) { - fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write - } - char buf[4096]; - size_t bytes; - while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { - fwrite(buf, 1, bytes, dest_fp); - } - if (append) { - tmplog_offset = ftello(source_fp); - } - check_and_fclose(source_fp, source); - } - check_and_fclose(dest_fp, destination); - } -} - -static void copy_logs() { - // We only rotate and record the log of the current session if there are actual attempts to modify - // the flash, such as wipes, installs from BCB or menu selections. This is to avoid unnecessary - // rotation (and possible deletion) of log files, if it does not do anything loggable. - if (!modified_flash) { - return; - } - - // Always write to pmsg, this allows the OTA logs to be caught in `logcat -L`. - copy_log_file_to_pmsg(Paths::Get().temporary_log_file(), LAST_LOG_FILE); - copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE); - - // We can do nothing for now if there's no /cache partition. - if (!has_cache) { - return; - } - - ensure_path_mounted(LAST_LOG_FILE); - ensure_path_mounted(LAST_KMSG_FILE); - rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); - - // Copy logs to cache so the system can find out what happened. - copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true); - copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false); - copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false); - save_kernel_log(LAST_KMSG_FILE); - chmod(LOG_FILE, 0600); - chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); - chmod(LAST_KMSG_FILE, 0600); - chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); - chmod(LAST_LOG_FILE, 0640); - chmod(LAST_INSTALL_FILE, 0644); - sync(); -} - // 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. @@ -366,7 +244,7 @@ static void finish_recovery() { } } - copy_logs(); + copy_logs(modified_flash, has_cache); // Reset to normal system boot so recovery won't cycle indefinitely. std::string err; @@ -482,8 +360,8 @@ static bool erase_volume(const char* volume) { // Any part of the log we'd copied to cache is now gone. // Reset the pointer so we copy from the beginning of the temp // log. - tmplog_offset = 0; - copy_logs(); + reset_tmplog_offset(); + copy_logs(modified_flash, has_cache); } return (result == 0); @@ -1000,7 +878,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); - copy_logs(); + copy_logs(modified_flash, has_cache); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { @@ -1394,7 +1272,7 @@ int start_recovery(int argc, char** argv) { // RETRY_LIMIT times before we abandon this OTA update. static constexpr int RETRY_LIMIT = 4; if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) { - copy_logs(); + copy_logs(modified_flash, has_cache); retry_count += 1; set_retry_bootloader_message(retry_count, args); // Print retry count on screen. diff --git a/recovery_main.cpp b/recovery_main.cpp index 9f579f7cd..3147511ee 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -24,10 +24,10 @@ #include /* private pmsg functions */ #include "common.h" +#include "logging.h" #include "minadbd/minadbd.h" #include "otautil/paths.h" #include "private/recovery.h" -#include "rotate_logs.h" #include "ui.h" static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, diff --git a/rotate_logs.cpp b/rotate_logs.cpp deleted file mode 100644 index da008792c..000000000 --- a/rotate_logs.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2016 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 "rotate_logs.h" - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include /* private pmsg functions */ - -static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg"; -static const std::string LAST_LOG_FILTER = "recovery/last_log"; - -ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */, - size_t len, void* arg) { - bool* do_rotate = static_cast(arg); - if (LAST_KMSG_FILTER.find(filename) != std::string::npos || - LAST_LOG_FILTER.find(filename) != std::string::npos) { - *do_rotate = true; - } - return len; -} - -ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, - void* arg) { - bool* do_rotate = static_cast(arg); - if (!*do_rotate) { - return __android_log_pmsg_file_write(id, prio, filename, buf, len); - } - - std::string name(filename); - size_t dot = name.find_last_of('.'); - std::string sub = name.substr(0, dot); - - if (LAST_KMSG_FILTER.find(sub) == std::string::npos && - LAST_LOG_FILTER.find(sub) == std::string::npos) { - return __android_log_pmsg_file_write(id, prio, filename, buf, len); - } - - // filename rotation - if (dot == std::string::npos) { - name += ".1"; - } else { - std::string number = name.substr(dot + 1); - if (!isdigit(number[0])) { - name += ".1"; - } else { - size_t i; - if (!android::base::ParseUint(number, &i)) { - LOG(ERROR) << "failed to parse uint in " << number; - return -1; - } - name = sub + "." + std::to_string(i + 1); - } - } - - return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len); -} - -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -void rotate_logs(const char* last_log_file, const char* last_kmsg_file) { - // Logs should only be rotated once. - static bool rotated = false; - if (rotated) { - return; - } - rotated = true; - - for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) { - std::string old_log = android::base::StringPrintf("%s", last_log_file); - if (i > 0) { - old_log += "." + std::to_string(i); - } - std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1); - // Ignore errors if old_log doesn't exist. - rename(old_log.c_str(), new_log.c_str()); - - std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file); - if (i > 0) { - old_kmsg += "." + std::to_string(i); - } - std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1); - rename(old_kmsg.c_str(), new_kmsg.c_str()); - } -} diff --git a/rotate_logs.h b/rotate_logs.h deleted file mode 100644 index 007c33d44..000000000 --- a/rotate_logs.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2016 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 _ROTATE_LOGS_H -#define _ROTATE_LOGS_H - -#include -#include - -#include - -static constexpr int KEEP_LOG_COUNT = 10; - -ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len, - void* arg); - -ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, - void* arg); - -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -void rotate_logs(const char* last_log_file, const char* last_kmsg_file); - -#endif //_ROTATE_LOG_H -- cgit v1.2.3 From 92969c49dce519803ed0a1986781c474b1bbc82f Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Tue, 17 Jul 2018 14:20:55 -0700 Subject: Make recovery libraries shared / recovery_available Test: compiles Bug: 78793464 Change-Id: Iff64bc1a597e70f749a9d825f7d386baa427be3d --- Android.mk | 7 +++++++ bootloader_message/Android.bp | 5 +++-- fuse_sideload/Android.bp | 3 ++- minadbd/Android.bp | 8 +++++--- otautil/Android.bp | 5 +++-- tests/Android.mk | 7 +++++++ 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Android.mk b/Android.mk index 906fcd6a5..0a9a33a75 100644 --- a/Android.mk +++ b/Android.mk @@ -120,11 +120,18 @@ librecovery_static_libraries := \ libverifier \ libotautil \ $(health_hal_static_libraries) \ + libadbd \ libasyncio \ + libavb_user \ + libdiagnose_usb \ libcrypto_utils \ libcrypto \ libext4_utils \ libfs_mgr \ + libfec \ + libfec_rs \ + libsquashfs_utils \ + liblogwrap \ libpng \ libsparse \ libvintf_recovery \ diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp index ab23733cd..6155daad4 100644 --- a/bootloader_message/Android.bp +++ b/bootloader_message/Android.bp @@ -14,7 +14,7 @@ // limitations under the License. // -cc_library_static { +cc_library { name: "libbootloader_message", recovery_available: true, srcs: ["bootloader_message.cpp"], @@ -22,9 +22,10 @@ cc_library_static { "-Wall", "-Werror", ], - static_libs: [ + shared_libs: [ "libbase", "libfs_mgr", + "liblog", ], export_include_dirs: ["include"], } diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp index 76bc16df9..29404cea2 100644 --- a/fuse_sideload/Android.bp +++ b/fuse_sideload/Android.bp @@ -14,6 +14,7 @@ cc_library_static { name: "libfusesideload", + recovery_available: true, cflags: [ "-D_XOPEN_SOURCE", @@ -30,7 +31,7 @@ cc_library_static { "include", ], - static_libs: [ + shared_libs: [ "libbase", "libcrypto", ], diff --git a/minadbd/Android.bp b/minadbd/Android.bp index 432b2f0f5..0ef4af9b3 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -28,6 +28,7 @@ cc_defaults { cc_library_static { name: "libminadbd", + recovery_available: true, defaults: [ "minadbd_defaults", @@ -41,12 +42,12 @@ cc_library_static { static_libs: [ "libfusesideload", - "libbase", - "libcrypto", ], - whole_static_libs: [ + shared_libs: [ "libadbd", + "libbase", + "libcrypto", ], } @@ -67,6 +68,7 @@ cc_test { ], shared_libs: [ + "libadbd", "libbase", "libcutils", "liblog", diff --git a/otautil/Android.bp b/otautil/Android.bp index b058f7b35..16af7e781 100644 --- a/otautil/Android.bp +++ b/otautil/Android.bp @@ -16,6 +16,7 @@ cc_library_static { name: "libotautil", host_supported: true, + recovery_available: true, // Minimal set of files to support host build. srcs: [ @@ -23,7 +24,7 @@ cc_library_static { "rangeset.cpp", ], - static_libs: [ + shared_libs: [ "libbase", ], @@ -46,7 +47,7 @@ cc_library_static { "thermalutil.cpp", ], - static_libs: [ + shared_libs: [ "libselinux", "libcutils", ], diff --git a/tests/Android.mk b/tests/Android.mk index daec11f11..de55587fe 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -176,10 +176,17 @@ librecovery_static_libraries := \ libotautil \ $(health_hal_static_libraries) \ libasyncio \ + libadbd \ + libavb_user \ + libdiagnose_usb \ libcrypto_utils \ libcrypto \ libext4_utils \ libfs_mgr \ + libfec \ + libfec_rs \ + libsquashfs_utils \ + liblogwrap \ libpng \ libsparse \ libvintf_recovery \ -- cgit v1.2.3 From df115ad5371726f8c4c0bd92fcd652ef5e85992c Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 30 Oct 2018 19:01:50 -0700 Subject: Import translations. DO NOT MERGE Change-Id: Icfe0dc21567e74da70cc7b2f1229815bceeac958 Auto-generated-cl: translation import --- tools/recovery_l10n/res/values-af/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-am/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ar/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-as/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-az/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-b+sr+Latn/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-be/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-bg/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-bn/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-bs/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ca/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-cs/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-da/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-de/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-el/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-en-rAU/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-en-rCA/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-en-rGB/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-en-rIN/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-en-rXC/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-es-rUS/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-es/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-et/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-eu/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-fa/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-fi/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-fr-rCA/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-fr/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-gl/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-gu/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-hi/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-hr/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-hu/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-hy/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-in/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-is/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-it/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-iw/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ja/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ka/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-kk/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-km/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-kn/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ko/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ky/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-lo/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-lt/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-lv/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-mk/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ml/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-mn/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-mr/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ms/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-my/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-nb/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ne/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-nl/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-or/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-pa/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-pl/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-pt-rBR/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-pt-rPT/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-pt/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ro/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ru/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-si/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-sk/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-sl/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-sq/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-sr/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-sv/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-sw/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ta/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-te/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-th/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-tl/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-tr/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-uk/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-ur/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-uz/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-vi/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-zh-rCN/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-zh-rHK/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-zh-rTW/strings.xml | 10 ++++++++++ tools/recovery_l10n/res/values-zu/strings.xml | 10 ++++++++++ 85 files changed, 850 insertions(+) diff --git a/tools/recovery_l10n/res/values-af/strings.xml b/tools/recovery_l10n/res/values-af/strings.xml index b1974da20..5439d939d 100644 --- a/tools/recovery_l10n/res/values-af/strings.xml +++ b/tools/recovery_l10n/res/values-af/strings.xml @@ -6,4 +6,14 @@ "Geen opdrag nie" "Fout!" "Installeer tans sekuriteitopdatering" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-am/strings.xml b/tools/recovery_l10n/res/values-am/strings.xml index 75c17fbad..8c4c18229 100644 --- a/tools/recovery_l10n/res/values-am/strings.xml +++ b/tools/recovery_l10n/res/values-am/strings.xml @@ -6,4 +6,14 @@ "ምንም ትዕዛዝ የለም" "ስህተት!" "የደህንነት ዝማኔ በመጫን ላይ" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml index 601b5832b..b9b2eef73 100644 --- a/tools/recovery_l10n/res/values-ar/strings.xml +++ b/tools/recovery_l10n/res/values-ar/strings.xml @@ -6,4 +6,14 @@ "ليس هناك أي أمر" "خطأ!" "جارٍ تثبيت تحديث الأمان" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-as/strings.xml b/tools/recovery_l10n/res/values-as/strings.xml index 2624cebe4..286d95f6e 100644 --- a/tools/recovery_l10n/res/values-as/strings.xml +++ b/tools/recovery_l10n/res/values-as/strings.xml @@ -6,4 +6,14 @@ "কোনো আদেশ নাই" "ত্ৰুটি!" "সুৰক্ষা আপডেইট ইনষ্টল কৰি থকা হৈছে" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-az/strings.xml b/tools/recovery_l10n/res/values-az/strings.xml index c6765a9ea..a3f8f57b0 100644 --- a/tools/recovery_l10n/res/values-az/strings.xml +++ b/tools/recovery_l10n/res/values-az/strings.xml @@ -6,4 +6,14 @@ "Əmr yoxdur" "Xəta!" "Təhlükəsizlik güncəlləməsi yüklənir" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml index c2d8f2239..db612b34c 100644 --- a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml +++ b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml @@ -6,4 +6,14 @@ "Nema komande" "Greška!" "Instalira se bezbednosno ažuriranje" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-be/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml index 7c0954d31..9514a1be2 100644 --- a/tools/recovery_l10n/res/values-be/strings.xml +++ b/tools/recovery_l10n/res/values-be/strings.xml @@ -6,4 +6,14 @@ "Няма каманды" "Памылка" "Усталёўка абнаўлення сістэмы бяспекі" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-bg/strings.xml b/tools/recovery_l10n/res/values-bg/strings.xml index 9e628a2af..e7d790c5f 100644 --- a/tools/recovery_l10n/res/values-bg/strings.xml +++ b/tools/recovery_l10n/res/values-bg/strings.xml @@ -6,4 +6,14 @@ "Без команда" "Грешка!" "Актуализацията на сигурносттa се инсталира" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-bn/strings.xml b/tools/recovery_l10n/res/values-bn/strings.xml index 0a481faf1..5dba0f63e 100644 --- a/tools/recovery_l10n/res/values-bn/strings.xml +++ b/tools/recovery_l10n/res/values-bn/strings.xml @@ -6,4 +6,14 @@ "কোনো আদেশ নেই" "ত্রুটি!" "নিরাপত্তার আপডেট ইনস্টল করা হচ্ছে" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-bs/strings.xml b/tools/recovery_l10n/res/values-bs/strings.xml index 412cf0276..d9b397f35 100644 --- a/tools/recovery_l10n/res/values-bs/strings.xml +++ b/tools/recovery_l10n/res/values-bs/strings.xml @@ -6,4 +6,14 @@ "Nema komande" "Greška!" "Instaliranje sigurnosnog ažuriranja…" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ca/strings.xml b/tools/recovery_l10n/res/values-ca/strings.xml index 3f266d2df..43b08d930 100644 --- a/tools/recovery_l10n/res/values-ca/strings.xml +++ b/tools/recovery_l10n/res/values-ca/strings.xml @@ -6,4 +6,14 @@ "No hi ha cap ordre" "S\'ha produït un error" "S\'està instal·lant una actualització de seguretat" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-cs/strings.xml b/tools/recovery_l10n/res/values-cs/strings.xml index eb436a810..fcb10c234 100644 --- a/tools/recovery_l10n/res/values-cs/strings.xml +++ b/tools/recovery_l10n/res/values-cs/strings.xml @@ -6,4 +6,14 @@ "Žádný příkaz" "Chyba!" "Instalace aktualizace zabezpečení" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-da/strings.xml b/tools/recovery_l10n/res/values-da/strings.xml index c6e64a245..4811adead 100644 --- a/tools/recovery_l10n/res/values-da/strings.xml +++ b/tools/recovery_l10n/res/values-da/strings.xml @@ -6,4 +6,14 @@ "Ingen kommando" "Fejl!" "Installerer sikkerhedsopdateringen" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-de/strings.xml b/tools/recovery_l10n/res/values-de/strings.xml index 6b6726a23..c79b88a17 100644 --- a/tools/recovery_l10n/res/values-de/strings.xml +++ b/tools/recovery_l10n/res/values-de/strings.xml @@ -6,4 +6,14 @@ "Kein Befehl" "Fehler" "Sicherheitsupdate wird installiert" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-el/strings.xml b/tools/recovery_l10n/res/values-el/strings.xml index 4cb2da5f9..c4fe60f3d 100644 --- a/tools/recovery_l10n/res/values-el/strings.xml +++ b/tools/recovery_l10n/res/values-el/strings.xml @@ -6,4 +6,14 @@ "Καμία εντολή" "Σφάλμα!" "Εγκατάσταση ενημέρωσης ασφαλείας" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-en-rAU/strings.xml b/tools/recovery_l10n/res/values-en-rAU/strings.xml index dc75c2374..25d5e3036 100644 --- a/tools/recovery_l10n/res/values-en-rAU/strings.xml +++ b/tools/recovery_l10n/res/values-en-rAU/strings.xml @@ -6,4 +6,14 @@ "No command" "Error!" "Installing security update" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-en-rCA/strings.xml b/tools/recovery_l10n/res/values-en-rCA/strings.xml index dc75c2374..25d5e3036 100644 --- a/tools/recovery_l10n/res/values-en-rCA/strings.xml +++ b/tools/recovery_l10n/res/values-en-rCA/strings.xml @@ -6,4 +6,14 @@ "No command" "Error!" "Installing security update" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-en-rGB/strings.xml b/tools/recovery_l10n/res/values-en-rGB/strings.xml index dc75c2374..25d5e3036 100644 --- a/tools/recovery_l10n/res/values-en-rGB/strings.xml +++ b/tools/recovery_l10n/res/values-en-rGB/strings.xml @@ -6,4 +6,14 @@ "No command" "Error!" "Installing security update" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-en-rIN/strings.xml b/tools/recovery_l10n/res/values-en-rIN/strings.xml index dc75c2374..25d5e3036 100644 --- a/tools/recovery_l10n/res/values-en-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-en-rIN/strings.xml @@ -6,4 +6,14 @@ "No command" "Error!" "Installing security update" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-en-rXC/strings.xml b/tools/recovery_l10n/res/values-en-rXC/strings.xml index 2d528b3fb..9419a0a01 100644 --- a/tools/recovery_l10n/res/values-en-rXC/strings.xml +++ b/tools/recovery_l10n/res/values-en-rXC/strings.xml @@ -6,4 +6,14 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-es-rUS/strings.xml b/tools/recovery_l10n/res/values-es-rUS/strings.xml index 06b86069b..5451372a7 100644 --- a/tools/recovery_l10n/res/values-es-rUS/strings.xml +++ b/tools/recovery_l10n/res/values-es-rUS/strings.xml @@ -6,4 +6,14 @@ "Ningún comando" "Error" "Instalando actualización de seguridad" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-es/strings.xml b/tools/recovery_l10n/res/values-es/strings.xml index d8618f2f4..e0496b2fc 100644 --- a/tools/recovery_l10n/res/values-es/strings.xml +++ b/tools/recovery_l10n/res/values-es/strings.xml @@ -6,4 +6,14 @@ "Sin comandos" "Error" "Instalando actualización de seguridad" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-et/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml index 072a9ef80..6346e072d 100644 --- a/tools/recovery_l10n/res/values-et/strings.xml +++ b/tools/recovery_l10n/res/values-et/strings.xml @@ -6,4 +6,14 @@ "Käsk puudub" "Viga!" "Turvavärskenduse installimine" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-eu/strings.xml b/tools/recovery_l10n/res/values-eu/strings.xml index 5540469d0..033098b35 100644 --- a/tools/recovery_l10n/res/values-eu/strings.xml +++ b/tools/recovery_l10n/res/values-eu/strings.xml @@ -6,4 +6,14 @@ "Ez dago agindurik" "Errorea" "Segurtasun-eguneratzea instalatzen" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-fa/strings.xml b/tools/recovery_l10n/res/values-fa/strings.xml index cc390ae84..de9a24097 100644 --- a/tools/recovery_l10n/res/values-fa/strings.xml +++ b/tools/recovery_l10n/res/values-fa/strings.xml @@ -6,4 +6,14 @@ "فرمانی وجود ندارد" "خطا!" "در حال نصب به‌روزرسانی امنیتی" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-fi/strings.xml b/tools/recovery_l10n/res/values-fi/strings.xml index 5141642c8..8d8cb914b 100644 --- a/tools/recovery_l10n/res/values-fi/strings.xml +++ b/tools/recovery_l10n/res/values-fi/strings.xml @@ -6,4 +6,14 @@ "Ei komentoa" "Virhe!" "Asennetaan tietoturvapäivitystä" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-fr-rCA/strings.xml b/tools/recovery_l10n/res/values-fr-rCA/strings.xml index b2415290b..197830840 100644 --- a/tools/recovery_l10n/res/values-fr-rCA/strings.xml +++ b/tools/recovery_l10n/res/values-fr-rCA/strings.xml @@ -6,4 +6,14 @@ "Aucune commande" "Erreur!" "Installation de la mise à jour de sécurité en cours..." + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-fr/strings.xml b/tools/recovery_l10n/res/values-fr/strings.xml index f0472b5ac..599233d07 100644 --- a/tools/recovery_l10n/res/values-fr/strings.xml +++ b/tools/recovery_l10n/res/values-fr/strings.xml @@ -6,4 +6,14 @@ "Aucune commande" "Erreur !" "Installation de la mise à jour de sécurité…" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml index 42b2016c2..8c652d621 100644 --- a/tools/recovery_l10n/res/values-gl/strings.xml +++ b/tools/recovery_l10n/res/values-gl/strings.xml @@ -6,4 +6,14 @@ "Non hai ningún comando" "Erro" "Instalando actualización de seguranza" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-gu/strings.xml b/tools/recovery_l10n/res/values-gu/strings.xml index 2355a0f4f..84b4f4515 100644 --- a/tools/recovery_l10n/res/values-gu/strings.xml +++ b/tools/recovery_l10n/res/values-gu/strings.xml @@ -6,4 +6,14 @@ "કોઈ આદેશ નથી" "ભૂલ!" "સુરક્ષા અપડેટ ઇન્સ્ટૉલ કરી રહ્યાં છે" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml index 65d003352..50248b599 100644 --- a/tools/recovery_l10n/res/values-hi/strings.xml +++ b/tools/recovery_l10n/res/values-hi/strings.xml @@ -6,4 +6,14 @@ "कोई निर्देश नहीं मिला" "गड़बड़ी!" "सुरक्षा अपडेट इंस्टॉल किया जा रहा है" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-hr/strings.xml b/tools/recovery_l10n/res/values-hr/strings.xml index 3b75ff115..001e33493 100644 --- a/tools/recovery_l10n/res/values-hr/strings.xml +++ b/tools/recovery_l10n/res/values-hr/strings.xml @@ -6,4 +6,14 @@ "Nema naredbe" "Pogreška!" "Instaliranje sigurnosnog ažuriranja" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-hu/strings.xml b/tools/recovery_l10n/res/values-hu/strings.xml index 12d4d9fe7..5a6d60189 100644 --- a/tools/recovery_l10n/res/values-hu/strings.xml +++ b/tools/recovery_l10n/res/values-hu/strings.xml @@ -6,4 +6,14 @@ "Nincs parancs" "Hiba!" "Biztonsági frissítés telepítése" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-hy/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml index 9d62bb763..a652e3392 100644 --- a/tools/recovery_l10n/res/values-hy/strings.xml +++ b/tools/recovery_l10n/res/values-hy/strings.xml @@ -6,4 +6,14 @@ "Հրամանը տրված չէ" "Սխալ" "Անվտանգության թարմացման տեղադրում" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml index 0e56e0dd9..b34e39419 100644 --- a/tools/recovery_l10n/res/values-in/strings.xml +++ b/tools/recovery_l10n/res/values-in/strings.xml @@ -6,4 +6,14 @@ "Tidak ada perintah" "Error!" "Memasang pembaruan keamanan" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-is/strings.xml b/tools/recovery_l10n/res/values-is/strings.xml index 5065b6522..d1e790a23 100644 --- a/tools/recovery_l10n/res/values-is/strings.xml +++ b/tools/recovery_l10n/res/values-is/strings.xml @@ -6,4 +6,14 @@ "Engin skipun" "Villa!" "Setur upp öryggisuppfærslu" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-it/strings.xml b/tools/recovery_l10n/res/values-it/strings.xml index 2c0364e60..12c627526 100644 --- a/tools/recovery_l10n/res/values-it/strings.xml +++ b/tools/recovery_l10n/res/values-it/strings.xml @@ -6,4 +6,14 @@ "Nessun comando" "Errore!" "Installazione aggiornamento sicurezza…" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-iw/strings.xml b/tools/recovery_l10n/res/values-iw/strings.xml index ea5e6f2c9..42d931b5d 100644 --- a/tools/recovery_l10n/res/values-iw/strings.xml +++ b/tools/recovery_l10n/res/values-iw/strings.xml @@ -6,4 +6,14 @@ "אין פקודה" "שגיאה!" "מתקין עדכון אבטחה" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml index 36e029b0f..4e832117c 100644 --- a/tools/recovery_l10n/res/values-ja/strings.xml +++ b/tools/recovery_l10n/res/values-ja/strings.xml @@ -6,4 +6,14 @@ "コマンドが指定されていません" "エラーが発生しました。" "セキュリティ アップデートをインストールしています" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ka/strings.xml b/tools/recovery_l10n/res/values-ka/strings.xml index 6a46b3677..0c6f49a7b 100644 --- a/tools/recovery_l10n/res/values-ka/strings.xml +++ b/tools/recovery_l10n/res/values-ka/strings.xml @@ -6,4 +6,14 @@ "ბრძანება არ არის" "წარმოიქმნა შეცდომა!" "მიმდინარეობს უსაფრთხოების განახლების ინსტალაცია" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-kk/strings.xml b/tools/recovery_l10n/res/values-kk/strings.xml index a4bd86e66..787bda07b 100644 --- a/tools/recovery_l10n/res/values-kk/strings.xml +++ b/tools/recovery_l10n/res/values-kk/strings.xml @@ -6,4 +6,14 @@ "Пәрмен жоқ" "Қате!" "Қауіпсіздік жаңартуы орнатылуда" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-km/strings.xml b/tools/recovery_l10n/res/values-km/strings.xml index 313c0f457..355bffd2c 100644 --- a/tools/recovery_l10n/res/values-km/strings.xml +++ b/tools/recovery_l10n/res/values-km/strings.xml @@ -6,4 +6,14 @@ "គ្មានពាក្យបញ្ជាទេ" "កំហុស!" "កំពុងដំឡើងការអាប់ដេតសុវត្ថិភាព" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-kn/strings.xml b/tools/recovery_l10n/res/values-kn/strings.xml index 5bf6260ee..ef6b08012 100644 --- a/tools/recovery_l10n/res/values-kn/strings.xml +++ b/tools/recovery_l10n/res/values-kn/strings.xml @@ -6,4 +6,14 @@ "ಯಾವುದೇ ಆದೇಶವಿಲ್ಲ" "ದೋಷ!" "ಭದ್ರತೆಯ ಅಪ್‌ಡೇಟ್‌ ಸ್ಥಾಪಿಸಲಾಗುತ್ತಿದೆ" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ko/strings.xml b/tools/recovery_l10n/res/values-ko/strings.xml index aca13bbe7..3f83b2058 100644 --- a/tools/recovery_l10n/res/values-ko/strings.xml +++ b/tools/recovery_l10n/res/values-ko/strings.xml @@ -6,4 +6,14 @@ "명령어 없음" "오류!" "보안 업데이트 설치 중" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ky/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml index 0a6bd783a..3832f33e7 100644 --- a/tools/recovery_l10n/res/values-ky/strings.xml +++ b/tools/recovery_l10n/res/values-ky/strings.xml @@ -6,4 +6,14 @@ "Буйрук берилген жок" "Ката!" "Коопсуздук жаңыртуусу орнотулууда" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-lo/strings.xml b/tools/recovery_l10n/res/values-lo/strings.xml index d3dbb3970..f5224ff3b 100644 --- a/tools/recovery_l10n/res/values-lo/strings.xml +++ b/tools/recovery_l10n/res/values-lo/strings.xml @@ -6,4 +6,14 @@ "ບໍ່ມີຄຳສັ່ງ" "ຜິດພາດ!" "ກຳລັງຕິດຕັ້ງອັບເດດຄວາມປອດໄພ" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-lt/strings.xml b/tools/recovery_l10n/res/values-lt/strings.xml index d5d5e88fd..1ec3586b2 100644 --- a/tools/recovery_l10n/res/values-lt/strings.xml +++ b/tools/recovery_l10n/res/values-lt/strings.xml @@ -6,4 +6,14 @@ "Nėra jokių komandų" "Klaida!" "Diegiamas saugos naujinys" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-lv/strings.xml b/tools/recovery_l10n/res/values-lv/strings.xml index d877f6a61..83b059405 100644 --- a/tools/recovery_l10n/res/values-lv/strings.xml +++ b/tools/recovery_l10n/res/values-lv/strings.xml @@ -6,4 +6,14 @@ "Nav nevienas komandas" "Kļūda!" "Notiek drošības atjauninājuma instalēšana" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-mk/strings.xml b/tools/recovery_l10n/res/values-mk/strings.xml index 351459730..39b4fa42c 100644 --- a/tools/recovery_l10n/res/values-mk/strings.xml +++ b/tools/recovery_l10n/res/values-mk/strings.xml @@ -6,4 +6,14 @@ "Нема наредба" "Грешка!" "Се инсталира безбедносно ажурирање" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ml/strings.xml b/tools/recovery_l10n/res/values-ml/strings.xml index b506e2530..752a3ed12 100644 --- a/tools/recovery_l10n/res/values-ml/strings.xml +++ b/tools/recovery_l10n/res/values-ml/strings.xml @@ -6,4 +6,14 @@ "കമാൻഡ് ഒന്നുമില്ല" "പിശക്!" "സുരക്ഷാ അപ്ഡേറ്റ് ഇൻസ്റ്റാൾ ചെയ്യുന്നു" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-mn/strings.xml b/tools/recovery_l10n/res/values-mn/strings.xml index e3dd2e90e..df61476c0 100644 --- a/tools/recovery_l10n/res/values-mn/strings.xml +++ b/tools/recovery_l10n/res/values-mn/strings.xml @@ -6,4 +6,14 @@ "Тушаал байхгүй" "Алдаа!" "Аюулгүй байдлын шинэчлэлтийг суулгаж байна" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-mr/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml index 5f820336f..f96ddea9b 100644 --- a/tools/recovery_l10n/res/values-mr/strings.xml +++ b/tools/recovery_l10n/res/values-mr/strings.xml @@ -6,4 +6,14 @@ "कोणतीही कमांड नाही" "एरर!" "सुरक्षा अपडेट इंस्टॉल करत आहे" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ms/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml index 0e24ac4e1..7a3480ee9 100644 --- a/tools/recovery_l10n/res/values-ms/strings.xml +++ b/tools/recovery_l10n/res/values-ms/strings.xml @@ -6,4 +6,14 @@ "Tiada perintah" "Ralat!" "Memasang kemas kini keselamatan" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-my/strings.xml b/tools/recovery_l10n/res/values-my/strings.xml index f13752461..1dc11e00d 100644 --- a/tools/recovery_l10n/res/values-my/strings.xml +++ b/tools/recovery_l10n/res/values-my/strings.xml @@ -6,4 +6,14 @@ "ညွှန်ကြားချက်မပေးထားပါ" "မှားနေပါသည်!" "လုံခြုံရေး အပ်ဒိတ်ကို ထည့်သွင်းနေသည်" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-nb/strings.xml b/tools/recovery_l10n/res/values-nb/strings.xml index ad6f20e46..f7d23a6ea 100644 --- a/tools/recovery_l10n/res/values-nb/strings.xml +++ b/tools/recovery_l10n/res/values-nb/strings.xml @@ -6,4 +6,14 @@ "Ingen kommandoer" "Feil!" "Installerer sikkerhetsoppdateringen" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ne/strings.xml b/tools/recovery_l10n/res/values-ne/strings.xml index 1880e807b..044776740 100644 --- a/tools/recovery_l10n/res/values-ne/strings.xml +++ b/tools/recovery_l10n/res/values-ne/strings.xml @@ -6,4 +6,14 @@ "कुनै आदेश छैन" "त्रुटि!" "सुरक्षा सम्बन्धी अद्यावधिकलाई स्थापना गर्दै" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-nl/strings.xml b/tools/recovery_l10n/res/values-nl/strings.xml index 0d6c15abb..3be534537 100644 --- a/tools/recovery_l10n/res/values-nl/strings.xml +++ b/tools/recovery_l10n/res/values-nl/strings.xml @@ -6,4 +6,14 @@ "Geen opdracht" "Fout!" "Beveiligingsupdate installeren" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-or/strings.xml b/tools/recovery_l10n/res/values-or/strings.xml index 2b0851cdd..749ad82f6 100644 --- a/tools/recovery_l10n/res/values-or/strings.xml +++ b/tools/recovery_l10n/res/values-or/strings.xml @@ -6,4 +6,14 @@ "କୌଣସି କମାଣ୍ଡ ନାହିଁ" "ତ୍ରୁଟି!" "ସୁରକ୍ଷା ଅପ୍‌ଡେଟ୍‌ ଇନ୍‌ଷ୍ଟଲ୍‌ କରୁଛି" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-pa/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml index 27972d117..0475a5e40 100644 --- a/tools/recovery_l10n/res/values-pa/strings.xml +++ b/tools/recovery_l10n/res/values-pa/strings.xml @@ -6,4 +6,14 @@ "ਕੋਈ ਆਦੇਸ਼ ਨਹੀਂ" "ਅਸ਼ੁੱਧੀ!" "ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-pl/strings.xml b/tools/recovery_l10n/res/values-pl/strings.xml index 8d6db388d..daea5bd4d 100644 --- a/tools/recovery_l10n/res/values-pl/strings.xml +++ b/tools/recovery_l10n/res/values-pl/strings.xml @@ -6,4 +6,14 @@ "Brak polecenia" "Błąd" "Instaluję aktualizację zabezpieczeń" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-pt-rBR/strings.xml b/tools/recovery_l10n/res/values-pt-rBR/strings.xml index b72704385..3d3227cba 100644 --- a/tools/recovery_l10n/res/values-pt-rBR/strings.xml +++ b/tools/recovery_l10n/res/values-pt-rBR/strings.xml @@ -6,4 +6,14 @@ "Nenhum comando" "Erro!" "Instalando atualização de segurança" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-pt-rPT/strings.xml b/tools/recovery_l10n/res/values-pt-rPT/strings.xml index 981463739..ae3938870 100644 --- a/tools/recovery_l10n/res/values-pt-rPT/strings.xml +++ b/tools/recovery_l10n/res/values-pt-rPT/strings.xml @@ -6,4 +6,14 @@ "Nenhum comando" "Erro!" "A instalar atualização de segurança" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-pt/strings.xml b/tools/recovery_l10n/res/values-pt/strings.xml index b72704385..3d3227cba 100644 --- a/tools/recovery_l10n/res/values-pt/strings.xml +++ b/tools/recovery_l10n/res/values-pt/strings.xml @@ -6,4 +6,14 @@ "Nenhum comando" "Erro!" "Instalando atualização de segurança" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ro/strings.xml b/tools/recovery_l10n/res/values-ro/strings.xml index 8032865b8..4d90718e9 100644 --- a/tools/recovery_l10n/res/values-ro/strings.xml +++ b/tools/recovery_l10n/res/values-ro/strings.xml @@ -6,4 +6,14 @@ "Nicio comandă" "Eroare!" "Se instalează actualizarea de securitate" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ru/strings.xml b/tools/recovery_l10n/res/values-ru/strings.xml index feebecf31..ab7520aeb 100644 --- a/tools/recovery_l10n/res/values-ru/strings.xml +++ b/tools/recovery_l10n/res/values-ru/strings.xml @@ -6,4 +6,14 @@ "Команды нет" "Ошибка" "Установка обновления системы безопасности…" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-si/strings.xml b/tools/recovery_l10n/res/values-si/strings.xml index 456cdc567..cffb6e9b2 100644 --- a/tools/recovery_l10n/res/values-si/strings.xml +++ b/tools/recovery_l10n/res/values-si/strings.xml @@ -6,4 +6,14 @@ "විධානයක් නොමැත" "දෝෂය!" "ආරක්ෂක යාවත්කාලීනය ස්ථාපනය කරමින්" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-sk/strings.xml b/tools/recovery_l10n/res/values-sk/strings.xml index b15f3802b..59985c0e0 100644 --- a/tools/recovery_l10n/res/values-sk/strings.xml +++ b/tools/recovery_l10n/res/values-sk/strings.xml @@ -6,4 +6,14 @@ "Žiadny príkaz" "Chyba!" "Inštaluje sa bezpečnostná aktualizácia" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-sl/strings.xml b/tools/recovery_l10n/res/values-sl/strings.xml index d608b7506..e36dfb458 100644 --- a/tools/recovery_l10n/res/values-sl/strings.xml +++ b/tools/recovery_l10n/res/values-sl/strings.xml @@ -6,4 +6,14 @@ "Ni ukaza" "Napaka" "Nameščanje varnostne posodobitve" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-sq/strings.xml b/tools/recovery_l10n/res/values-sq/strings.xml index 1156931fb..58945c0e9 100644 --- a/tools/recovery_l10n/res/values-sq/strings.xml +++ b/tools/recovery_l10n/res/values-sq/strings.xml @@ -6,4 +6,14 @@ "Nuk ka komanda" "Gabim!" "Po instalon përditësimin e sigurisë" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-sr/strings.xml b/tools/recovery_l10n/res/values-sr/strings.xml index a593d8faa..275a3aaf2 100644 --- a/tools/recovery_l10n/res/values-sr/strings.xml +++ b/tools/recovery_l10n/res/values-sr/strings.xml @@ -6,4 +6,14 @@ "Нема команде" "Грешка!" "Инсталира се безбедносно ажурирање" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-sv/strings.xml b/tools/recovery_l10n/res/values-sv/strings.xml index b33ce253f..512b5d4b4 100644 --- a/tools/recovery_l10n/res/values-sv/strings.xml +++ b/tools/recovery_l10n/res/values-sv/strings.xml @@ -6,4 +6,14 @@ "Inget kommando" "Fel!" "Säkerhetsuppdatering installeras" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-sw/strings.xml b/tools/recovery_l10n/res/values-sw/strings.xml index 156765881..b0a072f96 100644 --- a/tools/recovery_l10n/res/values-sw/strings.xml +++ b/tools/recovery_l10n/res/values-sw/strings.xml @@ -6,4 +6,14 @@ "Hakuna amri" "Hitilafu fulani imetokea!" "Inasakinisha sasisho la usalama" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ta/strings.xml b/tools/recovery_l10n/res/values-ta/strings.xml index d49186d8d..ff2e3e792 100644 --- a/tools/recovery_l10n/res/values-ta/strings.xml +++ b/tools/recovery_l10n/res/values-ta/strings.xml @@ -6,4 +6,14 @@ "கட்டளை இல்லை" "பிழை!" "பாதுகாப்புப் புதுப்பிப்பை நிறுவுகிறது" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml index e35c82bc4..66039ac96 100644 --- a/tools/recovery_l10n/res/values-te/strings.xml +++ b/tools/recovery_l10n/res/values-te/strings.xml @@ -6,4 +6,14 @@ "ఆదేశం లేదు" "ఎర్రర్ సంభవించింది!" "భద్రతా నవీకరణను ఇన్‌స్టాల్ చేస్తోంది" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-th/strings.xml b/tools/recovery_l10n/res/values-th/strings.xml index 155affea0..3b3158165 100644 --- a/tools/recovery_l10n/res/values-th/strings.xml +++ b/tools/recovery_l10n/res/values-th/strings.xml @@ -6,4 +6,14 @@ "ไม่มีคำสั่ง" "ข้อผิดพลาด!" "กำลังติดตั้งการอัปเดตความปลอดภัย" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-tl/strings.xml b/tools/recovery_l10n/res/values-tl/strings.xml index 555b42b8d..b414fd944 100644 --- a/tools/recovery_l10n/res/values-tl/strings.xml +++ b/tools/recovery_l10n/res/values-tl/strings.xml @@ -6,4 +6,14 @@ "Walang command" "Error!" "Nag-i-install ng update sa seguridad" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-tr/strings.xml b/tools/recovery_l10n/res/values-tr/strings.xml index 5387cb2ae..4e948b70d 100644 --- a/tools/recovery_l10n/res/values-tr/strings.xml +++ b/tools/recovery_l10n/res/values-tr/strings.xml @@ -6,4 +6,14 @@ "Komut yok" "Hata!" "Güvenlik güncellemesi yükleniyor" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-uk/strings.xml b/tools/recovery_l10n/res/values-uk/strings.xml index 0c2fa164a..196562070 100644 --- a/tools/recovery_l10n/res/values-uk/strings.xml +++ b/tools/recovery_l10n/res/values-uk/strings.xml @@ -6,4 +6,14 @@ "Немає команди" "Помилка!" "Установлюється оновлення системи безпеки" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-ur/strings.xml b/tools/recovery_l10n/res/values-ur/strings.xml index 12e32fbc1..9ace0f08d 100644 --- a/tools/recovery_l10n/res/values-ur/strings.xml +++ b/tools/recovery_l10n/res/values-ur/strings.xml @@ -6,4 +6,14 @@ "کوئی کمانڈ نہیں ہے" "خرابی!" "سیکیورٹی اپ ڈیٹ انسٹال ہو رہی ہے" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-uz/strings.xml b/tools/recovery_l10n/res/values-uz/strings.xml index 2c309d646..676be7a6d 100644 --- a/tools/recovery_l10n/res/values-uz/strings.xml +++ b/tools/recovery_l10n/res/values-uz/strings.xml @@ -6,4 +6,14 @@ "Buyruq yo‘q" "Xato!" "Xavfsizlik yangilanishi o‘rnatilmoqda" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-vi/strings.xml b/tools/recovery_l10n/res/values-vi/strings.xml index c77d0c8c2..fce6cd901 100644 --- a/tools/recovery_l10n/res/values-vi/strings.xml +++ b/tools/recovery_l10n/res/values-vi/strings.xml @@ -6,4 +6,14 @@ "Không có lệnh nào" "Lỗi!" "Đang cài đặt bản cập nhật bảo mật" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-zh-rCN/strings.xml b/tools/recovery_l10n/res/values-zh-rCN/strings.xml index e06149791..66c876d75 100644 --- a/tools/recovery_l10n/res/values-zh-rCN/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rCN/strings.xml @@ -6,4 +6,14 @@ "无命令" "出错了!" "正在安装安全更新" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-zh-rHK/strings.xml b/tools/recovery_l10n/res/values-zh-rHK/strings.xml index ec3315d32..5dc38d7a4 100644 --- a/tools/recovery_l10n/res/values-zh-rHK/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rHK/strings.xml @@ -6,4 +6,14 @@ "沒有指令" "錯誤!" "正在安裝安全性更新" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-zh-rTW/strings.xml b/tools/recovery_l10n/res/values-zh-rTW/strings.xml index 78eae2429..e94038b29 100644 --- a/tools/recovery_l10n/res/values-zh-rTW/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rTW/strings.xml @@ -6,4 +6,14 @@ "沒有指令" "錯誤!" "正在安裝安全性更新" + + + + + + + + + + diff --git a/tools/recovery_l10n/res/values-zu/strings.xml b/tools/recovery_l10n/res/values-zu/strings.xml index 6b815e1ab..eef2293d3 100644 --- a/tools/recovery_l10n/res/values-zu/strings.xml +++ b/tools/recovery_l10n/res/values-zu/strings.xml @@ -6,4 +6,14 @@ "Awukho umyalo" "Iphutha!" "Ifaka isibuyekezo sokuphepha" + + + + + + + + + + -- cgit v1.2.3 From f560a89470c83936f3187d96e4bbe8b21eecc8e3 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 13 Nov 2018 08:09:37 -0800 Subject: Import translations. DO NOT MERGE Change-Id: I1bd6d7f12c7bfffb7e022f09347a5be5a96766a3 Auto-generated-cl: translation import --- tools/recovery_l10n/res/values-af/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-am/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ar/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-as/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-az/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-b+sr+Latn/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-be/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-bg/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-bn/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-bs/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ca/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-cs/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-da/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-de/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-el/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-en-rAU/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-en-rCA/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-en-rGB/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-en-rIN/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-en-rXC/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-es-rUS/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-es/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-et/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-eu/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-fa/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-fi/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-fr-rCA/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-fr/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-gl/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-gu/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-hi/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-hr/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-hu/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-hy/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-in/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-is/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-it/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-iw/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ja/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ka/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-kk/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-km/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-kn/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ko/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ky/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-lo/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-lt/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-lv/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-mk/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ml/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-mn/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-mr/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ms/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-my/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-nb/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ne/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-nl/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-or/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-pa/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-pl/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-pt-rBR/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-pt-rPT/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-pt/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ro/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ru/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-si/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-sk/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-sl/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-sq/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-sr/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-sv/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-sw/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ta/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-te/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-th/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-tl/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-tr/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-uk/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-ur/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-uz/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-vi/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-zh-rCN/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-zh-rHK/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-zh-rTW/strings.xml | 15 +++++---------- tools/recovery_l10n/res/values-zu/strings.xml | 15 +++++---------- 85 files changed, 425 insertions(+), 850 deletions(-) diff --git a/tools/recovery_l10n/res/values-af/strings.xml b/tools/recovery_l10n/res/values-af/strings.xml index 5439d939d..85a3c9037 100644 --- a/tools/recovery_l10n/res/values-af/strings.xml +++ b/tools/recovery_l10n/res/values-af/strings.xml @@ -6,14 +6,9 @@ "Geen opdrag nie" "Fout!" "Installeer tans sekuriteitopdatering" - - - - - - - - - - + "Kan nie Android-stelsel laai nie. Jou data is dalk korrup. As jy aanhou om hierdie boodskap te kry, sal jy dalk \'n fabrieksterugstelling moet doen en alle gebruikerdata moet uitvee wat op hierdie toestel geberg word." + "Probeer weer" + "Fabrieksterugstelling" + "Vee alle gebruikerdata uit?\n\n DIT KAN NIE ONTDOEN WORD NIE!" + "Kanselleer" diff --git a/tools/recovery_l10n/res/values-am/strings.xml b/tools/recovery_l10n/res/values-am/strings.xml index 8c4c18229..353f2233b 100644 --- a/tools/recovery_l10n/res/values-am/strings.xml +++ b/tools/recovery_l10n/res/values-am/strings.xml @@ -6,14 +6,9 @@ "ምንም ትዕዛዝ የለም" "ስህተት!" "የደህንነት ዝማኔ በመጫን ላይ" - - - - - - - - - - + "የAndroid ስርዓትን መጫን አልተቻለም። የእርስዎ ውሂብ የተበላሸ ሊሆን ይችላል። ይህን መልዕክት ማግኘቱን ከቀጠሉ የፋብሪካ ውሂብ ዳግም ማስጀመር ማከናወንና በዚህ መሣሪያ ላይ የተከማቸ ሁሉንም የተጠቃሚ ውሂብ መሰረዝ ሊኖርብዎት ይችላል።" + "እንደገና ሞክር" + "የፋብሪካ ውሂብ ዳግም ማስጀመር" + "ሁሉም የተጠቃሚ ውሂብ ይሰረዝ?\n\n ይህ ሊቀለበስ አይችልም!" + "ይቅር" diff --git a/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml index b9b2eef73..2af36d64a 100644 --- a/tools/recovery_l10n/res/values-ar/strings.xml +++ b/tools/recovery_l10n/res/values-ar/strings.xml @@ -6,14 +6,9 @@ "ليس هناك أي أمر" "خطأ!" "جارٍ تثبيت تحديث الأمان" - - - - - - - - - - + "‏يتعذَّر تحميل نظام Android، حيث قد تكون بياناتك تالفة. وإذا استمر ظهور هذه الرسالة، قد يتعيَّن عليك إجراء إعادة الضبط بحسب بيانات المصنع ومحو جميع بيانات المستخدم المُخزَّنة على هذا الجهاز." + "إعادة المحاولة" + "إعادة الضبط بحسب بيانات المصنع" + "هل تريد حجب كل بيانات المستخدم؟\n\n لا يمكن التراجع عن هذا الإجراء." + "إلغاء" diff --git a/tools/recovery_l10n/res/values-as/strings.xml b/tools/recovery_l10n/res/values-as/strings.xml index 286d95f6e..33a204d05 100644 --- a/tools/recovery_l10n/res/values-as/strings.xml +++ b/tools/recovery_l10n/res/values-as/strings.xml @@ -6,14 +6,9 @@ "কোনো আদেশ নাই" "ত্ৰুটি!" "সুৰক্ষা আপডেইট ইনষ্টল কৰি থকা হৈছে" - - - - - - - - - - + "Android ছিষ্টেম ল\'ড কৰিব নোৱাৰি। আপোনাৰ ডেটাত কিবা আসোঁৱাহ থকা যেন লাগিছে। আপুনি যদি এই বাৰ্তাটো পায়েই থাকে, আপুনি নিজৰ ডিভাইচটো ফেক্টৰী ডেটা ৰিছেট কৰি সেইটোত থকা ব্যৱহাৰকাৰীৰ সকলো ডেটা মচিব লগা হ\'ব পাৰে।" + "আকৌ চেষ্টা কৰক" + "ফেক্টৰী ডেটা ৰিছেট" + "ব্যৱহাৰকাৰীৰ সকলো ডেটা মচিবনে?\n\n এইটো কৰাৰ পিছত আনডু কৰিব নোৱাৰি!" + "বাতিল কৰক" diff --git a/tools/recovery_l10n/res/values-az/strings.xml b/tools/recovery_l10n/res/values-az/strings.xml index a3f8f57b0..35194c4b2 100644 --- a/tools/recovery_l10n/res/values-az/strings.xml +++ b/tools/recovery_l10n/res/values-az/strings.xml @@ -6,14 +6,9 @@ "Əmr yoxdur" "Xəta!" "Təhlükəsizlik güncəlləməsi yüklənir" - - - - - - - - - - + "Android sistemi yüklənmir. Datanız zədələnə bilər. Bu mesajı yenə qəbul etsəniz, data zavod sıfırlamasını həyata keçirməli və bu cihazda saxlanmış istifadəçi datasının hamısını silməlisiniz." + "Yenidən cəhd edin" + "Data zavod sıfırlaması" + "Bütün istifadəçi datası silinsin?\n\n BU ƏMƏLİYYATI GERİ QAYTARMAQ MÜMKÜN DEYİL!" + "Ləğv edin" diff --git a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml index db612b34c..19c6f4194 100644 --- a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml +++ b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml @@ -6,14 +6,9 @@ "Nema komande" "Greška!" "Instalira se bezbednosno ažuriranje" - - - - - - - - - - + "Učitavanje Android sistema nije uspelo. Podaci su možda oštećeni. Ako nastavite da dobijate ovu poruku, možda ćete morati da resetujete uređaj na fabrička podešavanja i obrišete sve podatke korisnika koje čuvate na njemu." + "Probaj ponovo" + "Resetovanje na fabrička podešavanja" + "Želite li da izbrišete sve podatke korisnika?\n\n OVO NE MOŽE DA SE OPOZOVE!" + "Otkaži" diff --git a/tools/recovery_l10n/res/values-be/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml index 9514a1be2..ad14fbe27 100644 --- a/tools/recovery_l10n/res/values-be/strings.xml +++ b/tools/recovery_l10n/res/values-be/strings.xml @@ -6,14 +6,9 @@ "Няма каманды" "Памылка" "Усталёўка абнаўлення сістэмы бяспекі" - - - - - - - - - - + "Не ўдалося загрузіць сістэму Android. Магчыма, вашы даныя пашкоджаны. Калі вы зноў убачыце гэта паведамленне, скіньце налады прылады да заводскіх значэнняў і сатрыце ўсе карыстальніцкія даныя, якія на ёй захоўваюцца." + "Паўтарыць спробу" + "Скінуць да заводскіх налад" + "Ачысціць усе карыстальніцкія даныя?\n\n ГЭТА ДЗЕЯННЕ НЕЛЬГА АДРАБІЦЬ!" + "Скасаваць" diff --git a/tools/recovery_l10n/res/values-bg/strings.xml b/tools/recovery_l10n/res/values-bg/strings.xml index e7d790c5f..e96ff4464 100644 --- a/tools/recovery_l10n/res/values-bg/strings.xml +++ b/tools/recovery_l10n/res/values-bg/strings.xml @@ -6,14 +6,9 @@ "Без команда" "Грешка!" "Актуализацията на сигурносттa се инсталира" - - - - - - - - - - + "Системата Android не може да се зареди. Данните ви може да са повредени. Ако продължите да получавате това съобщение, може да е необходимо да възстановите фабричните настройки и да изтриете всички потребителски данни, съхранени на това устройство." + "Нов опит" + "Възстановяване на фабричните настройки" + "Да се изчистят ли всички потребителски данни?\n\n ТОВА ДЕЙСТВИЕ НЕ МОЖЕ ДА БЪДЕ ОТМЕНЕНО!" + "Отказ" diff --git a/tools/recovery_l10n/res/values-bn/strings.xml b/tools/recovery_l10n/res/values-bn/strings.xml index 5dba0f63e..5967bc4b8 100644 --- a/tools/recovery_l10n/res/values-bn/strings.xml +++ b/tools/recovery_l10n/res/values-bn/strings.xml @@ -6,14 +6,9 @@ "কোনো আদেশ নেই" "ত্রুটি!" "নিরাপত্তার আপডেট ইনস্টল করা হচ্ছে" - - - - - - - - - - + "Android সিস্টেম লোড করা যায়নি। আপনার ডেটা হয়ত নষ্ট হয়ে গেছে। যদি এই মেসেজটি আসতেই থাকে তাহলে হয়ত ফ্যাক্টরি ডেটা রিসেট করে এই ডিভাইসে থাকা ব্যবহারকারীর সব ডেটা মুছে ফেলতে হবে।" + "আবার চেষ্টা করুন" + "ফ্যাক্টরি ডেটা রিসেট করুন" + "ব্যবহারকারীর সব ডেটা মুছে দিতে চান?\n\n এই ডেটা আর ফিরে পাওয়া যাবে না!" + "বাতিল করুন" diff --git a/tools/recovery_l10n/res/values-bs/strings.xml b/tools/recovery_l10n/res/values-bs/strings.xml index d9b397f35..38f197f29 100644 --- a/tools/recovery_l10n/res/values-bs/strings.xml +++ b/tools/recovery_l10n/res/values-bs/strings.xml @@ -6,14 +6,9 @@ "Nema komande" "Greška!" "Instaliranje sigurnosnog ažuriranja…" - - - - - - - - - - + "Nije moguće učitati Android sistem. Podaci su možda oštećeni. Ako opet primite ovu poruku, možda ćete morati vratiti uređaj na fabričke postavke i izbrisati sve podatke korisnika pohranjene na ovom uređaju." + "Pokušaj ponovo" + "Vraćanje na fabričke postavke" + "Izbrisati sve podatke korisnika?\n\n TA RADNJA SE NE MOŽE PONIŠTITI!" + "Otkaži" diff --git a/tools/recovery_l10n/res/values-ca/strings.xml b/tools/recovery_l10n/res/values-ca/strings.xml index 43b08d930..6b7bec077 100644 --- a/tools/recovery_l10n/res/values-ca/strings.xml +++ b/tools/recovery_l10n/res/values-ca/strings.xml @@ -6,14 +6,9 @@ "No hi ha cap ordre" "S\'ha produït un error" "S\'està instal·lant una actualització de seguretat" - - - - - - - - - - + "No s\'ha pogut carregar el sistema Android. És possible que les teves dades estiguin malmeses. Si continues veient aquest missatge, pot ser que hagis de restablir les dades de fàbrica i esborrar totes les dades d\'usuari emmagatzemades en aquest dispositiu." + "Torna-ho a provar" + "Restableix les dades de fàbrica" + "Vols eliminar totes les dades d\'usuari?\n\n AQUESTA ACCIÓ NO ES POT DESFER." + "Cancel·la" diff --git a/tools/recovery_l10n/res/values-cs/strings.xml b/tools/recovery_l10n/res/values-cs/strings.xml index fcb10c234..c42dab2c4 100644 --- a/tools/recovery_l10n/res/values-cs/strings.xml +++ b/tools/recovery_l10n/res/values-cs/strings.xml @@ -6,14 +6,9 @@ "Žádný příkaz" "Chyba!" "Instalace aktualizace zabezpečení" - - - - - - - - - - + "Systém Android se nepodařilo načíst. Vaše data jsou možná poškozena. Pokud se tato zpráva bude zobrazovat i nadále, bude nutné vymazat všechna uživatelská data v zařízení a obnovit tovární data." + "Zkusit znovu" + "Obnovení továrních dat" + "Vymazat všechna uživatelská data?\n\nTUTO AKCI NELZE VRÁTIT ZPĚT!" + "Zrušit" diff --git a/tools/recovery_l10n/res/values-da/strings.xml b/tools/recovery_l10n/res/values-da/strings.xml index 4811adead..814c0df09 100644 --- a/tools/recovery_l10n/res/values-da/strings.xml +++ b/tools/recovery_l10n/res/values-da/strings.xml @@ -6,14 +6,9 @@ "Ingen kommando" "Fejl!" "Installerer sikkerhedsopdateringen" - - - - - - - - - - + "Android-systemet kan ikke indlæses. Dine data er muligvis beskadigede. Hvis du bliver ved med at få denne meddelelse, er du måske nødt til at udføre en gendannelse af fabriksdata og slette alle brugerdata, der er gemt på denne enhed." + "Prøv igen" + "Gendannelse af fabriksdata" + "Vil du rydde alle brugerdata?\n\n DETTE KAN IKKE FORTRYDES!" + "Annuller" diff --git a/tools/recovery_l10n/res/values-de/strings.xml b/tools/recovery_l10n/res/values-de/strings.xml index c79b88a17..80fa97110 100644 --- a/tools/recovery_l10n/res/values-de/strings.xml +++ b/tools/recovery_l10n/res/values-de/strings.xml @@ -6,14 +6,9 @@ "Kein Befehl" "Fehler" "Sicherheitsupdate wird installiert" - - - - - - - - - - + "Android-System kann nicht geladen werden. Deine Daten sind eventuell beschädigt. Wenn du diese Nachricht weiterhin erhältst, musst du dein Gerät unter Umständen auf die Werkseinstellungen zurücksetzen und alle darauf gespeicherten Nutzerdaten löschen." + "Noch einmal versuchen" + "Zurücksetzen auf Werkseinstellungen" + "Alle Nutzerdaten löschen?\n\n DIESE AKTION KANN NICHT RÜCKGÄNGIG GEMACHT WERDEN." + "Abbrechen" diff --git a/tools/recovery_l10n/res/values-el/strings.xml b/tools/recovery_l10n/res/values-el/strings.xml index c4fe60f3d..204ae4092 100644 --- a/tools/recovery_l10n/res/values-el/strings.xml +++ b/tools/recovery_l10n/res/values-el/strings.xml @@ -6,14 +6,9 @@ "Καμία εντολή" "Σφάλμα!" "Εγκατάσταση ενημέρωσης ασφαλείας" - - - - - - - - - - + "Δεν είναι δυνατή η φόρτωση του συστήματος Android. Τα δεδομένα σας μπορεί να είναι κατεστραμμένα. Εάν εξακολουθήσετε να λαμβάνετε αυτό το μήνυμα, μπορεί να χρειαστεί να κάνετε επαναφορά εργοστασιακών ρυθμίσεων και να διαγράψετε όλα τα δεδομένα που έχουν αποθηκευτεί σε αυτήν τη συσκευή." + "Δοκιμάστε ξανά" + "Επαναφορά εργοστασιακών δεδομένων" + "Να διαγραφούν όλα τα δεδομένα χρήστη;\n\n ΔΕΝ ΕΙΝΑΙ ΔΥΝΑΤΗ Η ΑΝΑΙΡΕΣΗ ΑΥΤΗΣ ΤΗΣ ΕΝΕΡΓΕΙΑΣ!" + "Ακύρωση" diff --git a/tools/recovery_l10n/res/values-en-rAU/strings.xml b/tools/recovery_l10n/res/values-en-rAU/strings.xml index 25d5e3036..6451e5b6c 100644 --- a/tools/recovery_l10n/res/values-en-rAU/strings.xml +++ b/tools/recovery_l10n/res/values-en-rAU/strings.xml @@ -6,14 +6,9 @@ "No command" "Error!" "Installing security update" - - - - - - - - - - + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rCA/strings.xml b/tools/recovery_l10n/res/values-en-rCA/strings.xml index 25d5e3036..6451e5b6c 100644 --- a/tools/recovery_l10n/res/values-en-rCA/strings.xml +++ b/tools/recovery_l10n/res/values-en-rCA/strings.xml @@ -6,14 +6,9 @@ "No command" "Error!" "Installing security update" - - - - - - - - - - + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rGB/strings.xml b/tools/recovery_l10n/res/values-en-rGB/strings.xml index 25d5e3036..6451e5b6c 100644 --- a/tools/recovery_l10n/res/values-en-rGB/strings.xml +++ b/tools/recovery_l10n/res/values-en-rGB/strings.xml @@ -6,14 +6,9 @@ "No command" "Error!" "Installing security update" - - - - - - - - - - + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rIN/strings.xml b/tools/recovery_l10n/res/values-en-rIN/strings.xml index 25d5e3036..6451e5b6c 100644 --- a/tools/recovery_l10n/res/values-en-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-en-rIN/strings.xml @@ -6,14 +6,9 @@ "No command" "Error!" "Installing security update" - - - - - - - - - - + "Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device." + "Try again" + "Factory data reset" + "Wipe all user data?\n\n THIS CANNOT BE UNDONE!" + "Cancel" diff --git a/tools/recovery_l10n/res/values-en-rXC/strings.xml b/tools/recovery_l10n/res/values-en-rXC/strings.xml index 9419a0a01..61390f113 100644 --- a/tools/recovery_l10n/res/values-en-rXC/strings.xml +++ b/tools/recovery_l10n/res/values-en-rXC/strings.xml @@ -6,14 +6,9 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎" - - - - - - - - - - + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎Try again‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎Factory data reset‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‎Wipe all user data?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ THIS CAN NOT BE UNDONE!‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎Cancel‎‏‎‎‏‎" diff --git a/tools/recovery_l10n/res/values-es-rUS/strings.xml b/tools/recovery_l10n/res/values-es-rUS/strings.xml index 5451372a7..c0baa5924 100644 --- a/tools/recovery_l10n/res/values-es-rUS/strings.xml +++ b/tools/recovery_l10n/res/values-es-rUS/strings.xml @@ -6,14 +6,9 @@ "Ningún comando" "Error" "Instalando actualización de seguridad" - - - - - - - - - - + "No se puede cargar el sistema Android. Es posible que los datos estén dañados. Si este mensaje no desaparece, es posible que debas restablecer la configuración de fábrica del dispositivo y borrar todos los datos del usuario almacenados en él." + "Reintentar" + "Restablecer configuración de fábrica" + "¿Quieres borrar todos los datos del usuario?\n\n ESTA ACCIÓN NO SE PUEDE DESHACER" + "Cancelar" diff --git a/tools/recovery_l10n/res/values-es/strings.xml b/tools/recovery_l10n/res/values-es/strings.xml index e0496b2fc..de3b69bf3 100644 --- a/tools/recovery_l10n/res/values-es/strings.xml +++ b/tools/recovery_l10n/res/values-es/strings.xml @@ -6,14 +6,9 @@ "Sin comandos" "Error" "Instalando actualización de seguridad" - - - - - - - - - - + "No se puede cargar el sistema Android. Es posible que tus datos estén dañados. Si sigue apareciendo este mensaje, es posible que tengas que restablecer el estado de fábrica y borrar todos los datos de usuario almacenados en este dispositivo." + "Reintentar" + "Restablecer estado de fábrica" + "¿Quieres borrar todos los datos de usuario?\n\n ESTA ACCIÓN NO SE PUEDE DESHACER." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-et/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml index 6346e072d..cafb32ffd 100644 --- a/tools/recovery_l10n/res/values-et/strings.xml +++ b/tools/recovery_l10n/res/values-et/strings.xml @@ -6,14 +6,9 @@ "Käsk puudub" "Viga!" "Turvavärskenduse installimine" - - - - - - - - - - + "Android-süsteemi ei saa laadida. Teie andmed on võib-olla rikutud. Kui jätkate selle sõnumi hankimist, peate võib-olla tegema tehaseandmetele lähtestamise ja kustutama kõik sellesse seadmesse salvestatud kasutajaandmed." + "Proovige uuesti" + "Tehaseandmetele lähtestamine" + "Kas kustutada kõik kasutajaandmed?\n\n SEDA TOIMINGUT EI SAA TAGASI VÕTTA!" + "Tühista" diff --git a/tools/recovery_l10n/res/values-eu/strings.xml b/tools/recovery_l10n/res/values-eu/strings.xml index 033098b35..005a04264 100644 --- a/tools/recovery_l10n/res/values-eu/strings.xml +++ b/tools/recovery_l10n/res/values-eu/strings.xml @@ -6,14 +6,9 @@ "Ez dago agindurik" "Errorea" "Segurtasun-eguneratzea instalatzen" - - - - - - - - - - + "Ezin da kargatu Android sistema. Zure datuak hondatuta egon daitezke. Mezu hau jasotzen jarraitzen baduzu, jatorrizko datuak berrezarri beharko dituzu eta gailuan gordetako erabiltzaile-datu guztiak ezabatu beharko dituzu." + "Saiatu berriro" + "Berrezarri jatorrizko datuak" + "Erabiltzailearen datu guztiak xahutu nahi dituzu?\n\n EKINTZA HORI EZIN DA DESEGIN!" + "Utzi" diff --git a/tools/recovery_l10n/res/values-fa/strings.xml b/tools/recovery_l10n/res/values-fa/strings.xml index de9a24097..1c1be9ae3 100644 --- a/tools/recovery_l10n/res/values-fa/strings.xml +++ b/tools/recovery_l10n/res/values-fa/strings.xml @@ -6,14 +6,9 @@ "فرمانی وجود ندارد" "خطا!" "در حال نصب به‌روزرسانی امنیتی" - - - - - - - - - - + "‏نمی‌توان سیستم Android را بارگیری کرد. ممکن است داده‌های شما خراب باشند. اگر همچنان این پیام را دریافت می‌کنید، شاید لازم باشد بازنشانی داده‌های کارخانه‌ای انجام دهید و همه داده‌های کاربر را که در این دستگاه ذخیره شده است پاک کنید." + "تلاش مجدد" + "بازنشانی داده‌های کارخانه" + "همه داده‌های کاربر پاک شود؟\n\n این کار قابل‌واگرد نیست!" + "لغو" diff --git a/tools/recovery_l10n/res/values-fi/strings.xml b/tools/recovery_l10n/res/values-fi/strings.xml index 8d8cb914b..fddaf1453 100644 --- a/tools/recovery_l10n/res/values-fi/strings.xml +++ b/tools/recovery_l10n/res/values-fi/strings.xml @@ -6,14 +6,9 @@ "Ei komentoa" "Virhe!" "Asennetaan tietoturvapäivitystä" - - - - - - - - - - + "Android-järjestelmän lataaminen epäonnistui. Datasi voi olla vioittunut. Jos näet tämän viestin toistuvasti, sinun on ehkä palautettava tehdasasetukset ja poistettava kaikki laitteella olevat käyttäjätiedot." + "Yritä uudelleen" + "Tehdasasetuksien palauttaminen" + "Poistetaanko kaikki käyttäjätiedot?\n\nTÄTÄ EI VOI PERUA!" + "Peruuta" diff --git a/tools/recovery_l10n/res/values-fr-rCA/strings.xml b/tools/recovery_l10n/res/values-fr-rCA/strings.xml index 197830840..978e9ff93 100644 --- a/tools/recovery_l10n/res/values-fr-rCA/strings.xml +++ b/tools/recovery_l10n/res/values-fr-rCA/strings.xml @@ -6,14 +6,9 @@ "Aucune commande" "Erreur!" "Installation de la mise à jour de sécurité en cours..." - - - - - - - - - - + "Impossible de charger le système Android. Il se peut que vos données soient corrompues. Si vous continuez de recevoir ce message, vous devrez peut-être effectuer une réinitialisation de l\'appareil à ses paramètres d\'usine et effacer toutes les données d\'utilisateur qu\'il contient." + "Réessayer" + "Réinitialiser aux paramètres d\'usine" + "Effacer toutes les données de l\'utilisateur?\n\n CETTE ACTION NE PEUT PAS ÊTRE ANNULÉE!" + "Annuler" diff --git a/tools/recovery_l10n/res/values-fr/strings.xml b/tools/recovery_l10n/res/values-fr/strings.xml index 599233d07..693a5ddb4 100644 --- a/tools/recovery_l10n/res/values-fr/strings.xml +++ b/tools/recovery_l10n/res/values-fr/strings.xml @@ -6,14 +6,9 @@ "Aucune commande" "Erreur !" "Installation de la mise à jour de sécurité…" - - - - - - - - - - + "Impossible de charger le système Android. Vos données sont peut-être corrompues. Si vous continuez à recevoir ce message, vous devrez peut-être rétablir la configuration d\'usine de votre appareil et effacer toutes les données utilisateur stockées sur cet appareil." + "Réessayer" + "Rétablir la configuration d\'usine" + "Effacer toutes les données utilisateur ?\n\n CETTE ACTION NE PEUT PAS ÊTRE ANNULÉE." + "Annuler" diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml index 8c652d621..e51b36dfb 100644 --- a/tools/recovery_l10n/res/values-gl/strings.xml +++ b/tools/recovery_l10n/res/values-gl/strings.xml @@ -6,14 +6,9 @@ "Non hai ningún comando" "Erro" "Instalando actualización de seguranza" - - - - - - - - - - + "Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos de usuario almacenados neste dispositivo." + "Tentar de novo" + "Restablecemento dos datos de fábrica" + "Queres borrar todos os datos de usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-gu/strings.xml b/tools/recovery_l10n/res/values-gu/strings.xml index 84b4f4515..bd83447d7 100644 --- a/tools/recovery_l10n/res/values-gu/strings.xml +++ b/tools/recovery_l10n/res/values-gu/strings.xml @@ -6,14 +6,9 @@ "કોઈ આદેશ નથી" "ભૂલ!" "સુરક્ષા અપડેટ ઇન્સ્ટૉલ કરી રહ્યાં છે" - - - - - - - - - - + "Android સિસ્ટમ લોડ કરી શકાતી નથી. તમારો ડેટા કદાચ દૂષિત થયો હોઈ શકે છે. જો તમને આ સંદેશ મળવાનું ચાલુ રહે, તો કદાચ તમારે આ ડિવાઇસ માટે ફેક્ટરી ડેટા રીસેટ કરવાની પ્રક્રિયા કરવી અને આના પર સ્ટોર કરેલો વપરાશકર્તાનો બધો ડેટા કાઢી નાખવો જરૂરી રહેશે." + "ફરી પ્રયાસ કરો" + "ફેક્ટરી ડેટા રીસેટ કરો" + "શું વપરાશકર્તાનો બધો ડેટા વાઇપ કરીએ?\n\n આ ક્રિયામાં કરેલો ફેરફાર રદ કરી શકાતો નથી!" + "રદ કરો" diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml index 50248b599..c1aa2e97f 100644 --- a/tools/recovery_l10n/res/values-hi/strings.xml +++ b/tools/recovery_l10n/res/values-hi/strings.xml @@ -6,14 +6,9 @@ "कोई निर्देश नहीं मिला" "गड़बड़ी!" "सुरक्षा अपडेट इंस्टॉल किया जा रहा है" - - - - - - - - - - + "Android सिस्टम लोड नहीं किया जा सकता. शायद आपके डेटा में गड़बड़ी है. अगर आपको यह मैसेज मिलता रहता है, तो शायद आपको फ़ैक्ट्री डेटा रीसेट करना पड़े और इस डिवाइस की मेमोरी में मौजूद उपयोगकर्ता का सभी डेटा हमेशा के लिए मिटाना पड़े." + "फिर से कोशिश करें" + "फ़ैक्ट्री डेटा रीसेट" + "क्या उपयोगकर्ता का सभी डेटा मिटाएं?\n\n इसे वापस नहीं लाया जा सकता!" + "अभी नहीं" diff --git a/tools/recovery_l10n/res/values-hr/strings.xml b/tools/recovery_l10n/res/values-hr/strings.xml index 001e33493..0fa8fa9fe 100644 --- a/tools/recovery_l10n/res/values-hr/strings.xml +++ b/tools/recovery_l10n/res/values-hr/strings.xml @@ -6,14 +6,9 @@ "Nema naredbe" "Pogreška!" "Instaliranje sigurnosnog ažuriranja" - - - - - - - - - - + "Sustav Android ne može se učitati. Podaci su možda oštećeni. Ako opet primite ovu poruku, možda ćete morati vratiti uređaj na tvorničko stanje i izbrisati sve podatke korisnika pohranjene na ovom uređaju." + "Pokušaj ponovo" + "Vraćanje na tvorničko stanje" + "Želite li izbrisati sve podatke korisnika?\n\n TO SE NE MOŽE PONIŠTITI!" + "Odustani" diff --git a/tools/recovery_l10n/res/values-hu/strings.xml b/tools/recovery_l10n/res/values-hu/strings.xml index 5a6d60189..b7998cea3 100644 --- a/tools/recovery_l10n/res/values-hu/strings.xml +++ b/tools/recovery_l10n/res/values-hu/strings.xml @@ -6,14 +6,9 @@ "Nincs parancs" "Hiba!" "Biztonsági frissítés telepítése" - - - - - - - - - - + "Nem sikerült az Android rendszer betöltése. Az adatok sérültek lehetnek. Ha újra megjelenik ez az üzenet, előfordulhat, hogy vissza kell állítania az eszköz gyári adatait, és törölnie kell az eszközön tárolt összes felhasználói adatot." + "Újra" + "Gyári adatok visszaállítása" + "Törli az összes felhasználói adatot?\n\n A MŰVELET NEM VONHATÓ VISSZA." + "Mégse" diff --git a/tools/recovery_l10n/res/values-hy/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml index a652e3392..35a0ab113 100644 --- a/tools/recovery_l10n/res/values-hy/strings.xml +++ b/tools/recovery_l10n/res/values-hy/strings.xml @@ -6,14 +6,9 @@ "Հրամանը տրված չէ" "Սխալ" "Անվտանգության թարմացման տեղադրում" - - - - - - - - - - + "Չհաջողվեց բեռնել Android համակարգը։ Հնարավոր է՝ ձեր տվյալները վնասված են։ Եթե նորից տեսնեք այս հաղորդագրությունը, փորձեք վերակայել սարքի կարգավորումները և ջնջել օգտատիրոջ բոլոր տվյալները։" + "Նորից փորձել" + "Վերակայել բոլոր տվյալները" + "Մաքրե՞լ օգտատիրոջ բոլոր տվյալները։\n\n ԱՅՍ ԳՈՐԾՈՂՈՒԹՅՈՒՆԸ ՀՆԱՐԱՎՈՐ ՉԻ ԼԻՆԻ ՀԵՏԱՐԿԵԼ" + "Չեղարկել" diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml index b34e39419..15a78ec48 100644 --- a/tools/recovery_l10n/res/values-in/strings.xml +++ b/tools/recovery_l10n/res/values-in/strings.xml @@ -6,14 +6,9 @@ "Tidak ada perintah" "Error!" "Memasang pembaruan keamanan" - - - - - - - - - - + "Tidak dapat memuat sistem Android. Data Anda mungkin rusak. Jika terus mendapatkan pesan ini, Anda mungkin perlu melakukan reset ke setelan pabrik dan menghapus semua data pengguna yang disimpan di perangkat ini." + "Coba lagi" + "Reset ke setelan pabrik" + "Wipe semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!" + "Batal" diff --git a/tools/recovery_l10n/res/values-is/strings.xml b/tools/recovery_l10n/res/values-is/strings.xml index d1e790a23..4a6295af2 100644 --- a/tools/recovery_l10n/res/values-is/strings.xml +++ b/tools/recovery_l10n/res/values-is/strings.xml @@ -6,14 +6,9 @@ "Engin skipun" "Villa!" "Setur upp öryggisuppfærslu" - - - - - - - - - - + "Ekki er hægt að hlaða Android kerfi. Gögnin þín kunna að vera skemmd. Ef þessi skilaboð halda áfram að birtast gætirðu þurft að núllstilla og eyða öllum notandagögnum sem eru vistuð í þessu tæki." + "Reyna aftur" + "Núllstilling" + "Viltu eyða öllum notandagögnum?\n\n EKKI ER HÆGT AÐ AFTURKALLA ÞETTA!" + "Hætta við" diff --git a/tools/recovery_l10n/res/values-it/strings.xml b/tools/recovery_l10n/res/values-it/strings.xml index 12c627526..8bc203c15 100644 --- a/tools/recovery_l10n/res/values-it/strings.xml +++ b/tools/recovery_l10n/res/values-it/strings.xml @@ -6,14 +6,9 @@ "Nessun comando" "Errore!" "Installazione aggiornamento sicurezza…" - - - - - - - - - - + "Impossibile caricare il sistema Android. I tuoi dati potrebbero essere danneggiati. Se continui a ricevere questo messaggio, potrebbe essere necessario eseguire un ripristino dei dati di fabbrica e cancellare tutti i dati utente memorizzati su questo dispositivo." + "Riprova" + "Ripristino dati di fabbrica" + "Vuoi cancellare tutti i dati utente?\n\n NON È POSSIBILE ANNULLARE L\'OPERAZIONE." + "Annulla" diff --git a/tools/recovery_l10n/res/values-iw/strings.xml b/tools/recovery_l10n/res/values-iw/strings.xml index 42d931b5d..8ca3bdf00 100644 --- a/tools/recovery_l10n/res/values-iw/strings.xml +++ b/tools/recovery_l10n/res/values-iw/strings.xml @@ -6,14 +6,9 @@ "אין פקודה" "שגיאה!" "מתקין עדכון אבטחה" - - - - - - - - - - + "‏לא ניתן לטעון את מערכת Android. ייתכן שהנתונים שלך פגומים. אם הודעה זו תופיע שוב, ייתכן שיהיה עליך לבצע איפוס לנתוני היצרן ולמחוק את כל נתוני המשתמש ששמורים במכשיר זה." + "ניסיון נוסף" + "איפוס לנתוני היצרן" + "לאפס את כל נתוני המשתמש?\n\n לא ניתן לבטל פעולה זו!" + "ביטול" diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml index 4e832117c..3d6637278 100644 --- a/tools/recovery_l10n/res/values-ja/strings.xml +++ b/tools/recovery_l10n/res/values-ja/strings.xml @@ -6,14 +6,9 @@ "コマンドが指定されていません" "エラーが発生しました。" "セキュリティ アップデートをインストールしています" - - - - - - - - - - + "Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、この端末に保存されているすべてのユーザー データを消去することが必要な場合があります。" + "再試行" + "データの初期化" + "すべてのユーザー データをワイプしますか?\n\nこの操作は元に戻せません。" + "キャンセル" diff --git a/tools/recovery_l10n/res/values-ka/strings.xml b/tools/recovery_l10n/res/values-ka/strings.xml index 0c6f49a7b..04b8a417f 100644 --- a/tools/recovery_l10n/res/values-ka/strings.xml +++ b/tools/recovery_l10n/res/values-ka/strings.xml @@ -6,14 +6,9 @@ "ბრძანება არ არის" "წარმოიქმნა შეცდომა!" "მიმდინარეობს უსაფრთხოების განახლების ინსტალაცია" - - - - - - - - - - + "Android სისტემის ჩატვირთვა ვერ მოხერხდა. შესაძლოა თქვენი მონაცემები დაზიანებულია. თუ ამ შეტყობინებას კვლავ მიიღებთ, შეიძლება საჭირო იყოს ქარხნული მონაცემების აღდგენა და ამ მოწყობილობაზე შენახული მომხმარებლის ყველა მონაცემის ამოშლა." + "ხელახლა ცდა" + "ქარხნული მონაცემების აღდგენა" + "გსურთ მომხმარებლის ყველა მონაცემის ამოშლა?\n\n ამ მოქმედების გაუქმება ვერ მოხერხდება!" + "გაუქმება" diff --git a/tools/recovery_l10n/res/values-kk/strings.xml b/tools/recovery_l10n/res/values-kk/strings.xml index 787bda07b..3f6aa23da 100644 --- a/tools/recovery_l10n/res/values-kk/strings.xml +++ b/tools/recovery_l10n/res/values-kk/strings.xml @@ -6,14 +6,9 @@ "Пәрмен жоқ" "Қате!" "Қауіпсіздік жаңартуы орнатылуда" - - - - - - - - - - + "Android жүйесі жүктелмейді. Деректеріңіз бүлінген болуы мүмкін. Егер осы хабар қайта шықса, зауыттық деректерді қалпына келтіріп, пайдаланушы деректерін жойып көріңіз." + "Қайталау" + "Зауыттық деректерді қалпына келтіру" + "Пайдаланушының барлық деректері жойылсын ба?\n\n БҰЛ ӘРЕКЕТТІ ҚАЙТАРЫЛМАЙДЫ!" + "Бас тарту" diff --git a/tools/recovery_l10n/res/values-km/strings.xml b/tools/recovery_l10n/res/values-km/strings.xml index 355bffd2c..0cedb6bb7 100644 --- a/tools/recovery_l10n/res/values-km/strings.xml +++ b/tools/recovery_l10n/res/values-km/strings.xml @@ -6,14 +6,9 @@ "គ្មានពាក្យបញ្ជាទេ" "កំហុស!" "កំពុងដំឡើងការអាប់ដេតសុវត្ថិភាព" - - - - - - - - - - + "មិនអាច​ផ្ទុកប្រព័ន្ធ Android បានទេ។ ទិន្នន័យ​របស់​អ្នកអាច​នឹងខូច។ ប្រសិនបើ​អ្នក​បន្តទទួល​បានសារនេះ អ្នកអាចនឹងត្រូវកំណត់​ទិន្នន័យ​ដូច​ចេញ​ពី​រោងចក្រ និងលុបទិន្នន័យ​ទាំងអស់​របស់អ្នក​ប្រើប្រាស់​ដែលបានផ្ទុកនៅ​លើ​ឧបករណ៍​នេះ។" + "ព្យាយាម​ម្ដងទៀត" + "កំណត់​ទិន្នន័យ​ដូច​ចេញ​ពី​រោងចក្រ" + "ឈូស​ទិន្នន័យ​ទាំងអស់​របស់អ្នក​ប្រើប្រាស់?\n\nសកម្មភាព​នេះមិនអាចត្រឡប់វិញបានទេ!" + "បោះបង់" diff --git a/tools/recovery_l10n/res/values-kn/strings.xml b/tools/recovery_l10n/res/values-kn/strings.xml index ef6b08012..a98f4692a 100644 --- a/tools/recovery_l10n/res/values-kn/strings.xml +++ b/tools/recovery_l10n/res/values-kn/strings.xml @@ -6,14 +6,9 @@ "ಯಾವುದೇ ಆದೇಶವಿಲ್ಲ" "ದೋಷ!" "ಭದ್ರತೆಯ ಅಪ್‌ಡೇಟ್‌ ಸ್ಥಾಪಿಸಲಾಗುತ್ತಿದೆ" - - - - - - - - - - + "Android ಸಿಸ್ಟಂ ಅನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಡೇಟಾ ದೋಷಪೂರಿತವಾಗಿರಬಹುದು. ನೀವು ಈ ಸಂದೇಶ ಪಡೆಯುವುದು ಮುಂದುವರಿದರೆ, ನೀವು ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾ ರಿಸೆಟ್ ಮಾಡುವ ಅಗತ್ಯವಿದೆ ಮತ್ತು ಈ ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾದ ಎಲ್ಲಾ ಬಳಕೆದಾರರ ಡೇಟಾವನ್ನು ಅಳಿಸಬೇಕಾಗುತ್ತದೆ." + "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ" + "ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾ ರಿಸೆಟ್‌" + "ಎಲ್ಲಾ ಬಳಕೆದಾರರ ಡೇಟಾವನ್ನು ಅಳಿಸುವುದೇ?\n\n ಇದನ್ನು ರದ್ದುಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ!" + "ರದ್ದುಮಾಡಿ" diff --git a/tools/recovery_l10n/res/values-ko/strings.xml b/tools/recovery_l10n/res/values-ko/strings.xml index 3f83b2058..9067f4c34 100644 --- a/tools/recovery_l10n/res/values-ko/strings.xml +++ b/tools/recovery_l10n/res/values-ko/strings.xml @@ -6,14 +6,9 @@ "명령어 없음" "오류!" "보안 업데이트 설치 중" - - - - - - - - - - + "Android 시스템을 로드할 수 없습니다. 데이터가 손상되었을 수 있습니다. 이 메시지가 계속 표시되면 초기화를 실행하여 기기에 저장된 사용자 데이터를 모두 삭제해야 할 수도 있습니다." + "다시 시도" + "초기화" + "사용자 데이터를 모두 삭제하시겠습니까?\n\n 이 작업은 실행취소할 수 없습니다." + "취소" diff --git a/tools/recovery_l10n/res/values-ky/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml index 3832f33e7..1cd69ea84 100644 --- a/tools/recovery_l10n/res/values-ky/strings.xml +++ b/tools/recovery_l10n/res/values-ky/strings.xml @@ -6,14 +6,9 @@ "Буйрук берилген жок" "Ката!" "Коопсуздук жаңыртуусу орнотулууда" - - - - - - - - - - + "Android тутуму жүктөлбөй жатат. Дайындарыңыз бузук болушу мүмкүн. Бул билдирүү дагы деле келе берсе, түзмөктү кайра башынан жөндөп, анда сакталган бардык колдонуучу дайындарын тазалашыңыз керек." + "Кайталоо" + "Кайра башынан жөндөө" + "Колдонуучу дайындарынын баары жашырылсынбы?\n\n МУНУ АРТКА КАЙТАРУУ МҮМКҮН ЭМЕС!" + "Жок" diff --git a/tools/recovery_l10n/res/values-lo/strings.xml b/tools/recovery_l10n/res/values-lo/strings.xml index f5224ff3b..4a8142783 100644 --- a/tools/recovery_l10n/res/values-lo/strings.xml +++ b/tools/recovery_l10n/res/values-lo/strings.xml @@ -6,14 +6,9 @@ "ບໍ່ມີຄຳສັ່ງ" "ຜິດພາດ!" "ກຳລັງຕິດຕັ້ງອັບເດດຄວາມປອດໄພ" - - - - - - - - - - + "ບໍ່ສາມາດໂຫຼດລະບົບ Android ໄດ້. ຂໍ້ມູນຂອງທ່ານອາດເສຍຫາຍ. ຫາກທ່ານຍັງໄດ້ຮັບຂໍ້ຄວາມນີ້ຕໍ່ໄປ, ທ່ານອາດຕ້ອງຣີເຊັດເປັນຄ່າຈາກໂຮງງານ ແລະ ລຶບຂໍ້ມູນຜູ້ໃຊ້ທັງໝົດທີ່ຈັດເກັບໄວ້ຢູ່ອຸປະກອນນີ້ອອກ." + "ລອງໃໝ່" + "ຣີເຊັດຄ່າຈາກໂຮງງານ" + "ລຶບລ້າງຂໍ້ມູນຜູ້ໃຊ້ທັງໝົດບໍ?\n\n ຄຳສັ່ງນີ້ຈະບໍ່ສາມາດຍົກເລີກໄດ້!" + "ຍົກເລີກ" diff --git a/tools/recovery_l10n/res/values-lt/strings.xml b/tools/recovery_l10n/res/values-lt/strings.xml index 1ec3586b2..f9b7d3917 100644 --- a/tools/recovery_l10n/res/values-lt/strings.xml +++ b/tools/recovery_l10n/res/values-lt/strings.xml @@ -6,14 +6,9 @@ "Nėra jokių komandų" "Klaida!" "Diegiamas saugos naujinys" - - - - - - - - - - + "Negalima įkelti „Android“ sistemos. Duomenys gali būti pažeisti. Jei ir toliau gausite šį pranešimą, jums gali reikėti atkurti gamyklinius duomenis ir ištrinti visus naudotojo duomenis, saugomus šiame įrenginyje." + "Bandyti dar kartą" + "Gamyklinių duomenų atkūrimas" + "Išvalyti visus naudotojo duomenis?\n\n ŠIO VEIKSMO NEGALIMA ANULIUOTI!" + "Atšaukti" diff --git a/tools/recovery_l10n/res/values-lv/strings.xml b/tools/recovery_l10n/res/values-lv/strings.xml index 83b059405..6cf8ce30e 100644 --- a/tools/recovery_l10n/res/values-lv/strings.xml +++ b/tools/recovery_l10n/res/values-lv/strings.xml @@ -6,14 +6,9 @@ "Nav nevienas komandas" "Kļūda!" "Notiek drošības atjauninājuma instalēšana" - - - - - - - - - - + "Nevar ielādēt Android sistēmu. Jūsu dati var būt bojāti. Ja šis ziņojums tiek rādīts atkārtoti, iespējams, jums ir jāveic rūpnīcas datu atiestatīšana un jādzēš visi šajā ierīcē saglabātie lietotāja dati." + "Mēģināt vēlreiz" + "Rūpnīcas datu atiestatīšana" + "Vai dzēst visus lietotāja datus?\n\n ŠO DARBĪBU NEVAR ATSAUKT!" + "Atcelt" diff --git a/tools/recovery_l10n/res/values-mk/strings.xml b/tools/recovery_l10n/res/values-mk/strings.xml index 39b4fa42c..ff56131f9 100644 --- a/tools/recovery_l10n/res/values-mk/strings.xml +++ b/tools/recovery_l10n/res/values-mk/strings.xml @@ -6,14 +6,9 @@ "Нема наредба" "Грешка!" "Се инсталира безбедносно ажурирање" - - - - - - - - - - + "Не може да се вчита системот Android. Можно е податоците да се оштетени. Ако и понатаму ја примате поракава, можеби ќе треба да извршите ресетирање на фабрички податоци и да ги избришете сите кориснички податоци меморирани на уредов." + "Обиди се пак" + "Ресетирање на фабрички податоци" + "Да се избришат ли сите кориснички податоци?\n\n ОВА НЕ МОЖЕ ДА СЕ ВРАТИ!" + "Откажи" diff --git a/tools/recovery_l10n/res/values-ml/strings.xml b/tools/recovery_l10n/res/values-ml/strings.xml index 752a3ed12..2b331ac7e 100644 --- a/tools/recovery_l10n/res/values-ml/strings.xml +++ b/tools/recovery_l10n/res/values-ml/strings.xml @@ -6,14 +6,9 @@ "കമാൻഡ് ഒന്നുമില്ല" "പിശക്!" "സുരക്ഷാ അപ്ഡേറ്റ് ഇൻസ്റ്റാൾ ചെയ്യുന്നു" - - - - - - - - - - + "Android സിസ്‌റ്റം ലോഡ് ചെയ്യാനാവില്ല. നിങ്ങളുടെ ഡാറ്റ കേടായിരിക്കാം. ഈ സന്ദേശം തുടർന്നും ലഭിക്കുകയാണെങ്കിൽ, നിങ്ങൾ ഒരു ഫാക്‌ടറി ഡാറ്റ പുനഃക്രമീകരണം നടത്തേണ്ടതുണ്ട് ഒപ്പം ഈ ഉപകരണത്തിൽ സ്‌റ്റോർ ചെയ്‌തിട്ടുള്ള എല്ലാ ഉപയോക്തൃ ഡാറ്റകളും മായ്‌ക്കേണ്ടതുണ്ട്." + "വീണ്ടും ശ്രമിക്കുക" + "ഫാക്‌ടറി ഡാറ്റ പുനഃക്രമീകരണം" + "എല്ലാ ഉപയോക്തൃ ഡാറ്റകളും മായ്‌ക്കണോ?\n\n ഇത് പഴയപടിയാക്കാനാവില്ല!" + "റദ്ദാക്കുക" diff --git a/tools/recovery_l10n/res/values-mn/strings.xml b/tools/recovery_l10n/res/values-mn/strings.xml index df61476c0..b0a57ed1a 100644 --- a/tools/recovery_l10n/res/values-mn/strings.xml +++ b/tools/recovery_l10n/res/values-mn/strings.xml @@ -6,14 +6,9 @@ "Тушаал байхгүй" "Алдаа!" "Аюулгүй байдлын шинэчлэлтийг суулгаж байна" - - - - - - - - - - + "Андройд системийг ачаалах боломжгүй байна. Таны өгөгдөл эвдэрч болзошгүй. Хэрэв та энэ мессежийг үргэлжлүүлэн авах бол үйлдвэрээс гарсан төлөвийг ажиллуулж, энэ төхөөрөмжид хадгалсан хэрэглэгчийн бүх өгөгдлийг устгах шаардлагатай байж болзошгүй." + "Дахин оролдох" + "Үйлдвэрээс гарсан төлөвт" + "Хэрэглэгчийн бүх өгөгдлийг арчих уу?\n\n ҮҮНИЙГ БУЦААХ БОЛОМЖГҮЙ!" + "Цуцлах" diff --git a/tools/recovery_l10n/res/values-mr/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml index f96ddea9b..9b1370794 100644 --- a/tools/recovery_l10n/res/values-mr/strings.xml +++ b/tools/recovery_l10n/res/values-mr/strings.xml @@ -6,14 +6,9 @@ "कोणतीही कमांड नाही" "एरर!" "सुरक्षा अपडेट इंस्टॉल करत आहे" - - - - - - - - - - + "Android सिस्टम लोड करू शकत नाही. तुमचा डेटा धोक्यात असू शकतो.तुम्हाला हा मेसेज मिळत राहिल्यास, फॅक्टरी डेटा रीसेट करणे आणि या डिव्हाइसवर स्टोअर केलेला सर्व वापरकर्ता डेटा मिटवणे आवश्यक आहे." + "पुन्हा प्रयत्न करा" + "फॅक्‍टरी डेटा रीसेट" + "सर्व वापरकर्ता डेटा पुसून टाकायचा का?\n\n हे पहिल्‍यासारखे करू शकत नाही!" + "रद्द करा" diff --git a/tools/recovery_l10n/res/values-ms/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml index 7a3480ee9..d094f547b 100644 --- a/tools/recovery_l10n/res/values-ms/strings.xml +++ b/tools/recovery_l10n/res/values-ms/strings.xml @@ -6,14 +6,9 @@ "Tiada perintah" "Ralat!" "Memasang kemas kini keselamatan" - - - - - - - - - - + "Tidak dapat memuatkan sistem Android. Data anda mungkin rosak. Jika anda menerima mesej ini secara berterusan, anda mungkin perlu melaksanakan tetapan semula data kilang dan memadamkan semua data pengguna yang disimpan pada peranti ini." + "Cuba lagi" + "Tetapan semula data kilang" + "Lapkan semua data pengguna?\n\n TINDAKAN INI TIDAK BOLEH DIBUAT ASAL!" + "Batal" diff --git a/tools/recovery_l10n/res/values-my/strings.xml b/tools/recovery_l10n/res/values-my/strings.xml index 1dc11e00d..09cd4ea51 100644 --- a/tools/recovery_l10n/res/values-my/strings.xml +++ b/tools/recovery_l10n/res/values-my/strings.xml @@ -6,14 +6,9 @@ "ညွှန်ကြားချက်မပေးထားပါ" "မှားနေပါသည်!" "လုံခြုံရေး အပ်ဒိတ်ကို ထည့်သွင်းနေသည်" - - - - - - - - - - + "Android စနစ် ဖွင့်၍မရပါ။ သင့်ဒေတာများ ပျက်နေခြင်း ဖြစ်နိုင်သည်။ ဤမက်ဆေ့ဂျ် ဆက်လက်ရရှိနေလျှင် စက်ရုံထုတ်အခြေအနေပြန်ယူပြီး ဤစက်ပေါ်တွင် သိမ်းထားသော အသုံးပြုသူဒေတာအားလုံး ဖျက်ရန် လိုအပ်နိုင်သည်။" + "ထပ်စမ်းကြည့်ပါ" + "စက်ရုံထုတ်အခြေအနေပြန်ယူခြင်း" + "အသုံးပြုသူဒေတာ အားလုံးကို ရှင်းလင်းမလား။\n\n ၎င်းကို ပြန်ပြင်၍မရပါ။" + "မလုပ်တော့" diff --git a/tools/recovery_l10n/res/values-nb/strings.xml b/tools/recovery_l10n/res/values-nb/strings.xml index f7d23a6ea..e8cad136c 100644 --- a/tools/recovery_l10n/res/values-nb/strings.xml +++ b/tools/recovery_l10n/res/values-nb/strings.xml @@ -6,14 +6,9 @@ "Ingen kommandoer" "Feil!" "Installerer sikkerhetsoppdateringen" - - - - - - - - - - + "Kan ikke laste inn Android-systemet. Dataene dine er muligens skadet. Hvis du fortsetter å se denne meldingen, må du muligens tilbakestille til fabrikkstandard og tømme alle brukerdataene som er lagret på denne enheten." + "Prøv igjen" + "Tilbakestill til fabrikkstandard" + "Vil du viske ut alle brukerdataene?\n\n DETTE KAN IKKE ANGRES!" + "Avbryt" diff --git a/tools/recovery_l10n/res/values-ne/strings.xml b/tools/recovery_l10n/res/values-ne/strings.xml index 044776740..fa53e9dae 100644 --- a/tools/recovery_l10n/res/values-ne/strings.xml +++ b/tools/recovery_l10n/res/values-ne/strings.xml @@ -6,14 +6,9 @@ "कुनै आदेश छैन" "त्रुटि!" "सुरक्षा सम्बन्धी अद्यावधिकलाई स्थापना गर्दै" - - - - - - - - - - + "Android प्रणाली लोड गर्न सकिएन। तपाईंको डेटा बिग्रेको हुन सक्छ। तपाईं यो सन्देश प्राप्त गर्नुहुन्छ भने तपाईंले फ्याक्ट्री डेटा रिसेट गर्न आवश्यक छ र यो यन्त्रमा भण्डारण गरेका सबै प्रयोगकर्ताको डेटा मेट्न पर्छ।" + "फेरि प्रयास गर्नुहोस्" + "फ्याक्ट्री डेटा रिसेट" + "प्रयोगकर्ताको सबै डेटा मेट्ने हो?\n\n यो अन्डू गर्न सकिँदैन!" + "रद्द गर्नुहोस्" diff --git a/tools/recovery_l10n/res/values-nl/strings.xml b/tools/recovery_l10n/res/values-nl/strings.xml index 3be534537..b42bb6582 100644 --- a/tools/recovery_l10n/res/values-nl/strings.xml +++ b/tools/recovery_l10n/res/values-nl/strings.xml @@ -6,14 +6,9 @@ "Geen opdracht" "Fout!" "Beveiligingsupdate installeren" - - - - - - - - - - + "Kan het Android-systeem niet laden. Je gegevens zijn mogelijk beschadigd. Als je dit bericht blijft ontvangen, moet je mogelijk de fabrieksinstellingen terugzetten en alle gebruikersgegevens wissen die op dit apparaat zijn opgeslagen." + "Opnieuw proberen" + "Terugzetten op fabrieksinstellingen" + "Alle gebruikersgegevens wissen?\n\n DIT KAN NIET ONGEDAAN WORDEN GEMAAKT." + "Annuleren" diff --git a/tools/recovery_l10n/res/values-or/strings.xml b/tools/recovery_l10n/res/values-or/strings.xml index 749ad82f6..25b28e65a 100644 --- a/tools/recovery_l10n/res/values-or/strings.xml +++ b/tools/recovery_l10n/res/values-or/strings.xml @@ -6,14 +6,9 @@ "କୌଣସି କମାଣ୍ଡ ନାହିଁ" "ତ୍ରୁଟି!" "ସୁରକ୍ଷା ଅପ୍‌ଡେଟ୍‌ ଇନ୍‌ଷ୍ଟଲ୍‌ କରୁଛି" - - - - - - - - - - + "Android ସିଷ୍ଟମ୍‍ ଲୋଡ୍‍ କରାଯାଇପାରିବ ନାହିଁ। ଆପଣଙ୍କ ଡାଟା ହୁଏତ ତ୍ରୁଟି ରହିଥାଇ ପାରେ। ଯଦି ଆପଣ ଏହି ମେସେଜ୍‍ ପାଇବା ଜାରି ରଖନ୍ତି, ତେବେ ଆପଣଙ୍କୁ ଫ୍ୟାକ୍ଟେରୀ ଡାଟା ରିସେଟ୍‍ କରିବାକୁ ହେବ ଏବଂ ଏହି ଡିଭାଇସ୍‍‍ରେ ଷ୍ଟୋର୍‍ ହୋଇଥିବା ସମସ୍ତ ଡାଟା ଇରେଜ୍‍ କରନ୍ତୁ।" + "ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ" + "ଫ୍ୟାକ୍ଟୋରୀ ଡାଟା ରିସେଟ୍‌" + "ସମସ୍ଯ ଉପଯୋଗକର୍ତ୍ତା ଡାଟା ୱାଇପ୍‍ କରିବେ?\n\n ଏହା ଫେରାଇ ନିଆଯାଇପାରିବ ନାହିଁ!" + "ବାତିଲ୍‌ କରନ୍ତୁ" diff --git a/tools/recovery_l10n/res/values-pa/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml index 0475a5e40..374306805 100644 --- a/tools/recovery_l10n/res/values-pa/strings.xml +++ b/tools/recovery_l10n/res/values-pa/strings.xml @@ -6,14 +6,9 @@ "ਕੋਈ ਆਦੇਸ਼ ਨਹੀਂ" "ਅਸ਼ੁੱਧੀ!" "ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" - - - - - - - - - - + "Android ਸਿਸਟਮ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਸ਼ਾਇਦ ਤੁਹਾਡਾ ਡਾਟਾ ਖਰਾਬ ਹੈ। ਜੇਕਰ ਤੁਹਾਨੂੰ ਇਹ ਸੁਨੇਹਾ ਪ੍ਰਾਪਤ ਹੋਣਾ ਜਾਰੀ ਰਹਿੰਦਾ ਹੈ, ਤਾਂ ਸ਼ਾਇਦ ਤੁਹਾਨੂੰ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਨਾ ਪਵੇ ਅਤੇ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ ਵਰਤੋਂਕਾਰ ਡਾਟੇ ਨੂੰ ਮਿਟਾਉਣਾ ਪਵੇ।" + "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ" + "ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰੋ" + "ਕੀ ਸਾਰਾ ਵਰਤੋਂਕਾਰ ਡਾਟਾ ਸਾਫ਼ ਕਰਨਾ ਹੈ?\n\n ਇਸਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ!" + "ਰੱਦ ਕਰੋ" diff --git a/tools/recovery_l10n/res/values-pl/strings.xml b/tools/recovery_l10n/res/values-pl/strings.xml index daea5bd4d..48d3dbf6e 100644 --- a/tools/recovery_l10n/res/values-pl/strings.xml +++ b/tools/recovery_l10n/res/values-pl/strings.xml @@ -6,14 +6,9 @@ "Brak polecenia" "Błąd" "Instaluję aktualizację zabezpieczeń" - - - - - - - - - - + "Nie można załadować systemu Android. Dane mogą być uszkodzone. Jeśli ten komunikat nadal będzie się pojawiać, może być konieczne przywrócenie danych fabrycznych urządzenia i usunięcie wszystkich zapisanych na nim danych użytkownika." + "Ponów próbę" + "Przywracanie danych fabrycznych" + "Wyczyścić wszystkie dane użytkownika?\n\n TEJ CZYNNOŚCI NIE MOŻNA COFNĄĆ." + "Anuluj" diff --git a/tools/recovery_l10n/res/values-pt-rBR/strings.xml b/tools/recovery_l10n/res/values-pt-rBR/strings.xml index 3d3227cba..0df3edccb 100644 --- a/tools/recovery_l10n/res/values-pt-rBR/strings.xml +++ b/tools/recovery_l10n/res/values-pt-rBR/strings.xml @@ -6,14 +6,9 @@ "Nenhum comando" "Erro!" "Instalando atualização de segurança" - - - - - - - - - - + "Não é possível carregar o sistema Android. Seus dados podem estar corrompidos. Se você continuar recebendo esta mensagem, talvez seja necessário realizar uma redefinição para a configuração original e limpar todos os dados do usuário armazenados neste dispositivo." + "Tentar novamente" + "Redefinição para configuração original" + "Limpar todos os dados do usuário?\n\n NÃO É POSSÍVEL DESFAZER ESSA AÇÃO." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-pt-rPT/strings.xml b/tools/recovery_l10n/res/values-pt-rPT/strings.xml index ae3938870..08eb3c953 100644 --- a/tools/recovery_l10n/res/values-pt-rPT/strings.xml +++ b/tools/recovery_l10n/res/values-pt-rPT/strings.xml @@ -6,14 +6,9 @@ "Nenhum comando" "Erro!" "A instalar atualização de segurança" - - - - - - - - - - + "Não é possível carregar o sistema Android. Os seus dados podem estar danificados. Se continuar a receber esta mensagem, pode ter de efetuar uma reposição de dados de fábrica e apagar todos os dados do utilizador armazenados neste dispositivo." + "Tentar novamente" + "Reposição de dados de fábrica" + "Pretende limpar todos os dados do utilizador?\n\n NÃO É POSSÍVEL ANULAR ESTA AÇÃO." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-pt/strings.xml b/tools/recovery_l10n/res/values-pt/strings.xml index 3d3227cba..0df3edccb 100644 --- a/tools/recovery_l10n/res/values-pt/strings.xml +++ b/tools/recovery_l10n/res/values-pt/strings.xml @@ -6,14 +6,9 @@ "Nenhum comando" "Erro!" "Instalando atualização de segurança" - - - - - - - - - - + "Não é possível carregar o sistema Android. Seus dados podem estar corrompidos. Se você continuar recebendo esta mensagem, talvez seja necessário realizar uma redefinição para a configuração original e limpar todos os dados do usuário armazenados neste dispositivo." + "Tentar novamente" + "Redefinição para configuração original" + "Limpar todos os dados do usuário?\n\n NÃO É POSSÍVEL DESFAZER ESSA AÇÃO." + "Cancelar" diff --git a/tools/recovery_l10n/res/values-ro/strings.xml b/tools/recovery_l10n/res/values-ro/strings.xml index 4d90718e9..585db8355 100644 --- a/tools/recovery_l10n/res/values-ro/strings.xml +++ b/tools/recovery_l10n/res/values-ro/strings.xml @@ -6,14 +6,9 @@ "Nicio comandă" "Eroare!" "Se instalează actualizarea de securitate" - - - - - - - - - - + "Nu se poate încărca sistemul Android. Datele dvs. pot fi corupte. Dacă primiți în continuare acest mesaj, poate fi necesar să reveniți la setările din fabrică și să ștergeți toate datele utilizatorului stocate pe acest dispozitiv." + "Reîncercați" + "Revenire la setările din fabrică" + "Ștergeți toate datele utilizatorului?\n\n ACEST LUCRU NU POATE FI ANULAT!" + "Anulați" diff --git a/tools/recovery_l10n/res/values-ru/strings.xml b/tools/recovery_l10n/res/values-ru/strings.xml index ab7520aeb..db8b7611b 100644 --- a/tools/recovery_l10n/res/values-ru/strings.xml +++ b/tools/recovery_l10n/res/values-ru/strings.xml @@ -6,14 +6,9 @@ "Команды нет" "Ошибка" "Установка обновления системы безопасности…" - - - - - - - - - - + "Не удалось загрузить систему Android. Возможно, данные повреждены. Если вы снова увидите это сообщение, попробуйте сбросить настройки устройства и удалить все пользовательские данные." + "Повторить попытку" + "Сбросить настройки" + "Стереть все пользовательские данные?\n\nЭТО ДЕЙСТВИЕ НЕЛЬЗЯ ОТМЕНИТЬ." + "Отмена" diff --git a/tools/recovery_l10n/res/values-si/strings.xml b/tools/recovery_l10n/res/values-si/strings.xml index cffb6e9b2..67aca72f8 100644 --- a/tools/recovery_l10n/res/values-si/strings.xml +++ b/tools/recovery_l10n/res/values-si/strings.xml @@ -6,14 +6,9 @@ "විධානයක් නොමැත" "දෝෂය!" "ආරක්ෂක යාවත්කාලීනය ස්ථාපනය කරමින්" - - - - - - - - - - + "Android පද්ධතිය පූරණය කළ නොහැකිය. ඔබේ දත්ත දූෂිත විය හැකිය. ඔබට මෙම පණිවිඩය දිගටම ලැබෙන්නේ නම්, කර්මාන්ත ශාලා දත්ත යළි සැකසීමක් සිදු කර මෙම උපාංගයේ ගබඩා කළ සියලු පරිශීලක දත්ත මකා දැමීමට ඔබට අවශ්‍ය විය හැකිය." + "නැවත උත්සාහ කරන්න" + "කර්මාන්ත ශාලා දත්ත යළි සැකසීම" + "සියලු පරිශීලක දත්ත මකා දමන්නද?\n\n මෙය පසුගමනය කළ නොහැකිය!" + "අවලංගු කරන්න" diff --git a/tools/recovery_l10n/res/values-sk/strings.xml b/tools/recovery_l10n/res/values-sk/strings.xml index 59985c0e0..8a2d2e0c1 100644 --- a/tools/recovery_l10n/res/values-sk/strings.xml +++ b/tools/recovery_l10n/res/values-sk/strings.xml @@ -6,14 +6,9 @@ "Žiadny príkaz" "Chyba!" "Inštaluje sa bezpečnostná aktualizácia" - - - - - - - - - - + "Systém Android sa nedá načítať. Vaše údaje môžu byť poškodené. Ak chcete získať túto správu a budete pokračovať, zrejme budete musieť obnoviť výrobné nastavenia a vymazať tak všetky údaje používateľa uložené v tomto zariadení." + "Skúsiť znova" + "Obnovenie výrobných nastavení" + "Chcete vymazať všetky údaje používateľa?\n\n TÁTO AKCIA SA NEDÁ VRÁTIŤ SPÄŤ!" + "Zrušiť" diff --git a/tools/recovery_l10n/res/values-sl/strings.xml b/tools/recovery_l10n/res/values-sl/strings.xml index e36dfb458..653c4274e 100644 --- a/tools/recovery_l10n/res/values-sl/strings.xml +++ b/tools/recovery_l10n/res/values-sl/strings.xml @@ -6,14 +6,9 @@ "Ni ukaza" "Napaka" "Nameščanje varnostne posodobitve" - - - - - - - - - - + "Sistema Android ni mogoče naložiti. Podatki so morda poškodovani. Če se bo to sporočilo še naprej prikazovalo, boste morda morali izvesti ponastavitev na tovarniške nastavitve in izbrisati vse uporabniške podatke, ki so shranjeni v tej napravi." + "Poskusi znova" + "Ponastavitev na tovarniške nastavitve" + "Želite izbrisati vse uporabniške podatke?\n\n TEGA NI MOGOČE RAZVELJAVITI!" + "Prekliči" diff --git a/tools/recovery_l10n/res/values-sq/strings.xml b/tools/recovery_l10n/res/values-sq/strings.xml index 58945c0e9..5c824e683 100644 --- a/tools/recovery_l10n/res/values-sq/strings.xml +++ b/tools/recovery_l10n/res/values-sq/strings.xml @@ -6,14 +6,9 @@ "Nuk ka komanda" "Gabim!" "Po instalon përditësimin e sigurisë" - - - - - - - - - - + "Sistemi Android nuk mund të ngarkohet. Të dhënat e tua mund të jenë të dëmtuara. Nëse vazhdon të marrësh këtë mesazh, mund të jetë e nevojshme të kryesh një rivendosje të të dhënave të fabrikës dhe të spastrosh të gjitha të dhënat e përdoruesit të ruajtura në këtë pajisje." + "Provo përsëri" + "Rivendosja e të dhënave të fabrikës" + "Të pastrohen të gjitha të dhënat e përdoruesit?\n\n KJO NUK MUND TË ZHBËHET!" + "Anulo" diff --git a/tools/recovery_l10n/res/values-sr/strings.xml b/tools/recovery_l10n/res/values-sr/strings.xml index 275a3aaf2..1583beaaf 100644 --- a/tools/recovery_l10n/res/values-sr/strings.xml +++ b/tools/recovery_l10n/res/values-sr/strings.xml @@ -6,14 +6,9 @@ "Нема команде" "Грешка!" "Инсталира се безбедносно ажурирање" - - - - - - - - - - + "Учитавање Android система није успело. Подаци су можда оштећени. Ако наставите да добијате ову поруку, можда ћете морати да ресетујете уређај на фабричка подешавања и обришете све податке корисника које чувате на њему." + "Пробај поново" + "Ресетовање на фабричка подешавања" + "Желите ли да избришете све податке корисника?\n\n ОВО НЕ МОЖЕ ДА СЕ ОПОЗОВЕ!" + "Откажи" diff --git a/tools/recovery_l10n/res/values-sv/strings.xml b/tools/recovery_l10n/res/values-sv/strings.xml index 512b5d4b4..cf43b2511 100644 --- a/tools/recovery_l10n/res/values-sv/strings.xml +++ b/tools/recovery_l10n/res/values-sv/strings.xml @@ -6,14 +6,9 @@ "Inget kommando" "Fel!" "Säkerhetsuppdatering installeras" - - - - - - - - - - + "Det gick inte att läsa in Android-systemet. Data kan ha skadats. Om det här meddelandet visas igen kan du behöva återställa standardinställningarna så att all användardata som sparats på enheten raderas." + "Försök igen" + "Återställ standardinställningarna" + "Vill du rensa bort all användardata?\n\n DET GÅR INTE ATT ÅNGRA DENNA ÅTGÄRD." + "Avbryt" diff --git a/tools/recovery_l10n/res/values-sw/strings.xml b/tools/recovery_l10n/res/values-sw/strings.xml index b0a072f96..6fa72825a 100644 --- a/tools/recovery_l10n/res/values-sw/strings.xml +++ b/tools/recovery_l10n/res/values-sw/strings.xml @@ -6,14 +6,9 @@ "Hakuna amri" "Hitilafu fulani imetokea!" "Inasakinisha sasisho la usalama" - - - - - - - - - - + "Imeshindwa kupakia mfumo wa Android. Huenda data yako imeharibika. Kama utandelea kupata ujumbe huu, huenda ukahitaji kurejesha data iliyotoka nayo kiwandani na ufute data yote ya mtumiaji iliyohifadhiwa kwenye kifaa hiki." + "Jaribu tena" + "Kurejesha data iliyotoka nayo kiwandani" + "Ungependa kufuta data yote ya mtumiaji?\n\n KITENDO HIKI HAKIWEZI KUTENDULIWA!" + "Ghairi" diff --git a/tools/recovery_l10n/res/values-ta/strings.xml b/tools/recovery_l10n/res/values-ta/strings.xml index ff2e3e792..bc370f7bf 100644 --- a/tools/recovery_l10n/res/values-ta/strings.xml +++ b/tools/recovery_l10n/res/values-ta/strings.xml @@ -6,14 +6,9 @@ "கட்டளை இல்லை" "பிழை!" "பாதுகாப்புப் புதுப்பிப்பை நிறுவுகிறது" - - - - - - - - - - + "Android சிஸ்டத்தைக் காண்பிக்க இயலவில்லை. உங்களின் தரவு சிதைந்திருக்கலாம். இந்த மெசேஜ் உங்களுக்குத் தொடர்ந்து வந்தால், தரவின் ஆரம்பநிலைக்கு மீட்டமைத்தல் மற்றும் இந்தச் சாதனத்தில் சேமிக்கப்பட்டுள்ள அனைத்துப் பயனர் தரவையும் அழித்தல் ஆகியவற்றைச் செய்ய வேண்டியிருக்கலாம்." + "மீண்டும் முயல்க" + "தரவின் ஆரம்பநிலை மீட்டமைப்பு" + "பயனரின் அனைத்துத் தரவையும் நீக்கவா?\n\n இதைச் செயல்தவிர்க்க இயலாது!" + "இல்லை" diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml index 66039ac96..4d521143f 100644 --- a/tools/recovery_l10n/res/values-te/strings.xml +++ b/tools/recovery_l10n/res/values-te/strings.xml @@ -6,14 +6,9 @@ "ఆదేశం లేదు" "ఎర్రర్ సంభవించింది!" "భద్రతా నవీకరణను ఇన్‌స్టాల్ చేస్తోంది" - - - - - - - - - - + "Android సిస్టమ్‌ని లోడ్ చేయడం సాధ్యం కాదు. మీ డేటా పాడై ఉండవచ్చు. మీకు ఈ సందేశం వస్తూనే ఉంటే, మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసి, పరికరంలో నిల్వ అయిన వినియోగదారు డేటా మొత్తాన్ని తొలగించాల్సి రావచ్చు." + "మళ్లీ ప్రయత్నించు" + "ఫ్యాక్టరీ డేటా రీసెట్" + "వినియోగదారు డేటా మొత్తాన్ని తొలగించాలా?\n\n ఈ చర్యను రద్దు చేయలేరు!" + "రద్దు చేయి" diff --git a/tools/recovery_l10n/res/values-th/strings.xml b/tools/recovery_l10n/res/values-th/strings.xml index 3b3158165..83d445dfe 100644 --- a/tools/recovery_l10n/res/values-th/strings.xml +++ b/tools/recovery_l10n/res/values-th/strings.xml @@ -6,14 +6,9 @@ "ไม่มีคำสั่ง" "ข้อผิดพลาด!" "กำลังติดตั้งการอัปเดตความปลอดภัย" - - - - - - - - - - + "โหลดระบบ Android ไม่ได้ ข้อมูลของคุณอาจเสียหาย หากคุณยังคงได้รับข้อความนี้อยู่ คุณอาจต้องรีเซ็ตข้อมูลเป็นค่าเริ่มต้นและลบข้อมูลผู้ใช้ทั้งหมดที่เก็บอยู่ในอุปกรณ์นี้" + "ลองอีกครั้ง" + "รีเซ็ตข้อมูลเป็นค่าเริ่มต้น" + "ต้องการล้างข้อมูลผู้ใช้ทั้งหมดใช่ไหม\n\n การกระทำนี้จะยกเลิกไม่ได้" + "ยกเลิก" diff --git a/tools/recovery_l10n/res/values-tl/strings.xml b/tools/recovery_l10n/res/values-tl/strings.xml index b414fd944..6621473fd 100644 --- a/tools/recovery_l10n/res/values-tl/strings.xml +++ b/tools/recovery_l10n/res/values-tl/strings.xml @@ -6,14 +6,9 @@ "Walang command" "Error!" "Nag-i-install ng update sa seguridad" - - - - - - - - - - + "Hindi ma-load ang Android system. Maaaring sira ang iyong data. Kung patuloy mong matatanggap ang mensaheng ito, maaaring kailanganin mong magsagawa ng pag-reset ng factory data at burahin ang lahat ng data ng user na naka-store sa device na ito." + "Subukang muli" + "Pag-reset ng factory data" + "I-wipe ang lahat ng data ng user?\n\n HINDI ITO MAA-UNDO!" + "Kanselahin" diff --git a/tools/recovery_l10n/res/values-tr/strings.xml b/tools/recovery_l10n/res/values-tr/strings.xml index 4e948b70d..e4eca52d8 100644 --- a/tools/recovery_l10n/res/values-tr/strings.xml +++ b/tools/recovery_l10n/res/values-tr/strings.xml @@ -6,14 +6,9 @@ "Komut yok" "Hata!" "Güvenlik güncellemesi yükleniyor" - - - - - - - - - - + "Android sisteminiz yüklenemedi. Verileriniz bozulmuş olabilir. Bu mesajı almaya devam ederseniz fabrika verilerine sıfırlama işlemi yapmanız ve bu cihazda depolanan tüm kullanıcı verilerini silmeniz gerekebilir." + "Tekrar dene" + "Fabrika verilerine sıfırla" + "Tüm kullanıcı verileri silinsin mi?\n\n BU İŞLEM GERİ ALINAMAZ!" + "İptal" diff --git a/tools/recovery_l10n/res/values-uk/strings.xml b/tools/recovery_l10n/res/values-uk/strings.xml index 196562070..7bd6fecf0 100644 --- a/tools/recovery_l10n/res/values-uk/strings.xml +++ b/tools/recovery_l10n/res/values-uk/strings.xml @@ -6,14 +6,9 @@ "Немає команди" "Помилка!" "Установлюється оновлення системи безпеки" - - - - - - - - - - + "Не вдається завантажити систему Android. Можливо, ваші дані пошкоджено. Якщо ви далі отримуватимете це повідомлення, можливо, доведеться відновити заводські налаштування й видалити всі дані користувача з цього пристрою." + "Повторити" + "Відновити заводські налаштування" + "Видалити всі дані користувача?\n\n ЦЮ ДІЮ НЕ МОЖНА ВІДМІНИТИ." + "Скасувати" diff --git a/tools/recovery_l10n/res/values-ur/strings.xml b/tools/recovery_l10n/res/values-ur/strings.xml index 9ace0f08d..da03f1972 100644 --- a/tools/recovery_l10n/res/values-ur/strings.xml +++ b/tools/recovery_l10n/res/values-ur/strings.xml @@ -6,14 +6,9 @@ "کوئی کمانڈ نہیں ہے" "خرابی!" "سیکیورٹی اپ ڈیٹ انسٹال ہو رہی ہے" - - - - - - - - - - + "‏Android سسٹم لوڈ نہیں کیا جا سکتا۔ آپ کا ڈیٹا خراب ہو سکتا ہے۔ اگر آپ کو مستقل یہ پیغام موصول ہوتا ہے تو آپ کو فیکٹری ڈیٹا کی دوبارہ ترتیب انجام دینے اور اس آلہ پر اسٹور کردہ سبھی صارف ڈیٹا کو مٹانے کی ضرورت پڑ سکتی ہے۔" + "دوبارہ کوشش کریں" + "فیکٹری ڈیٹا کی دوبارہ ترتیب" + "سبھی صارف ڈیٹا صاف کریں؟\n\n اسے کالعدم نہیں کیا جا سکتا!" + "منسوخ کریں" diff --git a/tools/recovery_l10n/res/values-uz/strings.xml b/tools/recovery_l10n/res/values-uz/strings.xml index 676be7a6d..9bde4c6bf 100644 --- a/tools/recovery_l10n/res/values-uz/strings.xml +++ b/tools/recovery_l10n/res/values-uz/strings.xml @@ -6,14 +6,9 @@ "Buyruq yo‘q" "Xato!" "Xavfsizlik yangilanishi o‘rnatilmoqda" - - - - - - - - - - + "Android tizimi yuklanmadi. Maʼlumotlaringiz buzuq shekilli. Yana shu xabarni olsangiz, zavod sozlamalarini tiklashingiz va bu qurilmadagi barcha maʼlumotlarni tozalab tashlashingiz lozim." + "Qayta urinish" + "Zavod sozlamalarini tiklash" + "Barcha maʼlumotlar tozalab tashlansinmi?\n\n ULARNI TIKLASH IMKONSIZ!" + "Bekor qilish" diff --git a/tools/recovery_l10n/res/values-vi/strings.xml b/tools/recovery_l10n/res/values-vi/strings.xml index fce6cd901..3753394e6 100644 --- a/tools/recovery_l10n/res/values-vi/strings.xml +++ b/tools/recovery_l10n/res/values-vi/strings.xml @@ -6,14 +6,9 @@ "Không có lệnh nào" "Lỗi!" "Đang cài đặt bản cập nhật bảo mật" - - - - - - - - - - + "Không thể tải hệ thống Android. Dữ liệu của bạn có thể bị hỏng. Nếu tiếp tục thấy thông báo này, bạn có thể cần phải thiết lập lại dữ liệu ban đầu và xóa tất cả dữ liệu người dùng lưu trữ trên thiết bị này." + "Thử lại" + "Thiết lập lại dữ liệu ban đầu" + "Xóa sạch tất cả dữ liệu người dùng?\n\n KHÔNG THỂ HOÀN TÁC THAO TÁC NÀY!" + "Hủy" diff --git a/tools/recovery_l10n/res/values-zh-rCN/strings.xml b/tools/recovery_l10n/res/values-zh-rCN/strings.xml index 66c876d75..ab1fdbbc2 100644 --- a/tools/recovery_l10n/res/values-zh-rCN/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rCN/strings.xml @@ -6,14 +6,9 @@ "无命令" "出错了!" "正在安装安全更新" - - - - - - - - - - + "无法加载 Android 系统。您的数据可能已损坏。如果系统仍然显示这条消息,您可能需要恢复出厂设置,并清空存储在此设备上的所有用户数据。" + "重试" + "恢复出厂设置" + "是否清除所有用户数据?\n\n此操作无法撤消!" + "取消" diff --git a/tools/recovery_l10n/res/values-zh-rHK/strings.xml b/tools/recovery_l10n/res/values-zh-rHK/strings.xml index 5dc38d7a4..55ce31e93 100644 --- a/tools/recovery_l10n/res/values-zh-rHK/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rHK/strings.xml @@ -6,14 +6,9 @@ "沒有指令" "錯誤!" "正在安裝安全性更新" - - - - - - - - - - + "無法載入 Android 系統。您的資料可能已損壞。如您繼續收到此訊息,則可能需要將裝置回復原廠設定,並清除儲存在裝置上的所有使用者資料。" + "再試一次" + "回復原廠設定" + "要清除所有使用者資料嗎?\n\n這項操作無法復原!" + "取消" diff --git a/tools/recovery_l10n/res/values-zh-rTW/strings.xml b/tools/recovery_l10n/res/values-zh-rTW/strings.xml index e94038b29..0a777a6e3 100644 --- a/tools/recovery_l10n/res/values-zh-rTW/strings.xml +++ b/tools/recovery_l10n/res/values-zh-rTW/strings.xml @@ -6,14 +6,9 @@ "沒有指令" "錯誤!" "正在安裝安全性更新" - - - - - - - - - - + "無法載入 Android 系統。你的資料可能已經損毀。如果系統持續顯示這則訊息,你可能必須恢復原廠設定,並清除裝置上儲存的所有使用者資料。" + "再試一次" + "恢復原廠設定" + "要抹除所有使用者資料嗎?\n\n請注意,一旦抹除就無法復原!" + "取消" diff --git a/tools/recovery_l10n/res/values-zu/strings.xml b/tools/recovery_l10n/res/values-zu/strings.xml index eef2293d3..4667dac40 100644 --- a/tools/recovery_l10n/res/values-zu/strings.xml +++ b/tools/recovery_l10n/res/values-zu/strings.xml @@ -6,14 +6,9 @@ "Awukho umyalo" "Iphutha!" "Ifaka isibuyekezo sokuphepha" - - - - - - - - - - + "Ayikwazi ukulayisha isistimu ye-Android. Idatha yakho kungenzeka yonakele. Uma uqhubeka ukuthola lo mlayezo, kungenzeka kumele wenze ukusethwa kabusha kwasekuqaleni kwedatha uphinde usule yonke idatha yomsebenzisi egcinwe kule divayisi." + "Zama futhi" + "Ukuhlela kabusha idatha yasembonini" + "Sula yonke idatha yomsebenzisi?\n\n LOKHU AKUKWAZI UKUHLEHLISWA!" + "Khansela" -- cgit v1.2.3 From d5c7fb5e6efb98a4b8072be3c1e7e505b3344466 Mon Sep 17 00:00:00 2001 From: koushik panuganti Date: Wed, 12 Dec 2018 10:39:44 -0800 Subject: Migrate bootable/recovery to androidx.test See go/jetpack-test-android-migration Test: make checkbuild Change-Id: I0740a2205e6b3893ba292cd841592ea85071eefb --- updater_sample/tests/Android.bp | 2 +- updater_sample/tests/AndroidManifest.xml | 2 +- .../com/example/android/systemupdatersample/UpdateConfigTest.java | 7 ++++--- .../com/example/android/systemupdatersample/UpdateManagerTest.java | 7 ++++--- .../android/systemupdatersample/util/FileDownloaderTest.java | 7 ++++--- .../example/android/systemupdatersample/util/PayloadSpecsTest.java | 7 ++++--- .../android/systemupdatersample/util/UpdateConfigsTest.java | 4 ++-- 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/updater_sample/tests/Android.bp b/updater_sample/tests/Android.bp index 7867770a0..e43440518 100644 --- a/updater_sample/tests/Android.bp +++ b/updater_sample/tests/Android.bp @@ -23,7 +23,7 @@ android_test { ], static_libs: [ - "android-support-test", + "androidx.test.runner", "mockito-target-minus-junit4", "guava", ], diff --git a/updater_sample/tests/AndroidManifest.xml b/updater_sample/tests/AndroidManifest.xml index 76af5f1a9..a2da3f736 100644 --- a/updater_sample/tests/AndroidManifest.xml +++ b/updater_sample/tests/AndroidManifest.xml @@ -26,7 +26,7 @@ - diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java index 48d0e424d..ae666830b 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateConfigTest.java @@ -21,9 +21,10 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.tests.R; import com.google.common.io.CharStreams; diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java index e05ad290c..6bef6de0e 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java @@ -26,9 +26,10 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.os.UpdateEngine; import android.os.UpdateEngineCallback; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.tests.R; import com.example.android.systemupdatersample.util.PayloadSpecs; 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 a136ff0ed..ede24577e 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 @@ -19,9 +19,10 @@ package com.example.android.systemupdatersample.util; import static org.junit.Assert.assertEquals; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.tests.R; 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 03086930e..9931e7bfd 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 @@ -22,9 +22,10 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.PayloadSpec; import com.example.android.systemupdatersample.tests.R; diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java index 4ccae9380..4a967a951 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/util/UpdateConfigsTest.java @@ -18,8 +18,8 @@ package com.example.android.systemupdatersample.util; import static org.junit.Assert.assertArrayEquals; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.example.android.systemupdatersample.UpdateConfig; -- cgit v1.2.3 From 75f4073baf4b480f3d27da6b181e4aec6e30856d Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 14 Dec 2018 09:36:32 -0800 Subject: Add PrepareUpdateService. It's moved from PrepareStreamingService intent service. Now PrepareUpdateService takes an UpdateConfig and builds PayloadSpec for UpdateEngine for both streaming and non-streaming update. It allows us to do all preparations in intent service's thread, without blocking UI. We will also add checksum verification to PrepareUpdateService. Test: device, junit Bug: 77150191 Change-Id: Iea69acd9aa41e17538c26aff60f7598093ca7744 --- updater_sample/AndroidManifest.xml | 2 +- updater_sample/README.md | 6 +- .../android/systemupdatersample/UpdateManager.java | 79 +++---- .../services/PrepareStreamingService.java | 251 -------------------- .../services/PrepareUpdateService.java | 258 +++++++++++++++++++++ .../systemupdatersample/ui/MainActivity.java | 4 +- .../systemupdatersample/util/FileDownloader.java | 2 +- updater_sample/tests/Android.bp | 1 + .../systemupdatersample/UpdateManagerTest.java | 75 +++--- 9 files changed, 338 insertions(+), 340 deletions(-) delete mode 100644 updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java create mode 100644 updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml index 18d8425e1..0a2511617 100644 --- a/updater_sample/AndroidManifest.xml +++ b/updater_sample/AndroidManifest.xml @@ -33,7 +33,7 @@ - + diff --git a/updater_sample/README.md b/updater_sample/README.md index f9c3fb8ec..5894cf8cd 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -220,7 +220,7 @@ privileged system app, so it's granted the required permissions to access - [x] Add Sample app update state (separate from update_engine status) - [x] Add smart update completion detection using onStatusUpdate - [x] Add pause/resume demo -- [x] Verify system partition checksum for package +- [-] Verify system partition checksum for package ## Running tests @@ -235,8 +235,8 @@ privileged system app, so it's granted the required permissions to access 5. Run a test file ``` adb shell am instrument \ - -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner \ - -c com.example.android.systemupdatersample.util.PayloadSpecsTest + -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \ + com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner ``` diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java index 12a8f3f5f..c02e60846 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java +++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java @@ -17,19 +17,18 @@ package com.example.android.systemupdatersample; import android.content.Context; +import android.os.Handler; import android.os.UpdateEngine; import android.os.UpdateEngineCallback; import android.util.Log; -import com.example.android.systemupdatersample.services.PrepareStreamingService; -import com.example.android.systemupdatersample.util.PayloadSpecs; +import com.example.android.systemupdatersample.services.PrepareUpdateService; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineProperties; 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; @@ -50,11 +49,10 @@ public class UpdateManager { private static final String TAG = "UpdateManager"; /** 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) " + 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 final UpdateEngine mUpdateEngine; - private final PayloadSpecs mPayloadSpecs; private AtomicInteger mUpdateEngineStatus = new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE); @@ -84,9 +82,15 @@ public class UpdateManager { private final UpdateManager.UpdateEngineCallbackImpl mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl(); - public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) { + private final Handler mHandler; + + /** + * @param updateEngine UpdateEngine instance. + * @param handler Handler for {@link PrepareUpdateService} intent service. + */ + public UpdateManager(UpdateEngine updateEngine, Handler handler) { this.mUpdateEngine = updateEngine; - this.mPayloadSpecs = payloadSpecs; + this.mHandler = handler; } /** @@ -293,45 +297,17 @@ public class UpdateManager { mManualSwitchSlotRequired.set(false); } - if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) { - applyAbNonStreamingUpdate(config); - } else { - applyAbStreamingUpdate(context, config); - } - } - - private void applyAbNonStreamingUpdate(UpdateConfig config) - throws UpdaterState.InvalidTransitionException { - UpdateData.Builder builder = UpdateData.builder() - .setExtraProperties(prepareExtraProperties(config)); - - try { - builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile())); - } catch (IOException e) { - Log.e(TAG, "Error creating payload spec", e); - setUpdaterState(UpdaterState.ERROR); - return; - } - updateEngineApplyPayload(builder.build()); - } - - private void applyAbStreamingUpdate(Context context, UpdateConfig 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) { - builder.setPayload(payloadSpec); - builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT); - config.getAbConfig() - .getAuthorization() - .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s)); - updateEngineApplyPayload(builder.build()); - } else { - Log.e(TAG, "PrepareStreamingService failed, result code is " + code); + Log.d(TAG, "Starting PrepareUpdateService"); + PrepareUpdateService.startService(context, config, mHandler, (code, payloadSpec) -> { + if (code != PrepareUpdateService.RESULT_CODE_SUCCESS) { + Log.e(TAG, "PrepareUpdateService failed, result code is " + code); setUpdaterStateSilent(UpdaterState.ERROR); + return; } + updateEngineApplyPayload(UpdateData.builder() + .setExtraProperties(prepareExtraProperties(config)) + .setPayload(payloadSpec) + .build()); }); } @@ -343,6 +319,12 @@ public class UpdateManager { // User will enable it manually by clicking "Switch Slot" button on the screen. extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT); } + if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_STREAMING) { + extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT); + config.getAbConfig() + .getAuthorization() + .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s)); + } return extraProperties; } @@ -497,14 +479,14 @@ public class UpdateManager { * system/update_engine/binder_service_android.cc in * function BinderUpdateEngineAndroidService::bind). * - * @param status one of {@link UpdateEngine.UpdateStatusConstants}. + * @param status one of {@link UpdateEngine.UpdateStatusConstants}. * @param progress a number from 0.0 to 1.0. */ private void onStatusUpdate(int status, float progress) { Log.d(TAG, String.format( - "onStatusUpdate invoked, status=%s, progress=%.2f", - status, - progress)); + "onStatusUpdate invoked, status=%s, progress=%.2f", + status, + progress)); int previousStatus = mUpdateEngineStatus.get(); mUpdateEngineStatus.set(status); @@ -555,7 +537,6 @@ public class UpdateManager { } /** - * * Contains update data - PayloadSpec and extra properties list. * *

{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}. diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java deleted file mode 100644 index 931404857..000000000 --- a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java +++ /dev/null @@ -1,251 +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.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; - -import android.app.IntentService; -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; - -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.File; -import java.io.IOException; -import java.nio.file.Files; -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 - ); - - private final PayloadSpecs mPayloadSpecs = new PayloadSpecs(); - - @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 { - 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); - } - } - - /** - * 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 PayloadSpec execute(UpdateConfig config) - throws IOException, PreparationFailedException { - - downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); - - Optional payloadBinary = - UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config); - - if (!payloadBinary.isPresent()) { - throw new PreparationFailedException( - "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); - } - - 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"); - } - - 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"); - } - } - - return mPayloadSpecs.forStreaming(config.getUrl(), - payloadBinary.get().getOffset(), - payloadBinary.get().getSize(), - Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile()); - } - - /** - * Downloads files defined in {@link UpdateConfig#getAbConfig()} - * and exists in {@code PRE_STREAMING_FILES_SET}, and put them - * in directory {@code dir}. - * @throws IOException when can't download a file - */ - private 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.getAbConfig().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(); - } - } - } - - /** - * @param file physical location of {@link PackageFiles#COMPATIBILITY_ZIP_FILE_NAME} - * @return true if OTA package is compatible with this device - */ - private 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}. - */ - 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); - } - } - - private static class PreparationFailedException extends Exception { - PreparationFailedException(String message) { - super(message); - } - } - -} diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java new file mode 100644 index 000000000..06581bee3 --- /dev/null +++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java @@ -0,0 +1,258 @@ +/* + * 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.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; + +import android.app.IntentService; +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.os.UpdateEngine; +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.File; +import java.io.IOException; +import java.nio.file.Files; +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. + * + * PrepareUpdateService runs on it's own thread. It will notify activity + * using interface {@link UpdateResultCallback} when update is ready to install. + */ +public class PrepareUpdateService extends IntentService { + + /** + * UpdateResultCallback result codes. + */ + public static final int RESULT_CODE_SUCCESS = 0; + public static final int RESULT_CODE_ERROR = 1; + + /** + * Extra params that will be sent to IntentService. + */ + public static final String EXTRA_PARAM_CONFIG = "config"; + public static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver"; + + /** + * This interface is used to send results from {@link PrepareUpdateService} 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 PrepareUpdateService} + * @param payloadSpec prepared payload spec for streaming update + */ + void onReceiveResult(int resultCode, PayloadSpec payloadSpec); + } + + /** + * Starts PrepareUpdateService. + * + * @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, + Handler handler, + UpdateResultCallback resultCallback) { + Log.d(TAG, "Starting PrepareUpdateService"); + ResultReceiver receiver = new CallbackResultReceiver(handler, resultCallback); + Intent intent = new Intent(context, PrepareUpdateService.class); + intent.putExtra(EXTRA_PARAM_CONFIG, config); + intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver); + context.startService(intent); + } + + public PrepareUpdateService() { + super(TAG); + } + + private static final String TAG = "PrepareUpdateService"; + + /** + * 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 + ); + + private final PayloadSpecs mPayloadSpecs = new PayloadSpecs(); + private final UpdateEngine mUpdateEngine = new UpdateEngine(); + + @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 { + 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); + } + } + + /** + * 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 PayloadSpec execute(UpdateConfig config) + throws IOException, PreparationFailedException { + + if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) { + return mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()); + } + + downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); + + Optional payloadBinary = + UpdateConfigs.getPropertyFile(PAYLOAD_BINARY_FILE_NAME, config); + + if (!payloadBinary.isPresent()) { + throw new PreparationFailedException( + "Failed to find " + PAYLOAD_BINARY_FILE_NAME + " in config"); + } + + 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"); + } + + 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"); + } + } + + return mPayloadSpecs.forStreaming(config.getUrl(), + payloadBinary.get().getOffset(), + payloadBinary.get().getSize(), + Paths.get(OTA_PACKAGE_DIR, PAYLOAD_PROPERTIES_FILE_NAME).toFile()); + } + + /** + * Downloads files defined in {@link UpdateConfig#getAbConfig()} + * and exists in {@code PRE_STREAMING_FILES_SET}, and put them + * in directory {@code dir}. + * + * @throws IOException when can't download a file + */ + private 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.getAbConfig().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(); + } + } + } + + /** + * @param file physical location of {@link PackageFiles#COMPATIBILITY_ZIP_FILE_NAME} + * @return true if OTA package is compatible with this device + */ + private 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 PrepareUpdateService} 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); + } + } + + private static class PreparationFailedException extends Exception { + PreparationFailedException(String message) { + super(message); + } + } + +} 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 fc9fddd70..6d1e4c35a 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java +++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java @@ -21,6 +21,7 @@ import android.app.AlertDialog; import android.graphics.Color; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.UpdateEngine; import android.util.Log; import android.view.View; @@ -34,7 +35,6 @@ 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; @@ -67,7 +67,7 @@ public class MainActivity extends Activity { private List mConfigs; private final UpdateManager mUpdateManager = - new UpdateManager(new UpdateEngine(), new PayloadSpecs()); + new UpdateManager(new UpdateEngine(), new Handler()); @Override protected void onCreate(Bundle savedInstanceState) { 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 ddd0919b8..0f9083d27 100644 --- a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java +++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java @@ -30,7 +30,7 @@ import java.net.URLConnection; * Downloads chunk of a file from given url using {@code offset} and {@code size}, * and saves to a given location. * - * In real-life application this helper class should download from HTTP Server, + * In a real-life application this helper class should download from HTTP Server, * but in this sample app it will only download from a local file. */ public final class FileDownloader { diff --git a/updater_sample/tests/Android.bp b/updater_sample/tests/Android.bp index e43440518..806babd9e 100644 --- a/updater_sample/tests/Android.bp +++ b/updater_sample/tests/Android.bp @@ -24,6 +24,7 @@ android_test { static_libs: [ "androidx.test.runner", + "androidx.test.rules", "mockito-target-minus-junit4", "guava", ], diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java index 6bef6de0e..af891f007 100644 --- a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java +++ b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java @@ -18,21 +18,26 @@ package com.example.android.systemupdatersample; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.ResultReceiver; import android.os.UpdateEngine; import android.os.UpdateEngineCallback; import androidx.test.InstrumentationRegistry; +import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.example.android.systemupdatersample.services.PrepareUpdateService; import com.example.android.systemupdatersample.tests.R; -import com.example.android.systemupdatersample.util.PayloadSpecs; import com.google.common.collect.ImmutableList; import com.google.common.io.CharStreams; @@ -44,7 +49,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import java.io.File; import java.io.IOException; import java.io.InputStreamReader; @@ -61,49 +65,39 @@ public class UpdateManagerTest { @Mock private UpdateEngine mUpdateEngine; @Mock - private PayloadSpecs mPayloadSpecs; + private Context mMockContext; private UpdateManager mSubject; - private Context mContext; - private UpdateConfig mNonStreamingUpdate003; + private Context mTestContext; + private UpdateConfig mStreamingUpdate002; @Before public void setUp() throws Exception { - mContext = InstrumentationRegistry.getContext(); - mSubject = new UpdateManager(mUpdateEngine, mPayloadSpecs); - mNonStreamingUpdate003 = - UpdateConfig.fromJson(readResource(R.raw.update_config_003_nonstream)); + mTestContext = InstrumentationRegistry.getContext(); + mSubject = new UpdateManager(mUpdateEngine, null); + mStreamingUpdate002 = + UpdateConfig.fromJson(readResource(R.raw.update_config_002_stream)); } @Test public void applyUpdate_appliesPayloadToUpdateEngine() throws Exception { - PayloadSpec payload = buildMockPayloadSpec(); - when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload); - when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> { - // When UpdateManager is bound to update_engine, it passes - // UpdateEngineCallback as a callback to update_engine. - UpdateEngineCallback callback = answer.getArgument(0); - callback.onStatusUpdate( - UpdateEngine.UpdateStatusConstants.IDLE, - /*engineProgress*/ 0.0f); - return null; - }); - - mSubject.bind(); - mSubject.applyUpdate(null, mNonStreamingUpdate003); + mockContextStartServiceAnswer(buildMockPayloadSpec()); + mSubject.applyUpdate(mMockContext, mStreamingUpdate002); verify(mUpdateEngine).applyPayload( "file://blah", 120, 340, - new String[] { - "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false + new String[]{ + "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false + "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT }); } @Test - public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Exception { - PayloadSpec payload = buildMockPayloadSpec(); - when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload); + @UiThreadTest + public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Throwable { + mockContextStartServiceAnswer(buildMockPayloadSpec()); + // UpdateEngine always returns IDLE status. when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> { // When UpdateManager is bound to update_engine, it passes // UpdateEngineCallback as a callback to update_engine. @@ -115,21 +109,36 @@ public class UpdateManagerTest { }); mSubject.bind(); - mSubject.applyUpdate(null, mNonStreamingUpdate003); + mSubject.applyUpdate(mMockContext, mStreamingUpdate002); mSubject.unbind(); mSubject.bind(); // re-bind - now it should re-apply last update assertEquals(mSubject.getUpdaterState(), UpdaterState.RUNNING); - // it should be called 2 times verify(mUpdateEngine, times(2)).applyPayload( "file://blah", 120, 340, - new String[] { - "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false + new String[]{ + "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false + "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT }); } + private void mockContextStartServiceAnswer(PayloadSpec payloadSpec) { + doAnswer(args -> { + Intent intent = args.getArgument(0); + ResultReceiver resultReceiver = intent.getParcelableExtra( + PrepareUpdateService.EXTRA_PARAM_RESULT_RECEIVER); + Bundle b = new Bundle(); + b.putSerializable( + /* PrepareUpdateService.CallbackResultReceiver.BUNDLE_PARAM_PAYLOAD_SPEC */ + "payload-spec", + payloadSpec); + resultReceiver.send(PrepareUpdateService.RESULT_CODE_SUCCESS, b); + return null; + }).when(mMockContext).startService(any(Intent.class)); + } + private PayloadSpec buildMockPayloadSpec() { PayloadSpec payload = mock(PayloadSpec.class); when(payload.getUrl()).thenReturn("file://blah"); @@ -141,7 +150,7 @@ public class UpdateManagerTest { private String readResource(int id) throws IOException { return CharStreams.toString(new InputStreamReader( - mContext.getResources().openRawResource(id))); + mTestContext.getResources().openRawResource(id))); } } -- cgit v1.2.3 From 79f4680e4d706e43e58b4258ea47cab4fd81c526 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Sun, 13 Jan 2019 12:48:24 -0800 Subject: Add libprocessgroup dependency Because set_sched_policy is moved into libprocessgroup an additional dependency is requred for recovery_component_test to build. Exempt-From-Owner-Approval: janitorial Bug: 111307099 Test: builds, boots Change-Id: I7cf75e473ee1e2837940606c71d15be26db0c3f2 Signed-off-by: Suren Baghdasaryan --- tests/Android.bp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Android.bp b/tests/Android.bp index 1d6a056f6..898ed7d60 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -29,6 +29,7 @@ cc_defaults { "libcutils", "liblog", "libpng", + "libprocessgroup", "libselinux", "libz", "libziparchive", -- cgit v1.2.3 From 25bfb1bea9214ffe6f6969036322a45785cb6f7f Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Fri, 1 Feb 2019 21:22:54 -0800 Subject: Import translations. DO NOT MERGE Change-Id: Id46b7b2d7bf8f9fe1edd9bb90cc0e3d279f3472c Auto-generated-cl: translation import --- tools/recovery_l10n/res/values-en-rXC/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/recovery_l10n/res/values-en-rXC/strings.xml b/tools/recovery_l10n/res/values-en-rXC/strings.xml index 61390f113..18b077f29 100644 --- a/tools/recovery_l10n/res/values-en-rXC/strings.xml +++ b/tools/recovery_l10n/res/values-en-rXC/strings.xml @@ -1,14 +1,14 @@ - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎Installing system update‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎Erasing‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device.‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎Try again‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎Factory data reset‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‎Wipe all user data?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ THIS CAN NOT BE UNDONE!‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎Cancel‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎Installing system update‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎Erasing‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎Try again‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎Factory data reset‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‎Wipe all user data?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ THIS CAN NOT BE UNDONE!‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎Cancel‎‏‎‎‏‎" -- cgit v1.2.3 From aab4b829f46ad6aaeaf86a4886b95a10516dc394 Mon Sep 17 00:00:00 2001 From: xunchang Date: Wed, 13 Mar 2019 14:21:48 -0700 Subject: Update_verifier: Remove the support for legacy text format CareMap We have already switched to the protobuf format for new builds, and the downgrade packages will require a data wipe. So it should be safe to drop the support for text format. This also helps to save the issue when users sideload a package with a pending OTA, because the new CareMap contains the fingerprint of the intended build. Bug: 128536706 Test: unit tests pass, run update_verifier with legacy CareMap Change-Id: I1c4d0e54ec591f16cc0a65dac76767725ff9e7c4 (cherry picked from commit aaa6103ae72985d061432745e668df9ca29d6ac2) --- tests/component/update_verifier_test.cpp | 44 ++------------------ .../include/update_verifier/update_verifier.h | 3 -- update_verifier/update_verifier.cpp | 47 ++-------------------- 3 files changed, 6 insertions(+), 88 deletions(-) diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp index 0a594037c..e27e58c22 100644 --- a/tests/component/update_verifier_test.cpp +++ b/tests/component/update_verifier_test.cpp @@ -98,7 +98,7 @@ TEST_F(UpdateVerifierTest, verify_image_no_care_map) { ASSERT_FALSE(verifier_.ParseCareMap()); } -TEST_F(UpdateVerifierTest, verify_image_smoke) { +TEST_F(UpdateVerifierTest, verify_image_text_format) { // This test relies on dm-verity support. if (!verity_supported) { GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; @@ -107,49 +107,11 @@ TEST_F(UpdateVerifierTest, verify_image_smoke) { std::string content = "system\n2,0,1"; ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_)); - ASSERT_TRUE(verifier_.ParseCareMap()); - ASSERT_TRUE(verifier_.VerifyPartitions()); - - // Leading and trailing newlines should be accepted. - ASSERT_TRUE(android::base::WriteStringToFile("\n" + content + "\n\n", care_map_txt_)); - ASSERT_TRUE(verifier_.ParseCareMap()); - ASSERT_TRUE(verifier_.VerifyPartitions()); -} - -TEST_F(UpdateVerifierTest, verify_image_empty_care_map) { + // CareMap in text format is no longer supported. ASSERT_FALSE(verifier_.ParseCareMap()); } -TEST_F(UpdateVerifierTest, verify_image_wrong_lines) { - // The care map file can have only 2 / 4 / 6 lines. - ASSERT_TRUE(android::base::WriteStringToFile("line1", care_map_txt_)); - ASSERT_FALSE(verifier_.ParseCareMap()); - - ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2\nline3", care_map_txt_)); - ASSERT_FALSE(verifier_.ParseCareMap()); -} - -TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) { - // This test relies on dm-verity support. - if (!verity_supported) { - GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; - return; - } - - std::string content = "system\n2,1,0"; - ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_)); - ASSERT_FALSE(verifier_.ParseCareMap()); -} - -TEST_F(UpdateVerifierTest, verify_image_legacy_care_map) { - // This test relies on dm-verity support. - if (!verity_supported) { - GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support."; - return; - } - - std::string content = "/dev/block/bootdevice/by-name/system\n2,1,0"; - ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_)); +TEST_F(UpdateVerifierTest, verify_image_empty_care_map) { ASSERT_FALSE(verifier_.ParseCareMap()); } diff --git a/update_verifier/include/update_verifier/update_verifier.h b/update_verifier/include/update_verifier/update_verifier.h index b00890e82..4c64b1ea1 100644 --- a/update_verifier/include/update_verifier/update_verifier.h +++ b/update_verifier/include/update_verifier/update_verifier.h @@ -50,9 +50,6 @@ class UpdateVerifier { private: friend class UpdateVerifierTest; - // Parses the legacy care_map.txt in plain text format. - bool ParseCareMapPlainText(const std::string& content); - // Finds all the dm-enabled partitions, and returns a map of . std::map FindDmPartitions(); diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp index 5e5eac7ab..28c3fe780 100644 --- a/update_verifier/update_verifier.cpp +++ b/update_verifier/update_verifier.cpp @@ -68,6 +68,7 @@ using android::hardware::boot::V1_0::IBootControl; using android::hardware::boot::V1_0::BoolResult; using android::hardware::boot::V1_0::CommandResult; +// TODO(xunchang) remove the prefix and use a default path instead. constexpr const char* kDefaultCareMapPrefix = "/data/ota_package/care_map"; // Find directories in format of "/sys/block/dm-X". @@ -196,51 +197,13 @@ bool UpdateVerifier::VerifyPartitions() { return true; } -bool UpdateVerifier::ParseCareMapPlainText(const std::string& content) { - // care_map file has up to six lines, where every two lines make a pair. Within each pair, the - // first line has the partition name (e.g. "system"), while the second line holds the ranges of - // all the blocks to verify. - auto lines = android::base::Split(android::base::Trim(content), "\n"); - if (lines.size() != 2 && lines.size() != 4 && lines.size() != 6) { - LOG(WARNING) << "Invalid lines in care_map: found " << lines.size() - << " lines, expecting 2 or 4 or 6 lines."; - return false; - } - - for (size_t i = 0; i < lines.size(); i += 2) { - const std::string& partition_name = lines[i]; - const std::string& range_str = lines[i + 1]; - // We're seeing an N care_map.txt. Skip the verification since it's not compatible with O - // update_verifier (the last few metadata blocks can't be read via device mapper). - if (android::base::StartsWith(partition_name, "/dev/block/")) { - LOG(WARNING) << "Found legacy care_map.txt; skipped."; - return false; - } - - // For block range string, first integer 'count' equals 2 * total number of valid ranges, - // followed by 'count' number comma separated integers. Every two integers reprensent a - // block range with the first number included in range but second number not included. - // For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150). - RangeSet ranges = RangeSet::Parse(range_str); - if (!ranges) { - LOG(WARNING) << "Error parsing RangeSet string " << range_str; - return false; - } - - partition_map_.emplace(partition_name, ranges); - } - - return true; -} - bool UpdateVerifier::ParseCareMap() { partition_map_.clear(); std::string care_map_name = care_map_prefix_ + ".pb"; if (access(care_map_name.c_str(), R_OK) == -1) { - LOG(WARNING) << care_map_name - << " doesn't exist, falling back to read the care_map in plain text format."; - care_map_name = care_map_prefix_ + ".txt"; + LOG(ERROR) << care_map_name << " doesn't exist"; + return false; } android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY))); @@ -263,10 +226,6 @@ bool UpdateVerifier::ParseCareMap() { return false; } - if (android::base::EndsWith(care_map_name, ".txt")) { - return ParseCareMapPlainText(file_content); - } - recovery_update_verifier::CareMap care_map; if (!care_map.ParseFromString(file_content)) { LOG(WARNING) << "Failed to parse " << care_map_name << " in protobuf format."; -- cgit v1.2.3 From b5108c372c8b92671ea5ebb4eeff00757fcee187 Mon Sep 17 00:00:00 2001 From: Tianjie Xu Date: Mon, 20 Aug 2018 13:40:47 -0700 Subject: Move librecovery_ui to a sub-directory This helps to expose librecovery_ui for device specific RecoveryUi. Bug: 76436783 Test: mma, unit tests pass Change-Id: Ic6c3d301d5833e4a592e6ea9d9d059bc4e4919be --- Android.bp | 76 +- adb_install.cpp | 2 +- default_device.cpp | 22 - device.cpp | 96 -- device.h | 132 --- fastboot/fastboot.cpp | 3 +- fastboot/fastboot.h | 2 +- fuse_sdcard_install.h | 4 +- install.cpp | 2 +- recovery.cpp | 5 +- recovery.h | 2 +- recovery_main.cpp | 6 +- recovery_ui/Android.bp | 91 ++ recovery_ui/default_device.cpp | 22 + recovery_ui/device.cpp | 96 ++ recovery_ui/include/recovery_ui/device.h | 132 +++ recovery_ui/include/recovery_ui/screen_ui.h | 412 +++++++++ recovery_ui/include/recovery_ui/stub_ui.h | 87 ++ recovery_ui/include/recovery_ui/ui.h | 272 ++++++ recovery_ui/include/recovery_ui/vr_ui.h | 45 + recovery_ui/include/recovery_ui/wear_ui.h | 52 ++ recovery_ui/screen_ui.cpp | 1334 +++++++++++++++++++++++++++ recovery_ui/ui.cpp | 597 ++++++++++++ recovery_ui/vr_device.cpp | 22 + recovery_ui/vr_ui.cpp | 72 ++ recovery_ui/wear_device.cpp | 22 + recovery_ui/wear_ui.cpp | 108 +++ screen_ui.cpp | 1334 --------------------------- screen_ui.h | 412 --------- stub_ui.h | 87 -- tests/unit/screen_ui_test.cpp | 4 +- ui.cpp | 599 ------------ ui.h | 272 ------ vr_device.cpp | 23 - vr_ui.cpp | 72 -- vr_ui.h | 45 - wear_device.cpp | 23 - wear_ui.cpp | 108 --- wear_ui.h | 52 -- 39 files changed, 3383 insertions(+), 3364 deletions(-) delete mode 100644 default_device.cpp delete mode 100644 device.cpp delete mode 100644 device.h create mode 100644 recovery_ui/Android.bp create mode 100644 recovery_ui/default_device.cpp create mode 100644 recovery_ui/device.cpp create mode 100644 recovery_ui/include/recovery_ui/device.h create mode 100644 recovery_ui/include/recovery_ui/screen_ui.h create mode 100644 recovery_ui/include/recovery_ui/stub_ui.h create mode 100644 recovery_ui/include/recovery_ui/ui.h create mode 100644 recovery_ui/include/recovery_ui/vr_ui.h create mode 100644 recovery_ui/include/recovery_ui/wear_ui.h create mode 100644 recovery_ui/screen_ui.cpp create mode 100644 recovery_ui/ui.cpp create mode 100644 recovery_ui/vr_device.cpp create mode 100644 recovery_ui/vr_ui.cpp create mode 100644 recovery_ui/wear_device.cpp create mode 100644 recovery_ui/wear_ui.cpp delete mode 100644 screen_ui.cpp delete mode 100644 screen_ui.h delete mode 100644 stub_ui.h delete mode 100644 ui.cpp delete mode 100644 ui.h delete mode 100644 vr_device.cpp delete mode 100644 vr_ui.cpp delete mode 100644 vr_ui.h delete mode 100644 wear_device.cpp delete mode 100644 wear_ui.cpp delete mode 100644 wear_ui.h diff --git a/Android.bp b/Android.bp index a6986bb2c..7b67f4077 100644 --- a/Android.bp +++ b/Android.bp @@ -26,77 +26,6 @@ cc_defaults { ], } -cc_library { - name: "librecovery_ui", - recovery_available: true, - - defaults: [ - "recovery_defaults", - ], - - srcs: [ - "device.cpp", - "screen_ui.cpp", - "ui.cpp", - "vr_ui.cpp", - "wear_ui.cpp" - ], - - static_libs: [ - "libminui", - "libotautil", - "libfstab", - ], - - shared_libs: [ - "libbase", - "libpng", - "libz", - ], -} - -// Generic device that uses ScreenRecoveryUI. -cc_library_static { - name: "librecovery_ui_default", - recovery_available: true, - - defaults: [ - "recovery_defaults", - ], - - srcs: [ - "default_device.cpp", - ], -} - -// The default wear device that uses WearRecoveryUI. -cc_library_static { - name: "librecovery_ui_wear", - recovery_available: true, - - defaults: [ - "recovery_defaults", - ], - - srcs: [ - "wear_device.cpp", - ], -} - -// The default VR device that uses VrRecoveryUI. -cc_library_static { - name: "librecovery_ui_vr", - recovery_available: true, - - defaults: [ - "recovery_defaults", - ], - - srcs: [ - "vr_device.cpp", - ], -} - cc_library_static { name: "librecovery_fastboot", recovery_available: true, @@ -113,6 +42,7 @@ cc_library_static { "libbootloader_message", "libcutils", "liblog", + "librecovery_ui", ], static_libs: [ @@ -180,6 +110,10 @@ cc_library_static { "roots.cpp", ], + shared_libs: [ + "librecovery_ui", + ], + include_dirs: [ "system/vold", ], diff --git a/adb_install.cpp b/adb_install.cpp index 3f2843fef..1d19fd3a1 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -32,7 +32,7 @@ #include "common.h" #include "fuse_sideload.h" #include "install.h" -#include "ui.h" +#include "recovery_ui/ui.h" int apply_from_adb(bool* wipe_cache) { // Save the usb state to restore after the sideload operation. diff --git a/default_device.cpp b/default_device.cpp deleted file mode 100644 index a9718668d..000000000 --- a/default_device.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2009 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 "device.h" -#include "screen_ui.h" - -Device* make_device() { - return new Device(new ScreenRecoveryUI); -} diff --git a/device.cpp b/device.cpp deleted file mode 100644 index eec1932c2..000000000 --- a/device.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 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 "device.h" - -#include -#include -#include -#include - -#include - -#include "ui.h" - -static std::vector> g_menu_actions{ - { "Reboot system now", Device::REBOOT }, - { "Reboot to bootloader", Device::REBOOT_BOOTLOADER }, - { "Enter fastboot", Device::ENTER_FASTBOOT }, - { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD }, - { "Apply update from SD card", Device::APPLY_SDCARD }, - { "Wipe data/factory reset", Device::WIPE_DATA }, - { "Wipe cache partition", Device::WIPE_CACHE }, - { "Mount /system", Device::MOUNT_SYSTEM }, - { "View recovery logs", Device::VIEW_RECOVERY_LOGS }, - { "Run graphics test", Device::RUN_GRAPHICS_TEST }, - { "Run locale test", Device::RUN_LOCALE_TEST }, - { "Power off", Device::SHUTDOWN }, -}; - -static std::vector g_menu_items; - -static void PopulateMenuItems() { - g_menu_items.clear(); - std::transform(g_menu_actions.cbegin(), g_menu_actions.cend(), std::back_inserter(g_menu_items), - [](const auto& entry) { return entry.first; }); -} - -Device::Device(RecoveryUI* ui) : ui_(ui) { - PopulateMenuItems(); -} - -void Device::RemoveMenuItemForAction(Device::BuiltinAction action) { - g_menu_actions.erase( - std::remove_if(g_menu_actions.begin(), g_menu_actions.end(), - [action](const auto& entry) { return entry.second == action; })); - CHECK(!g_menu_actions.empty()); - - // Re-populate the menu items. - PopulateMenuItems(); -} - -const std::vector& Device::GetMenuItems() { - return g_menu_items; -} - -Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) { - return g_menu_actions[menu_position].second; -} - -int Device::HandleMenuKey(int key, bool visible) { - if (!visible) { - return kNoAction; - } - - switch (key) { - case KEY_DOWN: - case KEY_VOLUMEDOWN: - return kHighlightDown; - - case KEY_UP: - case KEY_VOLUMEUP: - return kHighlightUp; - - case KEY_ENTER: - case KEY_POWER: - return kInvokeItem; - - default: - // If you have all of the above buttons, any other buttons - // are ignored. Otherwise, any button cycles the highlight. - return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; - } -} diff --git a/device.h b/device.h deleted file mode 100644 index 6a8daf83e..000000000 --- a/device.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2011 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 _RECOVERY_DEVICE_H -#define _RECOVERY_DEVICE_H - -#include - -#include -#include -#include - -// Forward declaration to avoid including "ui.h". -class RecoveryUI; - -class Device { - public: - static constexpr const int kNoAction = -1; - static constexpr const int kHighlightUp = -2; - static constexpr const int kHighlightDown = -3; - static constexpr const int kInvokeItem = -4; - - enum BuiltinAction { - NO_ACTION = 0, - REBOOT = 1, - APPLY_SDCARD = 2, - // APPLY_CACHE was 3. - APPLY_ADB_SIDELOAD = 4, - WIPE_DATA = 5, - WIPE_CACHE = 6, - REBOOT_BOOTLOADER = 7, - SHUTDOWN = 8, - VIEW_RECOVERY_LOGS = 9, - MOUNT_SYSTEM = 10, - RUN_GRAPHICS_TEST = 11, - RUN_LOCALE_TEST = 12, - KEY_INTERRUPTED = 13, - ENTER_FASTBOOT = 14, - ENTER_RECOVERY = 15, - }; - - explicit Device(RecoveryUI* ui); - virtual ~Device() {} - - // Returns a raw pointer to the RecoveryUI object. - virtual RecoveryUI* GetUI() { - return ui_.get(); - } - - // 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 - // arguments have been parsed, but before anything else). - virtual void StartRecovery() {}; - - // 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, 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.) - // - // '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) - // - invoke a specific action (a menu position: non-negative value) - 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. - virtual const std::vector& GetMenuItems(); - - // Performs a recovery action selected from the menu. 'menu_position' will be the index of the - // selected menu item, or a non-negative value returned from HandleMenuKey(). The menu will be - // hidden when this is called; implementations can call ui_print() to print information to the - // screen. If the menu position is one of the builtin actions, you can just return the - // corresponding enum value. If it is an action specific to your device, you actually perform it - // here and return NO_ACTION. - virtual BuiltinAction InvokeMenuItem(size_t menu_position); - - // Removes the menu item for the given action. This allows tailoring the menu based on the - // runtime info, such as the availability of /cache or /sdcard. - virtual void RemoveMenuItemForAction(Device::BuiltinAction action); - - // Called before and after we do a wipe data/factory reset operation, either via a reboot from the - // main system with the --wipe_data flag, or when the user boots into recovery image manually and - // selects the option from the menu, to perform whatever device-specific wiping actions as needed. - // Returns true on success; returning false from PreWipeData will prevent the regular wipe, and - // returning false from PostWipeData will cause the wipe to be considered a failure. - virtual bool PreWipeData() { - return true; - } - - virtual bool PostWipeData() { - return true; - } - - private: - // The RecoveryUI object that should be used to display the user interface for this device. - std::unique_ptr ui_; -}; - -// Disable name mangling, as this function will be loaded via dlsym(3). -extern "C" { - -// The device-specific library must define this function (or the default one will be used, if there -// is no device-specific library). It returns the Device object that recovery should use. -Device* make_device(); -} - -#endif // _DEVICE_H diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index 8458c99dd..14f5e4bdc 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -27,8 +27,7 @@ #include #include -#include "device.h" -#include "ui.h" +#include "recovery_ui/ui.h" static const std::vector> kFastbootMenuActions{ { "Reboot system now", Device::REBOOT }, diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h index 53a2adcae..1aa7de66e 100644 --- a/fastboot/fastboot.h +++ b/fastboot/fastboot.h @@ -19,6 +19,6 @@ #include #include -#include "device.h" +#include "recovery_ui/device.h" Device::BuiltinAction StartFastboot(Device* device, const std::vector& args); diff --git a/fuse_sdcard_install.h b/fuse_sdcard_install.h index 5f0d64acf..345aea45b 100644 --- a/fuse_sdcard_install.h +++ b/fuse_sdcard_install.h @@ -16,7 +16,7 @@ #pragma once -#include "device.h" -#include "ui.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" int ApplyFromSdcard(Device* device, bool* wipe_cache, RecoveryUI* ui); diff --git a/install.cpp b/install.cpp index dbc815d47..b7fb78878 100644 --- a/install.cpp +++ b/install.cpp @@ -53,8 +53,8 @@ #include "otautil/thermalutil.h" #include "package.h" #include "private/install.h" +#include "recovery_ui/ui.h" #include "roots.h" -#include "ui.h" #include "verifier.h" using namespace std::chrono_literals; diff --git a/recovery.cpp b/recovery.cpp index 90ca3f0ec..d9c1f22f5 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -52,7 +52,6 @@ #include "adb_install.h" #include "common.h" -#include "device.h" #include "fsck_unshare_blocks.h" #include "fuse_sdcard_install.h" #include "install.h" @@ -62,9 +61,9 @@ #include "otautil/paths.h" #include "otautil/sysutil.h" #include "package.h" +#include "recovery_ui/screen_ui.h" +#include "recovery_ui/ui.h" #include "roots.h" -#include "screen_ui.h" -#include "ui.h" static constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; diff --git a/recovery.h b/recovery.h index 00e22daa6..f050549cc 100644 --- a/recovery.h +++ b/recovery.h @@ -19,6 +19,6 @@ #include #include -#include "device.h" +#include "recovery_ui/device.h" Device::BuiltinAction start_recovery(Device* device, const std::vector& args); diff --git a/recovery_main.cpp b/recovery_main.cpp index 935d69815..2f5a1845b 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -49,16 +49,16 @@ #include #include "common.h" -#include "device.h" #include "fastboot/fastboot.h" #include "logging.h" #include "minadbd/minadbd.h" #include "otautil/paths.h" #include "otautil/sysutil.h" #include "recovery.h" +#include "recovery_ui/device.h" +#include "recovery_ui/stub_ui.h" +#include "recovery_ui/ui.h" #include "roots.h" -#include "stub_ui.h" -#include "ui.h" static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; diff --git a/recovery_ui/Android.bp b/recovery_ui/Android.bp new file mode 100644 index 000000000..ee3149d5e --- /dev/null +++ b/recovery_ui/Android.bp @@ -0,0 +1,91 @@ +// Copyright (C) 2019 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. + +cc_library { + name: "librecovery_ui", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "device.cpp", + "screen_ui.cpp", + "ui.cpp", + "vr_ui.cpp", + "wear_ui.cpp", + ], + + export_include_dirs: ["include"], + + static_libs: [ + "libminui", + "libotautil", + ], + + shared_libs: [ + "libbase", + "libpng", + "libz", + ], +} + +// Generic device that uses ScreenRecoveryUI. +cc_library_static { + name: "librecovery_ui_default", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "default_device.cpp", + ], + + export_include_dirs: ["include"], +} + +// The default wear device that uses WearRecoveryUI. +cc_library_static { + name: "librecovery_ui_wear", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "wear_device.cpp", + ], + + export_include_dirs: ["include"], +} + +// The default VR device that uses VrRecoveryUI. +cc_library_static { + name: "librecovery_ui_vr", + recovery_available: true, + + defaults: [ + "recovery_defaults", + ], + + srcs: [ + "vr_device.cpp", + ], + + export_include_dirs: ["include"], +} diff --git a/recovery_ui/default_device.cpp b/recovery_ui/default_device.cpp new file mode 100644 index 000000000..4db461af6 --- /dev/null +++ b/recovery_ui/default_device.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009 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 "recovery_ui/device.h" +#include "recovery_ui/screen_ui.h" + +Device* make_device() { + return new Device(new ScreenRecoveryUI); +} diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp new file mode 100644 index 000000000..ddb0118db --- /dev/null +++ b/recovery_ui/device.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015 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 "recovery_ui/device.h" + +#include +#include +#include +#include + +#include + +#include "recovery_ui/ui.h" + +static std::vector> g_menu_actions{ + { "Reboot system now", Device::REBOOT }, + { "Reboot to bootloader", Device::REBOOT_BOOTLOADER }, + { "Enter fastboot", Device::ENTER_FASTBOOT }, + { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD }, + { "Apply update from SD card", Device::APPLY_SDCARD }, + { "Wipe data/factory reset", Device::WIPE_DATA }, + { "Wipe cache partition", Device::WIPE_CACHE }, + { "Mount /system", Device::MOUNT_SYSTEM }, + { "View recovery logs", Device::VIEW_RECOVERY_LOGS }, + { "Run graphics test", Device::RUN_GRAPHICS_TEST }, + { "Run locale test", Device::RUN_LOCALE_TEST }, + { "Power off", Device::SHUTDOWN }, +}; + +static std::vector g_menu_items; + +static void PopulateMenuItems() { + g_menu_items.clear(); + std::transform(g_menu_actions.cbegin(), g_menu_actions.cend(), std::back_inserter(g_menu_items), + [](const auto& entry) { return entry.first; }); +} + +Device::Device(RecoveryUI* ui) : ui_(ui) { + PopulateMenuItems(); +} + +void Device::RemoveMenuItemForAction(Device::BuiltinAction action) { + g_menu_actions.erase( + std::remove_if(g_menu_actions.begin(), g_menu_actions.end(), + [action](const auto& entry) { return entry.second == action; })); + CHECK(!g_menu_actions.empty()); + + // Re-populate the menu items. + PopulateMenuItems(); +} + +const std::vector& Device::GetMenuItems() { + return g_menu_items; +} + +Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) { + return g_menu_actions[menu_position].second; +} + +int Device::HandleMenuKey(int key, bool visible) { + if (!visible) { + return kNoAction; + } + + switch (key) { + case KEY_DOWN: + case KEY_VOLUMEDOWN: + return kHighlightDown; + + case KEY_UP: + case KEY_VOLUMEUP: + return kHighlightUp; + + case KEY_ENTER: + case KEY_POWER: + return kInvokeItem; + + default: + // If you have all of the above buttons, any other buttons + // are ignored. Otherwise, any button cycles the highlight. + return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; + } +} diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h new file mode 100644 index 000000000..cfa914e77 --- /dev/null +++ b/recovery_ui/include/recovery_ui/device.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 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 _RECOVERY_DEVICE_H +#define _RECOVERY_DEVICE_H + +#include + +#include +#include +#include + +// Forward declaration to avoid including "ui.h". +class RecoveryUI; + +class Device { + public: + static constexpr const int kNoAction = -1; + static constexpr const int kHighlightUp = -2; + static constexpr const int kHighlightDown = -3; + static constexpr const int kInvokeItem = -4; + + enum BuiltinAction { + NO_ACTION = 0, + REBOOT = 1, + APPLY_SDCARD = 2, + // APPLY_CACHE was 3. + APPLY_ADB_SIDELOAD = 4, + WIPE_DATA = 5, + WIPE_CACHE = 6, + REBOOT_BOOTLOADER = 7, + SHUTDOWN = 8, + VIEW_RECOVERY_LOGS = 9, + MOUNT_SYSTEM = 10, + RUN_GRAPHICS_TEST = 11, + RUN_LOCALE_TEST = 12, + KEY_INTERRUPTED = 13, + ENTER_FASTBOOT = 14, + ENTER_RECOVERY = 15, + }; + + explicit Device(RecoveryUI* ui); + virtual ~Device() {} + + // Returns a raw pointer to the RecoveryUI object. + virtual RecoveryUI* GetUI() { + return ui_.get(); + } + + // 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 + // arguments have been parsed, but before anything else). + virtual void StartRecovery() {} + + // 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, 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.) + // + // '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) + // - invoke a specific action (a menu position: non-negative value) + 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. + virtual const std::vector& GetMenuItems(); + + // Performs a recovery action selected from the menu. 'menu_position' will be the index of the + // selected menu item, or a non-negative value returned from HandleMenuKey(). The menu will be + // hidden when this is called; implementations can call ui_print() to print information to the + // screen. If the menu position is one of the builtin actions, you can just return the + // corresponding enum value. If it is an action specific to your device, you actually perform it + // here and return NO_ACTION. + virtual BuiltinAction InvokeMenuItem(size_t menu_position); + + // Removes the menu item for the given action. This allows tailoring the menu based on the + // runtime info, such as the availability of /cache or /sdcard. + virtual void RemoveMenuItemForAction(Device::BuiltinAction action); + + // Called before and after we do a wipe data/factory reset operation, either via a reboot from the + // main system with the --wipe_data flag, or when the user boots into recovery image manually and + // selects the option from the menu, to perform whatever device-specific wiping actions as needed. + // Returns true on success; returning false from PreWipeData will prevent the regular wipe, and + // returning false from PostWipeData will cause the wipe to be considered a failure. + virtual bool PreWipeData() { + return true; + } + + virtual bool PostWipeData() { + return true; + } + + private: + // The RecoveryUI object that should be used to display the user interface for this device. + std::unique_ptr ui_; +}; + +// Disable name mangling, as this function will be loaded via dlsym(3). +extern "C" { + +// The device-specific library must define this function (or the default one will be used, if there +// is no device-specific library). It returns the Device object that recovery should use. +Device* make_device(); +} + +#endif // _DEVICE_H diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h new file mode 100644 index 000000000..5cda2a2e5 --- /dev/null +++ b/recovery_ui/include/recovery_ui/screen_ui.h @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2011 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 RECOVERY_SCREEN_UI_H +#define RECOVERY_SCREEN_UI_H + +#include + +#include +#include +#include +#include +#include +#include + +#include "ui.h" + +// From minui/minui.h. +class GRSurface; + +enum class UIElement { + HEADER, + MENU, + MENU_SEL_BG, + MENU_SEL_BG_ACTIVE, + MENU_SEL_FG, + LOG, + TEXT_FILL, + INFO +}; + +// Interface to draw the UI elements on the screen. +class DrawInterface { + public: + virtual ~DrawInterface() = default; + + // Sets the color to the predefined value for |element|. + virtual void SetColor(UIElement element) const = 0; + + // Draws a highlight bar at (x, y) - (x + width, y + height). + virtual void DrawHighlightBar(int x, int y, int width, int height) const = 0; + + // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis. + virtual int DrawHorizontalRule(int y) const = 0; + + // Draws a line of text. Returns the offset it should be moving along Y-axis. + virtual int DrawTextLine(int x, int y, const std::string& line, bool bold) const = 0; + + // Draws surface portion (sx, sy, w, h) at screen location (dx, dy). + virtual void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const = 0; + + // Draws rectangle at (x, y) - (x + w, y + h). + virtual void DrawFill(int x, int y, int w, int h) const = 0; + + // Draws given surface (surface->pixel_bytes = 1) as text at (x, y). + virtual void DrawTextIcon(int x, int y, const GRSurface* surface) const = 0; + + // Draws multiple text lines. Returns the offset it should be moving along Y-axis. + virtual int DrawTextLines(int x, int y, const std::vector& lines) const = 0; + + // 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. + virtual int DrawWrappedTextLines(int x, int y, const std::vector& lines) const = 0; +}; + +// Interface for classes that maintain the menu selection and display. +class Menu { + public: + virtual ~Menu() = default; + // Returns the current menu selection. + size_t selection() const; + // Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is + // scrollable. + virtual int Select(int sel) = 0; + // Displays the menu headers on the screen at offset x, y + virtual int DrawHeader(int x, int y) const = 0; + // Iterates over the menu items and displays each of them at offset x, y. + virtual int DrawItems(int x, int y, int screen_width, bool long_press) const = 0; + + protected: + Menu(size_t initial_selection, const DrawInterface& draw_func); + // Current menu selection. + size_t selection_; + // Reference to the class that implements all the draw functions. + const DrawInterface& draw_funcs_; +}; + +// This class uses strings as the menu header and items. +class TextMenu : public Menu { + public: + // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial + // selection to |initial_selection|. + TextMenu(bool scrollable, size_t max_items, size_t max_length, + const std::vector& headers, const std::vector& items, + size_t initial_selection, int char_height, const DrawInterface& draw_funcs); + + int Select(int sel) override; + int DrawHeader(int x, int y) const override; + int DrawItems(int x, int y, int screen_width, bool long_press) const override; + + bool scrollable() const { + return scrollable_; + } + + // Returns count of menu items. + size_t ItemsCount() const; + + // Returns the index of the first menu item. + size_t MenuStart() const; + + // Returns the index of the last menu item + 1. + size_t MenuEnd() const; + + // Menu example: + // info: Android Recovery + // .... + // help messages: Swipe up/down to move + // Swipe left/right to select + // empty line (horizontal rule): + // menu headers: Select file to view + // menu items: /cache/recovery/last_log + // /cache/recovery/last_log.1 + // /cache/recovery/last_log.2 + // ... + const std::vector& text_headers() const; + std::string TextItem(size_t index) const; + + // Checks if the menu items fit vertically on the screen. Returns true and set the + // |cur_selection_str| if the items exceed the screen limit. + bool ItemsOverflow(std::string* cur_selection_str) const; + + private: + // The menu is scrollable to display more items. Used on wear devices who have smaller screens. + const bool scrollable_; + // The max number of menu items to fit vertically on a screen. + const size_t max_display_items_; + // The length of each item to fit horizontally on a screen. + const size_t max_item_length_; + // The menu headers. + std::vector text_headers_; + // The actual menu items trimmed to fit the given properties. + std::vector text_items_; + // The first item to display on the screen. + size_t menu_start_; + + // Height in pixels of each character. + int char_height_; +}; + +// This class uses GRSurface's as the menu header and items. +class GraphicMenu : public Menu { + public: + // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial + // selection to |initial_selection|. |headers| and |items| will be made local copies. + GraphicMenu(const GRSurface* graphic_headers, const std::vector& graphic_items, + size_t initial_selection, const DrawInterface& draw_funcs); + + int Select(int sel) override; + int DrawHeader(int x, int y) const override; + int DrawItems(int x, int y, int screen_width, bool long_press) const override; + + // Checks if all the header and items are valid GRSurface's; and that they can fit in the area + // defined by |max_width| and |max_height|. + static bool Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, + const std::vector& graphic_items); + + // Returns true if |surface| fits on the screen with a vertical offset |y|. + static bool ValidateGraphicSurface(size_t max_width, size_t max_height, int y, + const GRSurface* surface); + + private: + // Menu headers and items in graphic icons. These are the copies owned by the class instance. + std::unique_ptr graphic_headers_; + std::vector> graphic_items_; +}; + +// Implementation of RecoveryUI appropriate for devices with a screen +// (shows an icon + a progress bar, text logging, menu, etc.) +class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { + public: + ScreenRecoveryUI(); + explicit ScreenRecoveryUI(bool scrollable_menu); + ~ScreenRecoveryUI() override; + + bool Init(const std::string& locale) override; + std::string GetLocale() const override; + + // overall recovery state ("background image") + void SetBackground(Icon icon) override; + void SetSystemUpdateText(bool security_update) override; + + // progress indicator + void SetProgressType(ProgressType type) override; + void ShowProgress(float portion, float seconds) override; + void SetProgress(float fraction) override; + + void SetStage(int current, int max) override; + + // text log + void ShowText(bool visible) override; + bool IsTextVisible() override; + bool WasTextEverVisible() override; + + // printing messages + void Print(const char* fmt, ...) override __printflike(2, 3); + void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); + void ShowFile(const std::string& filename) override; + + // menu display + size_t ShowMenu(const std::vector& headers, const std::vector& items, + size_t initial_selection, bool menu_only, + const std::function& key_handler) override; + void SetTitle(const std::vector& lines) override; + + void KeyLongPress(int) override; + + void Redraw(); + + // 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(); + + // Displays the localized wipe data menu. + size_t ShowPromptWipeDataMenu(const std::vector& backup_headers, + const std::vector& backup_items, + const std::function& key_handler) override; + + // Displays the localized wipe data confirmation menu. + size_t ShowPromptWipeDataConfirmationMenu( + const std::vector& backup_headers, const std::vector& backup_items, + const std::function& key_handler) override; + + protected: + static constexpr int kMenuIndent = 4; + + // The margin that we don't want to use for showing texts (e.g. round screen, or screen with + // rounded corners). + const int margin_width_; + const int margin_height_; + + // Number of frames per sec (default: 30) for both parts of the animation. + const int animation_fps_; + + // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. + const float density_; + + virtual bool InitTextParams(); + + virtual bool LoadWipeDataMenuText(); + + // Creates a GraphicMenu with |graphic_header| and |graphic_items|. If the GraphicMenu isn't + // valid or it doesn't fit on the screen; falls back to create a TextMenu instead. If succeeds, + // returns a unique pointer to the created menu; otherwise returns nullptr. + virtual std::unique_ptr

CreateMenu(const GRSurface* graphic_header, + const std::vector& graphic_items, + const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const; + + // Creates a TextMenu with |text_headers| and |text_items|; and sets the menu selection to + // |initial_selection|. + virtual std::unique_ptr CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const; + + // Takes the ownership of |menu| and displays it. + virtual size_t ShowMenu(std::unique_ptr&& menu, bool menu_only, + const std::function& key_handler); + + // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item + // selected. + virtual int SelectMenu(int sel); + + virtual void draw_background_locked(); + virtual void draw_foreground_locked(); + virtual void draw_screen_locked(); + virtual void draw_menu_and_text_buffer_locked(const std::vector& help_message); + virtual void update_screen_locked(); + virtual void update_progress_locked(); + + const GRSurface* GetCurrentFrame() const; + const GRSurface* GetCurrentText() const; + + void ProgressThreadLoop(); + + virtual void ShowFile(FILE*); + virtual void PrintV(const char*, bool, va_list); + void PutChar(char); + void ClearText(); + + void LoadAnimation(); + std::unique_ptr LoadBitmap(const std::string& filename); + std::unique_ptr LoadLocalizedBitmap(const std::string& filename); + + int PixelsFromDp(int dp) const; + virtual int GetAnimationBaseline() const; + virtual int GetProgressBaseline() const; + virtual int GetTextBaseline() const; + + // Returns pixel width of draw buffer. + virtual int ScreenWidth() const; + // Returns pixel height of draw buffer. + virtual int ScreenHeight() const; + + // Implementation of the draw functions in DrawInterface. + void SetColor(UIElement e) const override; + void DrawHighlightBar(int x, int y, int width, int height) const override; + int DrawHorizontalRule(int y) const override; + void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const override; + void DrawFill(int x, int y, int w, int h) const override; + void DrawTextIcon(int x, int y, const GRSurface* surface) const override; + int DrawTextLine(int x, int y, const std::string& line, bool bold) const override; + int DrawTextLines(int x, int y, const std::vector& lines) const override; + int DrawWrappedTextLines(int x, int y, const std::vector& lines) const override; + + // The layout to use. + int layout_; + + // The images that contain localized texts. + std::unique_ptr erasing_text_; + std::unique_ptr error_text_; + std::unique_ptr installing_text_; + std::unique_ptr no_command_text_; + + // Localized text images for the wipe data menu. + std::unique_ptr cancel_wipe_data_text_; + std::unique_ptr factory_data_reset_text_; + std::unique_ptr try_again_text_; + std::unique_ptr wipe_data_confirmation_text_; + std::unique_ptr wipe_data_menu_header_text_; + + std::unique_ptr fastbootd_logo_; + + // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by + // current_frame_, or error_icon_. + Icon current_icon_; + std::unique_ptr error_icon_; + std::vector> intro_frames_; + std::vector> loop_frames_; + size_t current_frame_; + bool intro_done_; + + // progress_bar and stage_marker images. + std::unique_ptr progress_bar_empty_; + std::unique_ptr progress_bar_fill_; + std::unique_ptr stage_marker_empty_; + std::unique_ptr stage_marker_fill_; + + ProgressType progressBarType; + + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; + + // true when both graphics pages are the same (except for the progress bar). + bool pagesIdentical; + + size_t text_cols_, text_rows_; + + // Log text overlay, displayed when a magic key is pressed. + char** text_; + size_t text_col_, text_row_; + + bool show_text; + bool show_text_ever; // has show_text ever been true? + + std::vector title_lines_; + + bool scrollable_menu_; + std::unique_ptr menu_; + + // An alternate text screen, swapped with 'text_' when we're viewing a log file. + char** file_viewer_text_; + + std::thread progress_thread_; + std::atomic progress_thread_stopped_{ false }; + + int stage, max_stage; + + int char_width_; + int char_height_; + + // The locale that's used to show the rendered texts. + std::string locale_; + bool rtl_locale_; + + std::mutex updateMutex; + + private: + void SetLocale(const std::string&); + + // Display the background texts for "erasing", "error", "no_command" and "installing" for the + // selected locale. + void SelectAndShowBackgroundText(const std::vector& locales_entries, size_t sel); +}; + +#endif // RECOVERY_UI_H diff --git a/recovery_ui/include/recovery_ui/stub_ui.h b/recovery_ui/include/recovery_ui/stub_ui.h new file mode 100644 index 000000000..fb1d8c7a6 --- /dev/null +++ b/recovery_ui/include/recovery_ui/stub_ui.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 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 RECOVERY_STUB_UI_H +#define RECOVERY_STUB_UI_H + +#include +#include +#include + +#include "ui.h" + +// Stub implementation of RecoveryUI for devices without screen. +class StubRecoveryUI : public RecoveryUI { + public: + StubRecoveryUI() = default; + + std::string GetLocale() const override { + return ""; + } + void SetBackground(Icon /* icon */) override {} + void SetSystemUpdateText(bool /* security_update */) override {} + + // progress indicator + void SetProgressType(ProgressType /* type */) override {} + void ShowProgress(float /* portion */, float /* seconds */) override {} + void SetProgress(float /* fraction */) override {} + + void SetStage(int /* current */, int /* max */) override {} + + // text log + void ShowText(bool /* visible */) override {} + bool IsTextVisible() override { + return false; + } + bool WasTextEverVisible() override { + return false; + } + + // printing messages + void Print(const char* fmt, ...) override { + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + void PrintOnScreenOnly(const char* /* fmt */, ...) override {} + void ShowFile(const std::string& /* filename */) override {} + + // menu display + size_t ShowMenu(const std::vector& /* headers */, + const std::vector& /* items */, size_t initial_selection, + bool /* menu_only */, + const std::function& /* key_handler */) override { + return initial_selection; + } + + size_t ShowPromptWipeDataMenu(const std::vector& /* backup_headers */, + const std::vector& /* backup_items */, + const std::function& /* key_handle */) override { + return 0; + } + + size_t ShowPromptWipeDataConfirmationMenu( + const std::vector& /* backup_headers */, + const std::vector& /* backup_items */, + const std::function& /* key_handle */) override { + return 0; + } + + void SetTitle(const std::vector& /* lines */) override {} +}; + +#endif // RECOVERY_STUB_UI_H diff --git a/recovery_ui/include/recovery_ui/ui.h b/recovery_ui/include/recovery_ui/ui.h new file mode 100644 index 000000000..d55322cf0 --- /dev/null +++ b/recovery_ui/include/recovery_ui/ui.h @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2011 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 RECOVERY_UI_H +#define RECOVERY_UI_H + +#include // KEY_MAX + +#include +#include +#include +#include +#include +#include +#include + +// Abstract class for controlling the user interface during recovery. +class RecoveryUI { + public: + enum Icon { + NONE, + INSTALLING_UPDATE, + ERASING, + NO_COMMAND, + ERROR, + }; + + enum ProgressType { + EMPTY, + INDETERMINATE, + DETERMINATE, + }; + + enum KeyAction { + ENQUEUE, + TOGGLE, + REBOOT, + IGNORE, + }; + + enum class KeyError : int { + TIMED_OUT = -1, + INTERRUPTED = -2, + }; + + RecoveryUI(); + + virtual ~RecoveryUI(); + + // Initializes the object; called before anything else. UI texts will be initialized according + // to the given locale. Returns true on success. + virtual bool Init(const std::string& locale); + + virtual std::string GetLocale() const = 0; + + // Shows a stage indicator. Called immediately after Init(). + virtual void SetStage(int current, int max) = 0; + + // Sets the overall recovery state ("background image"). + virtual void SetBackground(Icon icon) = 0; + virtual void SetSystemUpdateText(bool security_update) = 0; + + // --- progress indicator --- + virtual void SetProgressType(ProgressType determinate) = 0; + + // Shows a progress bar and define the scope of the next operation: + // portion - fraction of the progress bar the next operation will use + // seconds - expected time interval (progress bar moves at this minimum rate) + virtual void ShowProgress(float portion, float seconds) = 0; + + // Sets progress bar position (0.0 - 1.0 within the scope defined by the last call to + // ShowProgress). + virtual void SetProgress(float fraction) = 0; + + // --- text log --- + + virtual void ShowText(bool visible) = 0; + + virtual bool IsTextVisible() = 0; + + virtual bool WasTextEverVisible() = 0; + + // Writes a message to the on-screen log (shown if the user has toggled on the text display). + // Print() will also dump the message to stdout / log file, while PrintOnScreenOnly() not. + virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; + virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; + + // Shows the contents of the given file. Caller ensures the patition that contains the file has + // been mounted. + virtual void ShowFile(const std::string& filename) = 0; + + // --- key handling --- + + // Waits for a key and return it. May return TIMED_OUT after timeout and + // KeyError::INTERRUPTED on a key interrupt. + virtual int WaitKey(); + + // Wakes up the UI if it is waiting on key input, causing WaitKey to return KeyError::INTERRUPTED. + virtual void InterruptKey(); + + virtual bool IsKeyPressed(int key); + virtual bool IsLongPress(); + + // Returns true if you have the volume up/down and power trio typical of phones and tablets, false + // otherwise. + virtual bool HasThreeButtons(); + + // Returns true if it has a power key. + virtual bool HasPowerKey() const; + + // Returns true if it supports touch inputs. + virtual bool HasTouchScreen() const; + + // Erases any queued-up keys. + virtual void FlushKeys(); + + // Called on each key press, even while operations are in progress. Return value indicates whether + // an immediate operation should be triggered (toggling the display, rebooting the device), or if + // the key should be enqueued for use by the main thread. + virtual KeyAction CheckKey(int key, bool is_long_press); + + // Called when a key is held down long enough to have been a long-press (but before the key is + // released). This means that if the key is eventually registered (released without any other keys + // being pressed in the meantime), CheckKey will be called with 'is_long_press' true. + virtual void KeyLongPress(int key); + + // Normally in recovery there's a key sequence that triggers immediate reboot of the device, + // regardless of what recovery is doing (with the default CheckKey implementation, it's pressing + // the power button 7 times in row). Call this to enable or disable that feature. It is enabled by + // default. + virtual void SetEnableReboot(bool enabled); + + // --- menu display --- + + virtual void SetTitle(const std::vector& lines) = 0; + + // Displays a menu with the given 'headers' and 'items'. The supplied 'key_handler' callback, + // which is typically bound to Device::HandleMenuKey(), should return the expected action for the + // given key code and menu visibility (e.g. to move the cursor or to select an item). Caller sets + // 'menu_only' to true to ensure only a menu item gets selected and returned. Otherwise if + // 'menu_only' is false, ShowMenu() will forward any non-negative value returned from the + // key_handler, which may be beyond the range of menu items. This could be used to trigger a + // device-specific action, even without that being listed in the menu. Caller needs to handle + // such a case accordingly (e.g. by calling Device::InvokeMenuItem() to process the action). + // Returns a non-negative value (the chosen item number or device-specific action code), or + // static_cast(TIMED_OUT) if timed out waiting for input or + // static_cast(ERR_KEY_INTERTUPT) if interrupted, such as by InterruptKey(). + virtual size_t ShowMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection, + bool menu_only, const std::function& key_handler) = 0; + + // Displays the localized wipe data menu with pre-generated graphs. If there's an issue + // with the graphs, falls back to use the backup string headers and items instead. The initial + // selection is the 0th item in the menu, which is expected to reboot the device without a wipe. + virtual size_t ShowPromptWipeDataMenu(const std::vector& backup_headers, + const std::vector& backup_items, + const std::function& key_handler) = 0; + // Displays the localized wipe data confirmation menu with pre-generated images. Falls back to + // the text strings upon failures. The initial selection is the 0th item, which returns to the + // upper level menu. + virtual size_t ShowPromptWipeDataConfirmationMenu( + const std::vector& backup_headers, const std::vector& backup_items, + const std::function& key_handler) = 0; + + // Set whether or not the fastbootd logo is displayed. + void SetEnableFastbootdLogo(bool enable) { + fastbootd_logo_enabled_ = enable; + } + + // Resets the key interrupt status. + void ResetKeyInterruptStatus() { + key_interrupted_ = false; + } + + // Returns the key interrupt status. + bool IsKeyInterrupted() const { + return key_interrupted_; + } + + protected: + void EnqueueKey(int key_code); + + // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of + // the max_brightness). Because the absolute values may vary across devices. These two values can + // be configured via subclassing. Setting brightness_normal_ to 0 to disable screensaver. + unsigned int brightness_normal_; + unsigned int brightness_dimmed_; + std::string brightness_file_; + std::string max_brightness_file_; + + // Whether we should listen for touch inputs (default: false). + bool touch_screen_allowed_; + + bool fastbootd_logo_enabled_; + + private: + enum class ScreensaverState { + DISABLED, + NORMAL, + DIMMED, + OFF, + }; + + // The sensitivity when detecting a swipe. + const int touch_low_threshold_; + const int touch_high_threshold_; + + void OnKeyDetected(int key_code); + 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(); + + bool InitScreensaver(); + void SetScreensaverState(ScreensaverState state); + // Key event input queue + std::mutex key_queue_mutex; + std::condition_variable key_queue_cond; + bool key_interrupted_; + 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 + bool key_long_press; // under key_queue_mutex + int key_down_count; // under key_queue_mutex + bool enable_reboot; // under key_queue_mutex + int rel_sum; + + int consecutive_power_keys; + int last_key; + + bool has_power_key; + bool has_up_key; + bool has_down_key; + bool has_touch_screen; + + // Touch event related variables. See the comments in RecoveryUI::OnInputEvent(). + int touch_slot_; + int touch_X_; + int touch_Y_; + int touch_start_X_; + int touch_start_Y_; + bool touch_finger_down_; + bool touch_swiping_; + bool is_bootreason_recovery_ui_; + + std::thread input_thread_; + std::atomic input_thread_stopped_{ false }; + + ScreensaverState screensaver_state_; + + // The following two contain the absolute values computed from brightness_normal_ and + // brightness_dimmed_ respectively. + unsigned int brightness_normal_value_; + unsigned int brightness_dimmed_value_; +}; + +#endif // RECOVERY_UI_H diff --git a/recovery_ui/include/recovery_ui/vr_ui.h b/recovery_ui/include/recovery_ui/vr_ui.h new file mode 100644 index 000000000..2e8ac5921 --- /dev/null +++ b/recovery_ui/include/recovery_ui/vr_ui.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 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 RECOVERY_VR_UI_H +#define RECOVERY_VR_UI_H + +#include + +#include "screen_ui.h" + +class VrRecoveryUI : public ScreenRecoveryUI { + public: + VrRecoveryUI(); + + protected: + // Pixel offsets to move drawing functions to visible range. + // Can vary per device depending on screen size and lens distortion. + const int stereo_offset_; + + int ScreenWidth() const override; + int ScreenHeight() const override; + + void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const override; + int DrawHorizontalRule(int y) const override; + void DrawHighlightBar(int x, int y, int width, int height) const override; + void DrawFill(int x, int y, int w, int h) const override; + void DrawTextIcon(int x, int y, const GRSurface* surface) const override; + int DrawTextLine(int x, int y, const std::string& line, bool bold) const override; +}; + +#endif // RECOVERY_VR_UI_H diff --git a/recovery_ui/include/recovery_ui/wear_ui.h b/recovery_ui/include/recovery_ui/wear_ui.h new file mode 100644 index 000000000..429af69d2 --- /dev/null +++ b/recovery_ui/include/recovery_ui/wear_ui.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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 RECOVERY_WEAR_UI_H +#define RECOVERY_WEAR_UI_H + +#include +#include + +#include "screen_ui.h" + +class WearRecoveryUI : public ScreenRecoveryUI { + public: + WearRecoveryUI(); + + void SetStage(int current, int max) override; + + protected: + // progress bar vertical position, it's centered horizontally + const int progress_bar_baseline_; + + // Unusable rows when displaying the recovery menu, including the lines for headers (Android + // Recovery, build id and etc) and the bottom lines that may otherwise go out of the screen. + const int menu_unusable_rows_; + + std::unique_ptr CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const override; + + int GetProgressBaseline() const override; + + void update_progress_locked() override; + + private: + void draw_background_locked() override; + void draw_screen_locked() override; +}; + +#endif // RECOVERY_WEAR_UI_H diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp new file mode 100644 index 000000000..870db621c --- /dev/null +++ b/recovery_ui/screen_ui.cpp @@ -0,0 +1,1334 @@ +/* + * Copyright (C) 2011 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 "recovery_ui/screen_ui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "minui/minui.h" +#include "otautil/paths.h" +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +Menu::Menu(size_t initial_selection, const DrawInterface& draw_func) + : selection_(initial_selection), draw_funcs_(draw_func) {} + +size_t Menu::selection() const { + return selection_; +} + +TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length, + const std::vector& headers, const std::vector& items, + size_t initial_selection, int char_height, const DrawInterface& draw_funcs) + : Menu(initial_selection, draw_funcs), + scrollable_(scrollable), + max_display_items_(max_items), + max_item_length_(max_length), + text_headers_(headers), + menu_start_(0), + char_height_(char_height) { + CHECK_LE(max_items, static_cast(std::numeric_limits::max())); + + // It's fine to have more entries than text_rows_ if scrollable menu is supported. + size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_); + for (size_t i = 0; i < items_count; ++i) { + text_items_.emplace_back(items[i].substr(0, max_item_length_)); + } + + CHECK(!text_items_.empty()); +} + +const std::vector& TextMenu::text_headers() const { + return text_headers_; +} + +std::string TextMenu::TextItem(size_t index) const { + CHECK_LT(index, text_items_.size()); + + return text_items_[index]; +} + +size_t TextMenu::MenuStart() const { + return menu_start_; +} + +size_t TextMenu::MenuEnd() const { + return std::min(ItemsCount(), menu_start_ + max_display_items_); +} + +size_t TextMenu::ItemsCount() const { + return text_items_.size(); +} + +bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const { + if (!scrollable_ || ItemsCount() <= max_display_items_) { + return false; + } + + *cur_selection_str = + android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount()); + return true; +} + +// TODO(xunchang) modify the function parameters to button up & down. +int TextMenu::Select(int sel) { + CHECK_LE(ItemsCount(), static_cast(std::numeric_limits::max())); + int count = ItemsCount(); + + // Wraps the selection at boundary if the menu is not scrollable. + if (!scrollable_) { + if (sel < 0) { + selection_ = count - 1; + } else if (sel >= count) { + selection_ = 0; + } else { + selection_ = sel; + } + + return selection_; + } + + if (sel < 0) { + selection_ = 0; + } else if (sel >= count) { + selection_ = count - 1; + } else { + if (static_cast(sel) < menu_start_) { + menu_start_--; + } else if (static_cast(sel) >= MenuEnd()) { + menu_start_++; + } + selection_ = sel; + } + + return selection_; +} + +int TextMenu::DrawHeader(int x, int y) const { + int offset = 0; + + draw_funcs_.SetColor(UIElement::HEADER); + if (!scrollable()) { + offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers()); + } else { + offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers()); + // Show the current menu item number in relation to total number if items don't fit on the + // screen. + std::string cur_selection_str; + if (ItemsOverflow(&cur_selection_str)) { + offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true); + } + } + + return offset; +} + +int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { + int offset = 0; + + draw_funcs_.SetColor(UIElement::MENU); + // Do not draw the horizontal rule for wear devices. + if (!scrollable()) { + offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; + } + for (size_t i = MenuStart(); i < MenuEnd(); ++i) { + bool bold = false; + if (i == selection()) { + // Draw the highlight bar. + draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); + + int bar_height = char_height_ + 4; + draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); + + // Bold white text for the selected item. + draw_funcs_.SetColor(UIElement::MENU_SEL_FG); + bold = true; + } + offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), bold); + + draw_funcs_.SetColor(UIElement::MENU); + } + offset += draw_funcs_.DrawHorizontalRule(y + offset); + + return offset; +} + +GraphicMenu::GraphicMenu(const GRSurface* graphic_headers, + const std::vector& graphic_items, + size_t initial_selection, const DrawInterface& draw_funcs) + : Menu(initial_selection, draw_funcs) { + graphic_headers_ = graphic_headers->Clone(); + graphic_items_.reserve(graphic_items.size()); + for (const auto& item : graphic_items) { + graphic_items_.emplace_back(item->Clone()); + } +} + +int GraphicMenu::Select(int sel) { + CHECK_LE(graphic_items_.size(), static_cast(std::numeric_limits::max())); + int count = graphic_items_.size(); + + // Wraps the selection at boundary if the menu is not scrollable. + if (sel < 0) { + selection_ = count - 1; + } else if (sel >= count) { + selection_ = 0; + } else { + selection_ = sel; + } + + return selection_; +} + +int GraphicMenu::DrawHeader(int x, int y) const { + draw_funcs_.SetColor(UIElement::HEADER); + draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get()); + return graphic_headers_->height; +} + +int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { + int offset = 0; + + draw_funcs_.SetColor(UIElement::MENU); + offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; + + for (size_t i = 0; i < graphic_items_.size(); i++) { + auto& item = graphic_items_[i]; + if (i == selection_) { + draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); + + int bar_height = item->height + 4; + draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); + + // Bold white text for the selected item. + draw_funcs_.SetColor(UIElement::MENU_SEL_FG); + } + draw_funcs_.DrawTextIcon(x, y + offset, item.get()); + offset += item->height; + + draw_funcs_.SetColor(UIElement::MENU); + } + offset += draw_funcs_.DrawHorizontalRule(y + offset); + + return offset; +} + +bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, + const std::vector& graphic_items) { + int offset = 0; + if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) { + return false; + } + offset += graphic_headers->height; + + for (const auto& item : graphic_items) { + if (!ValidateGraphicSurface(max_width, max_height, offset, item)) { + return false; + } + offset += item->height; + } + + return true; +} + +bool GraphicMenu::ValidateGraphicSurface(size_t max_width, size_t max_height, int y, + const GRSurface* surface) { + if (!surface) { + fprintf(stderr, "Graphic surface can not be null\n"); + return false; + } + + if (surface->pixel_bytes != 1 || surface->width != surface->row_bytes) { + fprintf(stderr, "Invalid graphic surface, pixel bytes: %zu, width: %zu row_bytes: %zu\n", + surface->pixel_bytes, surface->width, surface->row_bytes); + return false; + } + + if (surface->width > max_width || surface->height > max_height - y) { + fprintf(stderr, + "Graphic surface doesn't fit into the screen. width: %zu, height: %zu, max_width: %zu," + " max_height: %zu, vertical offset: %d\n", + surface->width, surface->height, max_width, max_height, y); + return false; + } + + return true; +} + +ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {} + +constexpr int kDefaultMarginHeight = 0; +constexpr int kDefaultMarginWidth = 0; +constexpr int kDefaultAnimationFps = 30; + +ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) + : margin_width_( + android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)), + margin_height_( + android::base::GetIntProperty("ro.recovery.ui.margin_height", kDefaultMarginHeight)), + animation_fps_( + android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)), + density_(static_cast(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), + current_icon_(NONE), + current_frame_(0), + intro_done_(false), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + pagesIdentical(false), + text_cols_(0), + text_rows_(0), + text_(nullptr), + text_col_(0), + text_row_(0), + show_text(false), + show_text_ever(false), + scrollable_menu_(scrollable_menu), + file_viewer_text_(nullptr), + stage(-1), + max_stage(-1), + locale_(""), + rtl_locale_(false) {} + +ScreenRecoveryUI::~ScreenRecoveryUI() { + progress_thread_stopped_ = true; + if (progress_thread_.joinable()) { + progress_thread_.join(); + } + // No-op if gr_init() (via Init()) was not called or had failed. + gr_exit(); +} + +const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { + if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) { + return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get(); + } + return error_icon_.get(); +} + +const GRSurface* ScreenRecoveryUI::GetCurrentText() const { + switch (current_icon_) { + case ERASING: + return erasing_text_.get(); + case ERROR: + return error_text_.get(); + case INSTALLING_UPDATE: + return installing_text_.get(); + case NO_COMMAND: + return no_command_text_.get(); + case NONE: + abort(); + } +} + +int ScreenRecoveryUI::PixelsFromDp(int dp) const { + return dp * density_; +} + +// Here's the intended layout: + +// | portrait large landscape large +// ---------+------------------------------------------------- +// gap | +// icon | (200dp) +// gap | 68dp 68dp 56dp 112dp +// text | (14sp) +// gap | 32dp 32dp 26dp 52dp +// progress | (2dp) +// gap | + +// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines +// work), so that's the more useful measurement for calling code. We use even top and bottom gaps. + +enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX }; +enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX }; +static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { + { 32, 68 }, // PORTRAIT + { 32, 68 }, // PORTRAIT_LARGE + { 26, 56 }, // LANDSCAPE + { 52, 112 }, // LANDSCAPE_LARGE +}; + +int ScreenRecoveryUI::GetAnimationBaseline() const { + return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - + gr_get_height(loop_frames_[0].get()); +} + +int ScreenRecoveryUI::GetTextBaseline() const { + return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - + gr_get_height(installing_text_.get()); +} + +int ScreenRecoveryUI::GetProgressBaseline() const { + int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) + + gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) + + gr_get_height(progress_bar_fill_.get()); + int bottom_gap = (ScreenHeight() - elements_sum) / 2; + return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get()); +} + +// Clear the screen and draw the currently selected background icon (if any). +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_background_locked() { + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_clear(); + if (current_icon_ != NONE) { + if (max_stage != -1) { + int stage_height = gr_get_height(stage_marker_empty_.get()); + int stage_width = gr_get_width(stage_marker_empty_.get()); + int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2; + int y = ScreenHeight() - stage_height - margin_height_; + for (int i = 0; i < max_stage; ++i) { + const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_; + DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y); + x += stage_width; + } + } + + const auto& text_surface = GetCurrentText(); + int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2; + int text_y = GetTextBaseline(); + gr_color(255, 255, 255, 255); + DrawTextIcon(text_x, text_y, text_surface); + } +} + +// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be +// called with updateMutex locked. +void ScreenRecoveryUI::draw_foreground_locked() { + if (current_icon_ != NONE) { + const auto& frame = GetCurrentFrame(); + int frame_width = gr_get_width(frame); + int frame_height = gr_get_height(frame); + int frame_x = (ScreenWidth() - frame_width) / 2; + int frame_y = GetAnimationBaseline(); + DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); + } + + if (progressBarType != EMPTY) { + int width = gr_get_width(progress_bar_empty_.get()); + int height = gr_get_height(progress_bar_empty_.get()); + + int progress_x = (ScreenWidth() - width) / 2; + int progress_y = GetProgressBaseline(); + + // Erase behind the progress bar (in case this was a progress-only update) + gr_color(0, 0, 0, 255); + DrawFill(progress_x, progress_y, width, height); + + if (progressBarType == DETERMINATE) { + float p = progressScopeStart + progress * progressScopeSize; + int pos = static_cast(p * width); + + if (rtl_locale_) { + // Fill the progress bar from right to left. + if (pos > 0) { + DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height, + progress_x + width - pos, progress_y); + } + if (pos < width - 1) { + DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y); + } + } else { + // Fill the progress bar from left to right. + if (pos > 0) { + DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y); + } + if (pos < width - 1) { + DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos, + progress_y); + } + } + } + } +} + +void ScreenRecoveryUI::SetColor(UIElement e) const { + switch (e) { + case UIElement::INFO: + gr_color(249, 194, 0, 255); + break; + case UIElement::HEADER: + gr_color(247, 0, 6, 255); + break; + case UIElement::MENU: + case UIElement::MENU_SEL_BG: + gr_color(0, 106, 157, 255); + break; + case UIElement::MENU_SEL_BG_ACTIVE: + gr_color(0, 156, 100, 255); + break; + case UIElement::MENU_SEL_FG: + gr_color(255, 255, 255, 255); + break; + case UIElement::LOG: + gr_color(196, 196, 196, 255); + break; + case UIElement::TEXT_FILL: + gr_color(0, 0, 0, 160); + break; + default: + gr_color(255, 255, 255, 255); + break; + } +} + +void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector& locales_entries, + size_t sel) { + SetLocale(locales_entries[sel]); + std::vector text_name = { "erasing_text", "error_text", "installing_text", + "installing_security_text", "no_command_text" }; + std::unordered_map> surfaces; + for (const auto& name : text_name) { + auto text_image = LoadLocalizedBitmap(name); + if (!text_image) { + Print("Failed to load %s\n", name.c_str()); + return; + } + surfaces.emplace(name, std::move(text_image)); + } + + std::lock_guard lg(updateMutex); + gr_color(0, 0, 0, 255); + gr_clear(); + + int text_y = margin_height_; + int text_x = margin_width_; + int line_spacing = gr_sys_font()->char_height; // Put some extra space between images. + // Write the header and descriptive texts. + SetColor(UIElement::INFO); + std::string header = "Show background text image"; + text_y += DrawTextLine(text_x, text_y, header, true); + std::string locale_selection = android::base::StringPrintf( + "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size()); + // clang-format off + std::vector instruction = { + locale_selection, + "Use volume up/down to switch locales and power to exit." + }; + // clang-format on + text_y += DrawWrappedTextLines(text_x, text_y, instruction); + + // Iterate through the text images and display them in order for the current locale. + for (const auto& p : surfaces) { + text_y += line_spacing; + SetColor(UIElement::LOG); + text_y += DrawTextLine(text_x, text_y, p.first, false); + gr_color(255, 255, 255, 255); + gr_texticon(text_x, text_y, p.second.get()); + text_y += gr_get_height(p.second.get()); + } + // Update the whole screen. + gr_flip(); +} + +void ScreenRecoveryUI::CheckBackgroundTextImages() { + // Load a list of locales embedded in one of the resource files. + 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); + + FlushKeys(); + while (true) { + int key = WaitKey(); + if (key == static_cast(KeyError::INTERRUPTED)) break; + if (key == KEY_POWER || key == KEY_ENTER) { + break; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1; + SelectAndShowBackgroundText(locales_entries, selected); + } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) { + selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1; + SelectAndShowBackgroundText(locales_entries, selected); + } + } + + SetLocale(saved_locale); +} + +int ScreenRecoveryUI::ScreenWidth() const { + return gr_fb_width(); +} + +int ScreenRecoveryUI::ScreenHeight() const { + return gr_fb_height(); +} + +void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const { + gr_blit(surface, sx, sy, w, h, dx, dy); +} + +int ScreenRecoveryUI::DrawHorizontalRule(int y) const { + gr_fill(0, y + 4, ScreenWidth(), y + 6); + return 8; +} + +void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const { + gr_fill(x, y, x + width, y + height); +} + +void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const { + gr_fill(x, y, w, h); +} + +void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { + gr_texticon(x, y, surface); +} + +int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { + gr_text(gr_sys_font(), x, y, line.c_str(), bold); + return char_height_ + 4; +} + +int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector& lines) const { + int offset = 0; + for (const auto& line : lines) { + offset += DrawTextLine(x, y + offset, line, false); + } + return offset; +} + +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) { + next_start += sub.size(); + } else { + // 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; + } else { + sub.resize(last_space); + next_start += last_space + 1; + } + } + offset += DrawTextLine(x, y + offset, sub, false); + } + } + return offset; +} + +void ScreenRecoveryUI::SetTitle(const std::vector& lines) { + title_lines_ = lines; +} + +// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex +// locked. +void ScreenRecoveryUI::draw_screen_locked() { + if (!show_text) { + draw_background_locked(); + draw_foreground_locked(); + return; + } + + gr_color(0, 0, 0, 255); + gr_clear(); + + // clang-format off + static std::vector REGULAR_HELP{ + "Use volume up/down and power.", + }; + static std::vector LONG_PRESS_HELP{ + "Any button cycles highlight.", + "Long-press activates.", + }; + // clang-format on + draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); +} + +// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. +void ScreenRecoveryUI::draw_menu_and_text_buffer_locked( + const std::vector& help_message) { + int y = margin_height_; + + if (fastbootd_logo_ && fastbootd_logo_enabled_) { + // Try to get this centered on screen. + auto width = gr_get_width(fastbootd_logo_.get()); + auto height = gr_get_height(fastbootd_logo_.get()); + auto centered_x = ScreenWidth() / 2 - width / 2; + DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y); + y += height; + } + + if (menu_) { + int x = margin_width_ + kMenuIndent; + + SetColor(UIElement::INFO); + + for (size_t i = 0; i < title_lines_.size(); i++) { + y += DrawTextLine(x, y, title_lines_[i], i == 0); + } + + y += DrawTextLines(x, y, help_message); + + y += menu_->DrawHeader(x, y); + y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress()); + } + + // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or + // we've displayed the entire text buffer. + SetColor(UIElement::LOG); + int row = text_row_; + size_t count = 0; + for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_; + ty -= char_height_, ++count) { + DrawTextLine(margin_width_, ty, text_[row], false); + --row; + if (row < 0) row = text_rows_ - 1; + } +} + +// Redraw everything on the screen and flip the screen (make it visible). +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_screen_locked() { + draw_screen_locked(); + gr_flip(); +} + +// Updates only the progress bar, if possible, otherwise redraws the screen. +// Should only be called with updateMutex locked. +void ScreenRecoveryUI::update_progress_locked() { + if (show_text || !pagesIdentical) { + draw_screen_locked(); // Must redraw the whole screen + pagesIdentical = true; + } else { + draw_foreground_locked(); // Draw only the progress bar and overlays + } + gr_flip(); +} + +void ScreenRecoveryUI::ProgressThreadLoop() { + double interval = 1.0 / animation_fps_; + while (!progress_thread_stopped_) { + double start = now(); + bool redraw = false; + { + std::lock_guard lg(updateMutex); + + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) { + if (!intro_done_) { + if (current_frame_ == intro_frames_.size() - 1) { + intro_done_ = true; + current_frame_ = 0; + } else { + ++current_frame_; + } + } else { + current_frame_ = (current_frame_ + 1) % loop_frames_.size(); + } + + 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(); + } + + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end - start); + if (delay < 0.02) delay = 0.02; + usleep(static_cast(delay * 1000000)); + } +} + +std::unique_ptr ScreenRecoveryUI::LoadBitmap(const std::string& filename) { + GRSurface* surface; + if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) { + LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; + return nullptr; + } + return std::unique_ptr(surface); +} + +std::unique_ptr ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) { + GRSurface* surface; + if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); + result < 0) { + LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; + return nullptr; + } + return std::unique_ptr(surface); +} + +static char** Alloc2d(size_t rows, size_t cols) { + char** result = new char*[rows]; + for (size_t i = 0; i < rows; ++i) { + result[i] = new char[cols]; + memset(result[i], 0, cols); + } + return result; +} + +// Choose the right background string to display during update. +void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { + if (security_update) { + installing_text_ = LoadLocalizedBitmap("installing_security_text"); + } else { + installing_text_ = LoadLocalizedBitmap("installing_text"); + } + Redraw(); +} + +bool ScreenRecoveryUI::InitTextParams() { + // gr_init() would return successfully on font initialization failure. + if (gr_sys_font() == nullptr) { + return false; + } + gr_font_size(gr_sys_font(), &char_width_, &char_height_); + text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_; + text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_; + return true; +} + +bool ScreenRecoveryUI::LoadWipeDataMenuText() { + // Ignores the errors since the member variables will stay as nullptr. + cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text"); + factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text"); + try_again_text_ = LoadLocalizedBitmap("try_again_text"); + wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text"); + wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text"); + return true; +} + +bool ScreenRecoveryUI::Init(const std::string& locale) { + RecoveryUI::Init(locale); + + if (gr_init() == -1) { + return false; + } + + if (!InitTextParams()) { + return false; + } + + // Are we portrait or landscape? + layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; + // Are we the large variant of our base layout? + if (gr_fb_height() > PixelsFromDp(800)) ++layout_; + + text_ = Alloc2d(text_rows_, text_cols_ + 1); + file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); + + text_col_ = text_row_ = 0; + + // Set up the locale info. + SetLocale(locale); + + error_icon_ = LoadBitmap("icon_error"); + + progress_bar_empty_ = LoadBitmap("progress_empty"); + progress_bar_fill_ = LoadBitmap("progress_fill"); + stage_marker_empty_ = LoadBitmap("stage_empty"); + stage_marker_fill_ = LoadBitmap("stage_fill"); + + erasing_text_ = LoadLocalizedBitmap("erasing_text"); + no_command_text_ = LoadLocalizedBitmap("no_command_text"); + error_text_ = LoadLocalizedBitmap("error_text"); + + if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { + fastbootd_logo_ = LoadBitmap("fastbootd"); + } + + // Background text for "installing_update" could be "installing update" or + // "installing security update". It will be set after Init() according to the commands in BCB. + installing_text_.reset(); + + LoadWipeDataMenuText(); + + LoadAnimation(); + + // Keep the progress bar updated, even when the process is otherwise busy. + progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this); + + return true; +} + +std::string ScreenRecoveryUI::GetLocale() const { + return locale_; +} + +void ScreenRecoveryUI::LoadAnimation() { + std::unique_ptr dir(opendir(Paths::Get().resource_dir().c_str()), + closedir); + dirent* de; + std::vector intro_frame_names; + std::vector loop_frame_names; + + while ((de = readdir(dir.get())) != nullptr) { + int value, num_chars; + if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { + intro_frame_names.emplace_back(de->d_name, num_chars); + } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { + loop_frame_names.emplace_back(de->d_name, num_chars); + } + } + + size_t intro_frames = intro_frame_names.size(); + size_t loop_frames = loop_frame_names.size(); + + // It's okay to not have an intro. + if (intro_frames == 0) intro_done_ = true; + // But you must have an animation. + if (loop_frames == 0) abort(); + + std::sort(intro_frame_names.begin(), intro_frame_names.end()); + std::sort(loop_frame_names.begin(), loop_frame_names.end()); + + intro_frames_.clear(); + intro_frames_.reserve(intro_frames); + for (const auto& frame_name : intro_frame_names) { + intro_frames_.emplace_back(LoadBitmap(frame_name)); + } + + loop_frames_.clear(); + loop_frames_.reserve(loop_frames); + for (const auto& frame_name : loop_frame_names) { + loop_frames_.emplace_back(LoadBitmap(frame_name)); + } +} + +void ScreenRecoveryUI::SetBackground(Icon icon) { + std::lock_guard lg(updateMutex); + + current_icon_ = icon; + update_screen_locked(); +} + +void ScreenRecoveryUI::SetProgressType(ProgressType type) { + std::lock_guard lg(updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_progress_locked(); +} + +void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { + std::lock_guard lg(updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_progress_locked(); +} + +void ScreenRecoveryUI::SetProgress(float fraction) { + std::lock_guard lg(updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = gr_get_width(progress_bar_empty_.get()); + float scale = width * progressScopeSize; + if ((int)(progress * scale) != (int)(fraction * scale)) { + progress = fraction; + update_progress_locked(); + } + } +} + +void ScreenRecoveryUI::SetStage(int current, int max) { + std::lock_guard lg(updateMutex); + stage = current; + max_stage = max; +} + +void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { + std::string str; + android::base::StringAppendV(&str, fmt, ap); + + if (copy_to_stdout) { + fputs(str.c_str(), stdout); + } + + 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_) { + text_[text_row_][text_col_] = '\0'; + text_col_ = 0; + text_row_ = (text_row_ + 1) % text_rows_; + } + if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; + } + text_[text_row_][text_col_] = '\0'; + update_screen_locked(); + } +} + +void ScreenRecoveryUI::Print(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, true, ap); + va_end(ap); +} + +void ScreenRecoveryUI::PrintOnScreenOnly(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); +} + +void ScreenRecoveryUI::PutChar(char ch) { + 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_; + } +} + +void ScreenRecoveryUI::ClearText() { + 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); + } +} + +void ScreenRecoveryUI::ShowFile(FILE* fp) { + std::vector offsets; + offsets.push_back(ftello(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + PrintOnScreenOnly("--(%d%% of %d bytes)--", + static_cast(100 * (double(ftello(fp)) / double(sb.st_size))), + static_cast(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == static_cast(KeyError::INTERRUPTED)) return; + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { + show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } + } else { + if (feof(fp)) { + return; + } + offsets.push_back(ftello(fp)); + } + } + ClearText(); + } + + int ch = getc(fp); + if (ch == EOF) { + while (text_row_ < text_rows_ - 1) PutChar('\n'); + show_prompt = true; + } else { + PutChar(ch); + if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { + show_prompt = true; + } + } + } +} + +void ScreenRecoveryUI::ShowFile(const std::string& filename) { + std::unique_ptr fp(fopen(filename.c_str(), "re"), fclose); + if (!fp) { + Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno)); + return; + } + + char** old_text = text_; + size_t old_text_col = text_col_; + size_t old_text_row = text_row_; + + // Swap in the alternate screen and clear it. + text_ = file_viewer_text_; + ClearText(); + + ShowFile(fp.get()); + + text_ = old_text; + text_col_ = old_text_col; + text_row_ = old_text_row; +} + +std::unique_ptr ScreenRecoveryUI::CreateMenu( + const GRSurface* graphic_header, const std::vector& graphic_items, + const std::vector& text_headers, const std::vector& text_items, + size_t initial_selection) const { + // horizontal unusable area: margin width + menu indent + size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent; + // vertical unusable area: margin height + title lines + helper message + high light bar. + // It is safe to reserve more space. + size_t max_height = ScreenHeight() - margin_height_ - char_height_ * (title_lines_.size() + 3); + if (GraphicMenu::Validate(max_width, max_height, graphic_header, graphic_items)) { + return std::make_unique(graphic_header, graphic_items, initial_selection, *this); + } + + fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n"); + + return CreateMenu(text_headers, text_items, initial_selection); +} + +std::unique_ptr ScreenRecoveryUI::CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const { + if (text_rows_ > 0 && text_cols_ > 1) { + return std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers, + text_items, initial_selection, char_height_, *this); + } + + fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_, + text_cols_); + return nullptr; +} + +int ScreenRecoveryUI::SelectMenu(int sel) { + std::lock_guard lg(updateMutex); + if (menu_) { + int old_sel = menu_->selection(); + sel = menu_->Select(sel); + + if (sel != old_sel) { + update_screen_locked(); + } + } + return sel; +} + +size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr&& menu, bool menu_only, + const std::function& key_handler) { + // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. + FlushKeys(); + + // If there is a key interrupt in progress, return KeyError::INTERRUPTED without starting the + // menu. + if (IsKeyInterrupted()) return static_cast(KeyError::INTERRUPTED); + + CHECK(menu != nullptr); + + // Starts and displays the menu + menu_ = std::move(menu); + Redraw(); + + int selected = menu_->selection(); + int chosen_item = -1; + while (chosen_item < 0) { + int key = WaitKey(); + if (key == static_cast(KeyError::INTERRUPTED)) { // WaitKey() was interrupted. + return static_cast(KeyError::INTERRUPTED); + } + if (key == static_cast(KeyError::TIMED_OUT)) { // WaitKey() timed out. + if (WasTextEverVisible()) { + continue; + } else { + LOG(INFO) << "Timed out waiting for key input; rebooting."; + menu_.reset(); + Redraw(); + return static_cast(KeyError::TIMED_OUT); + } + } + + bool visible = IsTextVisible(); + int action = key_handler(key, visible); + if (action < 0) { + switch (action) { + case Device::kHighlightUp: + selected = SelectMenu(--selected); + break; + case Device::kHighlightDown: + selected = SelectMenu(++selected); + break; + case Device::kInvokeItem: + chosen_item = selected; + break; + case Device::kNoAction: + break; + } + } else if (!menu_only) { + chosen_item = action; + } + } + + menu_.reset(); + Redraw(); + + return chosen_item; +} + +size_t ScreenRecoveryUI::ShowMenu(const std::vector& headers, + const std::vector& items, size_t initial_selection, + bool menu_only, + const std::function& key_handler) { + auto menu = CreateMenu(headers, items, initial_selection); + if (menu == nullptr) { + return initial_selection; + } + + return ShowMenu(CreateMenu(headers, items, initial_selection), menu_only, key_handler); +} + +size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector& backup_headers, + const std::vector& backup_items, + const std::function& key_handler) { + auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(), + { try_again_text_.get(), factory_data_reset_text_.get() }, + backup_headers, backup_items, 0); + if (wipe_data_menu == nullptr) { + return 0; + } + + return ShowMenu(std::move(wipe_data_menu), true, key_handler); +} + +size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu( + const std::vector& backup_headers, const std::vector& backup_items, + const std::function& key_handler) { + auto confirmation_menu = + CreateMenu(wipe_data_confirmation_text_.get(), + { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers, + backup_items, 0); + if (confirmation_menu == nullptr) { + return 0; + } + + return ShowMenu(std::move(confirmation_menu), true, key_handler); +} + +bool ScreenRecoveryUI::IsTextVisible() { + std::lock_guard lg(updateMutex); + int visible = show_text; + return visible; +} + +bool ScreenRecoveryUI::WasTextEverVisible() { + std::lock_guard lg(updateMutex); + int ever_visible = show_text_ever; + return ever_visible; +} + +void ScreenRecoveryUI::ShowText(bool visible) { + std::lock_guard lg(updateMutex); + show_text = visible; + if (show_text) show_text_ever = true; + update_screen_locked(); +} + +void ScreenRecoveryUI::Redraw() { + std::lock_guard lg(updateMutex); + update_screen_locked(); +} + +void ScreenRecoveryUI::KeyLongPress(int) { + // Redraw so that if we're in the menu, the highlight + // will change color to indicate a successful long press. + Redraw(); +} + +void ScreenRecoveryUI::SetLocale(const std::string& new_locale) { + locale_ = new_locale; + rtl_locale_ = false; + + if (!new_locale.empty()) { + size_t separator = new_locale.find('-'); + // lang has the language prefix prior to the separator, or full string if none exists. + std::string lang = new_locale.substr(0, separator); + + // A bit cheesy: keep an explicit list of supported RTL languages. + if (lang == "ar" || // Arabic + lang == "fa" || // Persian (Farsi) + lang == "he" || // Hebrew (new language code) + lang == "iw" || // Hebrew (old language code) + lang == "ur") { // Urdu + rtl_locale_ = true; + } + } +} diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp new file mode 100644 index 000000000..b7107ff21 --- /dev/null +++ b/recovery_ui/ui.cpp @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2011 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 "recovery_ui/ui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "minui/minui.h" +#include "otautil/sysutil.h" + +using namespace std::chrono_literals; + +constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120; +constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness"; +constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness"; +constexpr const char* BRIGHTNESS_FILE_SDM = "/sys/class/backlight/panel0-backlight/brightness"; +constexpr const char* MAX_BRIGHTNESS_FILE_SDM = + "/sys/class/backlight/panel0-backlight/max_brightness"; + +constexpr int kDefaultTouchLowThreshold = 50; +constexpr int kDefaultTouchHighThreshold = 90; + +RecoveryUI::RecoveryUI() + : brightness_normal_(50), + brightness_dimmed_(25), + brightness_file_(BRIGHTNESS_FILE), + max_brightness_file_(MAX_BRIGHTNESS_FILE), + touch_screen_allowed_(false), + fastbootd_logo_enabled_(false), + touch_low_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_low_threshold", + kDefaultTouchLowThreshold)), + touch_high_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_high_threshold", + kDefaultTouchHighThreshold)), + key_interrupted_(false), + key_queue_len(0), + key_last_down(-1), + key_long_press(false), + key_down_count(0), + enable_reboot(true), + consecutive_power_keys(0), + last_key(-1), + has_power_key(false), + has_up_key(false), + has_down_key(false), + has_touch_screen(false), + touch_slot_(0), + is_bootreason_recovery_ui_(false), + screensaver_state_(ScreensaverState::DISABLED) { + memset(key_pressed, 0, sizeof(key_pressed)); +} + +RecoveryUI::~RecoveryUI() { + ev_exit(); + input_thread_stopped_ = true; + if (input_thread_.joinable()) { + input_thread_.join(); + } +} + +void RecoveryUI::OnKeyDetected(int key_code) { + if (key_code == KEY_POWER) { + has_power_key = true; + } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { + has_down_key = true; + } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { + has_up_key = true; + } else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) { + has_touch_screen = true; + } +} + +bool RecoveryUI::InitScreensaver() { + // Disabled. + if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) { + return false; + } + if (access(brightness_file_.c_str(), R_OK | W_OK)) { + brightness_file_ = BRIGHTNESS_FILE_SDM; + } + if (access(max_brightness_file_.c_str(), R_OK)) { + max_brightness_file_ = MAX_BRIGHTNESS_FILE_SDM; + } + // Set the initial brightness level based on the max brightness. Note that reading the initial + // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so + // we don't have a good way to query the default value. + std::string content; + if (!android::base::ReadFileToString(max_brightness_file_, &content)) { + PLOG(WARNING) << "Failed to read max brightness"; + return false; + } + + unsigned int max_value; + if (!android::base::ParseUint(android::base::Trim(content), &max_value)) { + LOG(WARNING) << "Failed to parse max brightness: " << content; + return false; + } + + brightness_normal_value_ = max_value * brightness_normal_ / 100.0; + brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0; + if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_), + brightness_file_)) { + PLOG(WARNING) << "Failed to set brightness"; + return false; + } + + LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)"; + screensaver_state_ = ScreensaverState::NORMAL; + return true; +} + +bool RecoveryUI::Init(const std::string& /* locale */) { + ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2), + touch_screen_allowed_); + + ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); + + if (touch_screen_allowed_) { + ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); + + // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of + // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way + // to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or + // with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text + // mode will be turned on automatically on debuggable builds, even without a swipe. + std::string cmdline; + if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) { + is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos; + } else { + // Non-fatal, and won't affect Init() result. + PLOG(WARNING) << "Failed to read /proc/cmdline"; + } + } + + if (!InitScreensaver()) { + LOG(INFO) << "Screensaver disabled"; + } + + // 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; +} + +void RecoveryUI::OnTouchDetected(int dx, int dy) { + enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction; + + // We only consider a valid swipe if: + // - the delta along one axis is below touch_low_threshold_; + // - and the delta along the other axis is beyond touch_high_threshold_. + if (abs(dy) < touch_low_threshold_ && abs(dx) > touch_high_threshold_) { + direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT; + } else if (abs(dx) < touch_low_threshold_ && abs(dy) > touch_high_threshold_) { + direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN; + } else { + LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << touch_low_threshold_ + << ", high: " << touch_high_threshold_ << ")"; + return; + } + + // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui. + if (is_bootreason_recovery_ui_ && !IsTextVisible()) { + ShowText(true); + return; + } + + LOG(DEBUG) << "Swipe direction=" << direction; + switch (direction) { + case SwipeDirection::UP: + ProcessKey(KEY_UP, 1); // press up key + ProcessKey(KEY_UP, 0); // and release it + break; + + case SwipeDirection::DOWN: + ProcessKey(KEY_DOWN, 1); // press down key + ProcessKey(KEY_DOWN, 0); // and release it + break; + + case SwipeDirection::LEFT: + case SwipeDirection::RIGHT: + ProcessKey(KEY_POWER, 1); // press power key + ProcessKey(KEY_POWER, 0); // and release it + break; + }; +} + +int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { + struct input_event ev; + if (ev_get_input(fd, epevents, &ev) == -1) { + return -1; + } + + // Touch inputs handling. + // + // We handle the touch inputs by tracking the position changes between initial contacting and + // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon + // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes. + // + // Per the doc Multi-touch Protocol at below, there are two protocols. + // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt + // + // The main difference between the stateless type A protocol and the stateful type B slot protocol + // lies in the usage of identifiable contacts to reduce the amount of data sent to userspace. The + // slot protocol (i.e. type B) sends ABS_MT_TRACKING_ID with a unique id on initial contact, and + // sends ABS_MT_TRACKING_ID -1 upon lifting the contact. Protocol A doesn't send + // ABS_MT_TRACKING_ID -1 on lifting, but the driver may additionally report BTN_TOUCH event. + // + // For protocol A, we rely on BTN_TOUCH to recognize lifting, while for protocol B we look for + // ABS_MT_TRACKING_ID being -1. + // + // Touch input events will only be available if touch_screen_allowed_ is set. + + if (ev.type == EV_SYN) { + if (touch_screen_allowed_ && ev.code == SYN_REPORT) { + // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the + // contact. + if (touch_finger_down_ && !touch_swiping_) { + touch_start_X_ = touch_X_; + touch_start_Y_ = touch_Y_; + touch_swiping_ = true; + } else if (!touch_finger_down_ && touch_swiping_) { + touch_swiping_ = false; + OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_); + } + } + return 0; + } + + if (ev.type == EV_REL) { + if (ev.code == REL_Y) { + // accumulate the up or down motion reported by + // the trackball. When it exceeds a threshold + // (positive or negative), fake an up/down + // key event. + rel_sum += ev.value; + if (rel_sum > 3) { + ProcessKey(KEY_DOWN, 1); // press down key + ProcessKey(KEY_DOWN, 0); // and release it + rel_sum = 0; + } else if (rel_sum < -3) { + ProcessKey(KEY_UP, 1); // press up key + ProcessKey(KEY_UP, 0); // and release it + rel_sum = 0; + } + } + } else { + rel_sum = 0; + } + + if (touch_screen_allowed_ && ev.type == EV_ABS) { + if (ev.code == ABS_MT_SLOT) { + touch_slot_ = ev.value; + } + // Ignore other fingers. + if (touch_slot_ > 0) return 0; + + switch (ev.code) { + case ABS_MT_POSITION_X: + touch_X_ = ev.value; + touch_finger_down_ = true; + break; + + case ABS_MT_POSITION_Y: + touch_Y_ = ev.value; + touch_finger_down_ = true; + break; + + case ABS_MT_TRACKING_ID: + // Protocol B: -1 marks lifting the contact. + if (ev.value < 0) touch_finger_down_ = false; + break; + } + return 0; + } + + if (ev.type == EV_KEY && ev.code <= KEY_MAX) { + if (touch_screen_allowed_) { + if (ev.code == BTN_TOUCH) { + // A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means + // lifting the contact. + touch_finger_down_ = (ev.value == 1); + } + + // Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger + // additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than + // KEY_POWER and KEY_UP as KEY_DOWN). + if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) { + return 0; + } + } + + ProcessKey(ev.code, ev.value); + } + + return 0; +} + +// 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. +// +// 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; + + { + 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; + } + } + + bool reboot_enabled = enable_reboot; + if (register_key) { + switch (CheckKey(key_code, long_press)) { + case RecoveryUI::IGNORE: + break; + + case RecoveryUI::TOGGLE: + ShowText(!IsTextVisible()); + break; + + case RecoveryUI::REBOOT: + if (reboot_enabled) { + reboot("reboot,"); + while (true) { + pause(); + } + } + break; + + case RecoveryUI::ENQUEUE: + EnqueueKey(key_code); + break; + } + } +} + +void RecoveryUI::TimeKey(int key_code, int count) { + std::this_thread::sleep_for(750ms); // 750 ms == "long" + bool long_press = false; + { + std::lock_guard lg(key_queue_mutex); + if (key_last_down == key_code && key_down_count == count) { + long_press = key_long_press = true; + } + } + if (long_press) KeyLongPress(key_code); +} + +void RecoveryUI::EnqueueKey(int key_code) { + 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; + key_queue_cond.notify_one(); + } +} + +void RecoveryUI::SetScreensaverState(ScreensaverState state) { + switch (state) { + case ScreensaverState::NORMAL: + if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_), + brightness_file_)) { + screensaver_state_ = ScreensaverState::NORMAL; + LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ + << "%)"; + } else { + LOG(ERROR) << "Unable to set brightness to normal"; + } + break; + case ScreensaverState::DIMMED: + if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), + brightness_file_)) { + LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_ + << "%)"; + screensaver_state_ = ScreensaverState::DIMMED; + } else { + LOG(ERROR) << "Unable to set brightness to dim"; + } + break; + case ScreensaverState::OFF: + if (android::base::WriteStringToFile("0", brightness_file_)) { + LOG(INFO) << "Brightness: 0 (off)"; + screensaver_state_ = ScreensaverState::OFF; + } else { + LOG(ERROR) << "Unable to set brightness to off"; + } + break; + default: + LOG(ERROR) << "Invalid screensaver state"; + } +} + +int RecoveryUI::WaitKey() { + std::unique_lock lk(key_queue_mutex); + + // Check for a saved key queue interruption. + if (key_interrupted_) { + SetScreensaverState(ScreensaverState::NORMAL); + return static_cast(KeyError::INTERRUPTED); + } + + // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is plugged in. + do { + bool rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] { + return this->key_queue_len != 0 || key_interrupted_; + }); + if (key_interrupted_) { + SetScreensaverState(ScreensaverState::NORMAL); + return static_cast(KeyError::INTERRUPTED); + } + if (screensaver_state_ != ScreensaverState::DISABLED) { + if (!rc) { + // Must be after a timeout. Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF. + if (screensaver_state_ == ScreensaverState::NORMAL) { + SetScreensaverState(ScreensaverState::DIMMED); + } else if (screensaver_state_ == ScreensaverState::DIMMED) { + SetScreensaverState(ScreensaverState::OFF); + } + } else if (screensaver_state_ != ScreensaverState::NORMAL) { + // Drop the first key if it's changing from OFF to NORMAL. + if (screensaver_state_ == ScreensaverState::OFF) { + if (key_queue_len > 0) { + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + } + } + + // Reset the brightness to normal. + SetScreensaverState(ScreensaverState::NORMAL); + } + } + } while (IsUsbConnected() && key_queue_len == 0); + + int key = static_cast(KeyError::TIMED_OUT); + if (key_queue_len > 0) { + key = key_queue[0]; + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + } + return key; +} + +void RecoveryUI::InterruptKey() { + { + std::lock_guard lg(key_queue_mutex); + key_interrupted_ = true; + } + key_queue_cond.notify_one(); +} + +bool RecoveryUI::IsUsbConnected() { + int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); + if (fd < 0) { + printf("failed to open /sys/class/android_usb/android0/state: %s\n", strerror(errno)); + return 0; + } + + char buf; + // USB is connected if android_usb state is CONNECTED or CONFIGURED. + int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); + if (close(fd) < 0) { + printf("failed to close /sys/class/android_usb/android0/state: %s\n", strerror(errno)); + } + return connected; +} + +bool RecoveryUI::IsKeyPressed(int key) { + std::lock_guard lg(key_queue_mutex); + int pressed = key_pressed[key]; + return pressed; +} + +bool RecoveryUI::IsLongPress() { + std::lock_guard lg(key_queue_mutex); + bool result = key_long_press; + return result; +} + +bool RecoveryUI::HasThreeButtons() { + return has_power_key && has_up_key && has_down_key; +} + +bool RecoveryUI::HasPowerKey() const { + return has_power_key; +} + +bool RecoveryUI::HasTouchScreen() const { + return has_touch_screen; +} + +void RecoveryUI::FlushKeys() { + std::lock_guard lg(key_queue_mutex); + key_queue_len = 0; +} + +RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { + { + 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_)) { + if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) { + return TOGGLE; + } + } else { + // Otherwise long press of any button toggles to the text display, + // and there's no way to toggle back (but that's pretty useless anyway). + if (is_long_press && !IsTextVisible()) { + return TOGGLE; + } + + // Also, for button-limited devices, a long press is translated to KEY_ENTER. + if (is_long_press && IsTextVisible()) { + EnqueueKey(KEY_ENTER); + return IGNORE; + } + } + + // Press power seven times in a row to reboot. + if (key == KEY_POWER) { + bool reboot_enabled = enable_reboot; + + if (reboot_enabled) { + ++consecutive_power_keys; + if (consecutive_power_keys >= 7) { + return REBOOT; + } + } + } else { + consecutive_power_keys = 0; + } + + last_key = key; + return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; +} + +void RecoveryUI::KeyLongPress(int) {} + +void RecoveryUI::SetEnableReboot(bool enabled) { + std::lock_guard lg(key_queue_mutex); + enable_reboot = enabled; +} diff --git a/recovery_ui/vr_device.cpp b/recovery_ui/vr_device.cpp new file mode 100644 index 000000000..fd7613307 --- /dev/null +++ b/recovery_ui/vr_device.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 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 "recovery_ui/device.h" +#include "recovery_ui/vr_ui.h" + +Device* make_device() { + return new Device(new VrRecoveryUI); +} diff --git a/recovery_ui/vr_ui.cpp b/recovery_ui/vr_ui.cpp new file mode 100644 index 000000000..5b9b1b4e5 --- /dev/null +++ b/recovery_ui/vr_ui.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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 "recovery_ui/vr_ui.h" + +#include + +#include "minui/minui.h" + +constexpr int kDefaultStereoOffset = 0; + +VrRecoveryUI::VrRecoveryUI() + : stereo_offset_( + android::base::GetIntProperty("ro.recovery.ui.stereo_offset", kDefaultStereoOffset)) {} + +int VrRecoveryUI::ScreenWidth() const { + return gr_fb_width() / 2; +} + +int VrRecoveryUI::ScreenHeight() const { + return gr_fb_height(); +} + +void VrRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, + int dy) const { + gr_blit(surface, sx, sy, w, h, dx + stereo_offset_, dy); + gr_blit(surface, sx, sy, w, h, dx - stereo_offset_ + ScreenWidth(), dy); +} + +void VrRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { + gr_texticon(x + stereo_offset_, y, surface); + gr_texticon(x - stereo_offset_ + ScreenWidth(), y, surface); +} + +int VrRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { + gr_text(gr_sys_font(), x + stereo_offset_, y, line.c_str(), bold); + gr_text(gr_sys_font(), x - stereo_offset_ + ScreenWidth(), y, line.c_str(), bold); + return char_height_ + 4; +} + +int VrRecoveryUI::DrawHorizontalRule(int y) const { + y += 4; + gr_fill(margin_width_ + stereo_offset_, y, ScreenWidth() - margin_width_ + stereo_offset_, y + 2); + gr_fill(ScreenWidth() + margin_width_ - stereo_offset_, y, + gr_fb_width() - margin_width_ - stereo_offset_, y + 2); + return y + 4; +} + +void VrRecoveryUI::DrawHighlightBar(int /* x */, int y, int /* width */, int height) const { + gr_fill(margin_width_ + stereo_offset_, y, ScreenWidth() - margin_width_ + stereo_offset_, + y + height); + gr_fill(ScreenWidth() + margin_width_ - stereo_offset_, y, + gr_fb_width() - margin_width_ - stereo_offset_, y + height); +} + +void VrRecoveryUI::DrawFill(int x, int y, int w, int h) const { + gr_fill(x + stereo_offset_, y, w, h); + gr_fill(x - stereo_offset_ + ScreenWidth(), y, w, h); +} diff --git a/recovery_ui/wear_device.cpp b/recovery_ui/wear_device.cpp new file mode 100644 index 000000000..bf21bc962 --- /dev/null +++ b/recovery_ui/wear_device.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 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 "recovery_ui/device.h" +#include "recovery_ui/wear_ui.h" + +Device* make_device() { + return new Device(new WearRecoveryUI); +} diff --git a/recovery_ui/wear_ui.cpp b/recovery_ui/wear_ui.cpp new file mode 100644 index 000000000..8d8108f14 --- /dev/null +++ b/recovery_ui/wear_ui.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 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 "recovery_ui/wear_ui.h" + +#include + +#include +#include + +#include +#include +#include + +constexpr int kDefaultProgressBarBaseline = 259; +constexpr int kDefaultMenuUnusableRows = 9; + +WearRecoveryUI::WearRecoveryUI() + : ScreenRecoveryUI(true), + progress_bar_baseline_(android::base::GetIntProperty("ro.recovery.ui.progress_bar_baseline", + kDefaultProgressBarBaseline)), + menu_unusable_rows_(android::base::GetIntProperty("ro.recovery.ui.menu_unusable_rows", + kDefaultMenuUnusableRows)) { + // TODO: menu_unusable_rows_ should be computed based on the lines in draw_screen_locked(). + + touch_screen_allowed_ = true; +} + +int WearRecoveryUI::GetProgressBaseline() const { + return progress_bar_baseline_; +} + +// Draw background frame on the screen. Does not flip pages. +// Should only be called with updateMutex locked. +// TODO merge drawing routines with screen_ui +void WearRecoveryUI::draw_background_locked() { + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (current_icon_ != NONE) { + const auto& frame = GetCurrentFrame(); + int frame_width = gr_get_width(frame); + int frame_height = gr_get_height(frame); + int frame_x = (gr_fb_width() - frame_width) / 2; + int frame_y = (gr_fb_height() - frame_height) / 2; + gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); + + // Draw recovery text on screen above progress bar. + const auto& text = GetCurrentText(); + int text_x = (ScreenWidth() - gr_get_width(text)) / 2; + int text_y = GetProgressBaseline() - gr_get_height(text) - 10; + gr_color(255, 255, 255, 255); + gr_texticon(text_x, text_y, text); + } +} + +void WearRecoveryUI::draw_screen_locked() { + draw_background_locked(); + if (!show_text) { + draw_foreground_locked(); + } else { + SetColor(UIElement::TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + // clang-format off + static std::vector SWIPE_HELP = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + }; + // clang-format on + draw_menu_and_text_buffer_locked(SWIPE_HELP); + } +} + +// TODO merge drawing routines with screen_ui +void WearRecoveryUI::update_progress_locked() { + draw_screen_locked(); + gr_flip(); +} + +void WearRecoveryUI::SetStage(int /* current */, int /* max */) {} + +std::unique_ptr WearRecoveryUI::CreateMenu(const std::vector& text_headers, + const std::vector& text_items, + size_t initial_selection) const { + if (text_rows_ > 0 && text_cols_ > 0) { + return std::make_unique(scrollable_menu_, text_rows_ - menu_unusable_rows_ - 1, + text_cols_ - 1, text_headers, text_items, initial_selection, + char_height_, *this); + } + + return nullptr; +} diff --git a/screen_ui.cpp b/screen_ui.cpp deleted file mode 100644 index 6f2b68b41..000000000 --- a/screen_ui.cpp +++ /dev/null @@ -1,1334 +0,0 @@ -/* - * Copyright (C) 2011 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 "screen_ui.h" - -#include -#include -#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/paths.h" -#include "ui.h" - -// Return the current time as a double (including fractions of a second). -static double now() { - struct timeval tv; - gettimeofday(&tv, nullptr); - return tv.tv_sec + tv.tv_usec / 1000000.0; -} - -Menu::Menu(size_t initial_selection, const DrawInterface& draw_func) - : selection_(initial_selection), draw_funcs_(draw_func) {} - -size_t Menu::selection() const { - return selection_; -} - -TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length, - const std::vector& headers, const std::vector& items, - size_t initial_selection, int char_height, const DrawInterface& draw_funcs) - : Menu(initial_selection, draw_funcs), - scrollable_(scrollable), - max_display_items_(max_items), - max_item_length_(max_length), - text_headers_(headers), - menu_start_(0), - char_height_(char_height) { - CHECK_LE(max_items, static_cast(std::numeric_limits::max())); - - // It's fine to have more entries than text_rows_ if scrollable menu is supported. - size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_); - for (size_t i = 0; i < items_count; ++i) { - text_items_.emplace_back(items[i].substr(0, max_item_length_)); - } - - CHECK(!text_items_.empty()); -} - -const std::vector& TextMenu::text_headers() const { - return text_headers_; -} - -std::string TextMenu::TextItem(size_t index) const { - CHECK_LT(index, text_items_.size()); - - return text_items_[index]; -} - -size_t TextMenu::MenuStart() const { - return menu_start_; -} - -size_t TextMenu::MenuEnd() const { - return std::min(ItemsCount(), menu_start_ + max_display_items_); -} - -size_t TextMenu::ItemsCount() const { - return text_items_.size(); -} - -bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const { - if (!scrollable_ || ItemsCount() <= max_display_items_) { - return false; - } - - *cur_selection_str = - android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount()); - return true; -} - -// TODO(xunchang) modify the function parameters to button up & down. -int TextMenu::Select(int sel) { - CHECK_LE(ItemsCount(), static_cast(std::numeric_limits::max())); - int count = ItemsCount(); - - // Wraps the selection at boundary if the menu is not scrollable. - if (!scrollable_) { - if (sel < 0) { - selection_ = count - 1; - } else if (sel >= count) { - selection_ = 0; - } else { - selection_ = sel; - } - - return selection_; - } - - if (sel < 0) { - selection_ = 0; - } else if (sel >= count) { - selection_ = count - 1; - } else { - if (static_cast(sel) < menu_start_) { - menu_start_--; - } else if (static_cast(sel) >= MenuEnd()) { - menu_start_++; - } - selection_ = sel; - } - - return selection_; -} - -int TextMenu::DrawHeader(int x, int y) const { - int offset = 0; - - draw_funcs_.SetColor(UIElement::HEADER); - if (!scrollable()) { - offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers()); - } else { - offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers()); - // Show the current menu item number in relation to total number if items don't fit on the - // screen. - std::string cur_selection_str; - if (ItemsOverflow(&cur_selection_str)) { - offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true); - } - } - - return offset; -} - -int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { - int offset = 0; - - draw_funcs_.SetColor(UIElement::MENU); - // Do not draw the horizontal rule for wear devices. - if (!scrollable()) { - offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; - } - for (size_t i = MenuStart(); i < MenuEnd(); ++i) { - bool bold = false; - if (i == selection()) { - // Draw the highlight bar. - draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); - - int bar_height = char_height_ + 4; - draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); - - // Bold white text for the selected item. - draw_funcs_.SetColor(UIElement::MENU_SEL_FG); - bold = true; - } - offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), bold); - - draw_funcs_.SetColor(UIElement::MENU); - } - offset += draw_funcs_.DrawHorizontalRule(y + offset); - - return offset; -} - -GraphicMenu::GraphicMenu(const GRSurface* graphic_headers, - const std::vector& graphic_items, - size_t initial_selection, const DrawInterface& draw_funcs) - : Menu(initial_selection, draw_funcs) { - graphic_headers_ = graphic_headers->Clone(); - graphic_items_.reserve(graphic_items.size()); - for (const auto& item : graphic_items) { - graphic_items_.emplace_back(item->Clone()); - } -} - -int GraphicMenu::Select(int sel) { - CHECK_LE(graphic_items_.size(), static_cast(std::numeric_limits::max())); - int count = graphic_items_.size(); - - // Wraps the selection at boundary if the menu is not scrollable. - if (sel < 0) { - selection_ = count - 1; - } else if (sel >= count) { - selection_ = 0; - } else { - selection_ = sel; - } - - return selection_; -} - -int GraphicMenu::DrawHeader(int x, int y) const { - draw_funcs_.SetColor(UIElement::HEADER); - draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get()); - return graphic_headers_->height; -} - -int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { - int offset = 0; - - draw_funcs_.SetColor(UIElement::MENU); - offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; - - for (size_t i = 0; i < graphic_items_.size(); i++) { - auto& item = graphic_items_[i]; - if (i == selection_) { - draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); - - int bar_height = item->height + 4; - draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); - - // Bold white text for the selected item. - draw_funcs_.SetColor(UIElement::MENU_SEL_FG); - } - draw_funcs_.DrawTextIcon(x, y + offset, item.get()); - offset += item->height; - - draw_funcs_.SetColor(UIElement::MENU); - } - offset += draw_funcs_.DrawHorizontalRule(y + offset); - - return offset; -} - -bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, - const std::vector& graphic_items) { - int offset = 0; - if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) { - return false; - } - offset += graphic_headers->height; - - for (const auto& item : graphic_items) { - if (!ValidateGraphicSurface(max_width, max_height, offset, item)) { - return false; - } - offset += item->height; - } - - return true; -} - -bool GraphicMenu::ValidateGraphicSurface(size_t max_width, size_t max_height, int y, - const GRSurface* surface) { - if (!surface) { - fprintf(stderr, "Graphic surface can not be null\n"); - return false; - } - - if (surface->pixel_bytes != 1 || surface->width != surface->row_bytes) { - fprintf(stderr, "Invalid graphic surface, pixel bytes: %zu, width: %zu row_bytes: %zu\n", - surface->pixel_bytes, surface->width, surface->row_bytes); - return false; - } - - if (surface->width > max_width || surface->height > max_height - y) { - fprintf(stderr, - "Graphic surface doesn't fit into the screen. width: %zu, height: %zu, max_width: %zu," - " max_height: %zu, vertical offset: %d\n", - surface->width, surface->height, max_width, max_height, y); - return false; - } - - return true; -} - -ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {} - -constexpr int kDefaultMarginHeight = 0; -constexpr int kDefaultMarginWidth = 0; -constexpr int kDefaultAnimationFps = 30; - -ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) - : margin_width_( - android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)), - margin_height_( - android::base::GetIntProperty("ro.recovery.ui.margin_height", kDefaultMarginHeight)), - animation_fps_( - android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)), - density_(static_cast(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), - current_icon_(NONE), - current_frame_(0), - intro_done_(false), - progressBarType(EMPTY), - progressScopeStart(0), - progressScopeSize(0), - progress(0), - pagesIdentical(false), - text_cols_(0), - text_rows_(0), - text_(nullptr), - text_col_(0), - text_row_(0), - show_text(false), - show_text_ever(false), - scrollable_menu_(scrollable_menu), - file_viewer_text_(nullptr), - stage(-1), - max_stage(-1), - locale_(""), - rtl_locale_(false) {} - -ScreenRecoveryUI::~ScreenRecoveryUI() { - progress_thread_stopped_ = true; - if (progress_thread_.joinable()) { - progress_thread_.join(); - } - // No-op if gr_init() (via Init()) was not called or had failed. - gr_exit(); -} - -const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { - if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) { - return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get(); - } - return error_icon_.get(); -} - -const GRSurface* ScreenRecoveryUI::GetCurrentText() const { - switch (current_icon_) { - case ERASING: - return erasing_text_.get(); - case ERROR: - return error_text_.get(); - case INSTALLING_UPDATE: - return installing_text_.get(); - case NO_COMMAND: - return no_command_text_.get(); - case NONE: - abort(); - } -} - -int ScreenRecoveryUI::PixelsFromDp(int dp) const { - return dp * density_; -} - -// Here's the intended layout: - -// | portrait large landscape large -// ---------+------------------------------------------------- -// gap | -// icon | (200dp) -// gap | 68dp 68dp 56dp 112dp -// text | (14sp) -// gap | 32dp 32dp 26dp 52dp -// progress | (2dp) -// gap | - -// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines -// work), so that's the more useful measurement for calling code. We use even top and bottom gaps. - -enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX }; -enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX }; -static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { - { 32, 68, }, // PORTRAIT - { 32, 68, }, // PORTRAIT_LARGE - { 26, 56, }, // LANDSCAPE - { 52, 112, }, // LANDSCAPE_LARGE -}; - -int ScreenRecoveryUI::GetAnimationBaseline() const { - return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - - gr_get_height(loop_frames_[0].get()); -} - -int ScreenRecoveryUI::GetTextBaseline() const { - return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - - gr_get_height(installing_text_.get()); -} - -int ScreenRecoveryUI::GetProgressBaseline() const { - int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) + - gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) + - gr_get_height(progress_bar_fill_.get()); - int bottom_gap = (ScreenHeight() - elements_sum) / 2; - return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get()); -} - -// Clear the screen and draw the currently selected background icon (if any). -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_background_locked() { - pagesIdentical = false; - gr_color(0, 0, 0, 255); - gr_clear(); - if (current_icon_ != NONE) { - if (max_stage != -1) { - int stage_height = gr_get_height(stage_marker_empty_.get()); - int stage_width = gr_get_width(stage_marker_empty_.get()); - int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2; - int y = ScreenHeight() - stage_height - margin_height_; - for (int i = 0; i < max_stage; ++i) { - const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_; - DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y); - x += stage_width; - } - } - - const auto& text_surface = GetCurrentText(); - int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2; - int text_y = GetTextBaseline(); - gr_color(255, 255, 255, 255); - DrawTextIcon(text_x, text_y, text_surface); - } -} - -// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be -// called with updateMutex locked. -void ScreenRecoveryUI::draw_foreground_locked() { - if (current_icon_ != NONE) { - const auto& frame = GetCurrentFrame(); - int frame_width = gr_get_width(frame); - int frame_height = gr_get_height(frame); - int frame_x = (ScreenWidth() - frame_width) / 2; - int frame_y = GetAnimationBaseline(); - DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); - } - - if (progressBarType != EMPTY) { - int width = gr_get_width(progress_bar_empty_.get()); - int height = gr_get_height(progress_bar_empty_.get()); - - int progress_x = (ScreenWidth() - width) / 2; - int progress_y = GetProgressBaseline(); - - // Erase behind the progress bar (in case this was a progress-only update) - gr_color(0, 0, 0, 255); - DrawFill(progress_x, progress_y, width, height); - - if (progressBarType == DETERMINATE) { - float p = progressScopeStart + progress * progressScopeSize; - int pos = static_cast(p * width); - - if (rtl_locale_) { - // Fill the progress bar from right to left. - if (pos > 0) { - DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height, - progress_x + width - pos, progress_y); - } - if (pos < width - 1) { - DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y); - } - } else { - // Fill the progress bar from left to right. - if (pos > 0) { - DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y); - } - if (pos < width - 1) { - DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos, - progress_y); - } - } - } - } -} - -void ScreenRecoveryUI::SetColor(UIElement e) const { - switch (e) { - case UIElement::INFO: - gr_color(249, 194, 0, 255); - break; - case UIElement::HEADER: - gr_color(247, 0, 6, 255); - break; - case UIElement::MENU: - case UIElement::MENU_SEL_BG: - gr_color(0, 106, 157, 255); - break; - case UIElement::MENU_SEL_BG_ACTIVE: - gr_color(0, 156, 100, 255); - break; - case UIElement::MENU_SEL_FG: - gr_color(255, 255, 255, 255); - break; - case UIElement::LOG: - gr_color(196, 196, 196, 255); - break; - case UIElement::TEXT_FILL: - gr_color(0, 0, 0, 160); - break; - default: - gr_color(255, 255, 255, 255); - break; - } -} - -void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector& locales_entries, - size_t sel) { - SetLocale(locales_entries[sel]); - std::vector text_name = { "erasing_text", "error_text", "installing_text", - "installing_security_text", "no_command_text" }; - std::unordered_map> surfaces; - for (const auto& name : text_name) { - auto text_image = LoadLocalizedBitmap(name); - if (!text_image) { - Print("Failed to load %s\n", name.c_str()); - return; - } - surfaces.emplace(name, std::move(text_image)); - } - - std::lock_guard lg(updateMutex); - gr_color(0, 0, 0, 255); - gr_clear(); - - int text_y = margin_height_; - int text_x = margin_width_; - int line_spacing = gr_sys_font()->char_height; // Put some extra space between images. - // Write the header and descriptive texts. - SetColor(UIElement::INFO); - std::string header = "Show background text image"; - text_y += DrawTextLine(text_x, text_y, header, true); - std::string locale_selection = android::base::StringPrintf( - "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size()); - // clang-format off - std::vector instruction = { - locale_selection, - "Use volume up/down to switch locales and power to exit." - }; - // clang-format on - text_y += DrawWrappedTextLines(text_x, text_y, instruction); - - // Iterate through the text images and display them in order for the current locale. - for (const auto& p : surfaces) { - text_y += line_spacing; - SetColor(UIElement::LOG); - text_y += DrawTextLine(text_x, text_y, p.first, false); - gr_color(255, 255, 255, 255); - gr_texticon(text_x, text_y, p.second.get()); - text_y += gr_get_height(p.second.get()); - } - // Update the whole screen. - gr_flip(); -} - -void ScreenRecoveryUI::CheckBackgroundTextImages() { - // Load a list of locales embedded in one of the resource files. - 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); - - FlushKeys(); - while (true) { - int key = WaitKey(); - if (key == static_cast(KeyError::INTERRUPTED)) break; - if (key == KEY_POWER || key == KEY_ENTER) { - break; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { - selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1; - SelectAndShowBackgroundText(locales_entries, selected); - } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) { - selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1; - SelectAndShowBackgroundText(locales_entries, selected); - } - } - - SetLocale(saved_locale); -} - -int ScreenRecoveryUI::ScreenWidth() const { - return gr_fb_width(); -} - -int ScreenRecoveryUI::ScreenHeight() const { - return gr_fb_height(); -} - -void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, - int dy) const { - gr_blit(surface, sx, sy, w, h, dx, dy); -} - -int ScreenRecoveryUI::DrawHorizontalRule(int y) const { - gr_fill(0, y + 4, ScreenWidth(), y + 6); - return 8; -} - -void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const { - gr_fill(x, y, x + width, y + height); -} - -void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const { - gr_fill(x, y, w, h); -} - -void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { - gr_texticon(x, y, surface); -} - -int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { - gr_text(gr_sys_font(), x, y, line.c_str(), bold); - return char_height_ + 4; -} - -int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector& lines) const { - int offset = 0; - for (const auto& line : lines) { - offset += DrawTextLine(x, y + offset, line, false); - } - return offset; -} - -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) { - next_start += sub.size(); - } else { - // 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; - } else { - sub.resize(last_space); - next_start += last_space + 1; - } - } - offset += DrawTextLine(x, y + offset, sub, false); - } - } - return offset; -} - -void ScreenRecoveryUI::SetTitle(const std::vector& lines) { - title_lines_ = lines; -} - -// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex -// locked. -void ScreenRecoveryUI::draw_screen_locked() { - if (!show_text) { - draw_background_locked(); - draw_foreground_locked(); - return; - } - - gr_color(0, 0, 0, 255); - gr_clear(); - - // clang-format off - static std::vector REGULAR_HELP{ - "Use volume up/down and power.", - }; - static std::vector LONG_PRESS_HELP{ - "Any button cycles highlight.", - "Long-press activates.", - }; - // clang-format on - draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); -} - -// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. -void ScreenRecoveryUI::draw_menu_and_text_buffer_locked( - const std::vector& help_message) { - int y = margin_height_; - - if (fastbootd_logo_ && fastbootd_logo_enabled_) { - // Try to get this centered on screen. - auto width = gr_get_width(fastbootd_logo_.get()); - auto height = gr_get_height(fastbootd_logo_.get()); - auto centered_x = ScreenWidth() / 2 - width / 2; - DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y); - y += height; - } - - if (menu_) { - int x = margin_width_ + kMenuIndent; - - SetColor(UIElement::INFO); - - for (size_t i = 0; i < title_lines_.size(); i++) { - y += DrawTextLine(x, y, title_lines_[i], i == 0); - } - - y += DrawTextLines(x, y, help_message); - - y += menu_->DrawHeader(x, y); - y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress()); - } - - // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or - // we've displayed the entire text buffer. - SetColor(UIElement::LOG); - int row = text_row_; - size_t count = 0; - for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_; - ty -= char_height_, ++count) { - DrawTextLine(margin_width_, ty, text_[row], false); - --row; - if (row < 0) row = text_rows_ - 1; - } -} - -// Redraw everything on the screen and flip the screen (make it visible). -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::update_screen_locked() { - draw_screen_locked(); - gr_flip(); -} - -// Updates only the progress bar, if possible, otherwise redraws the screen. -// Should only be called with updateMutex locked. -void ScreenRecoveryUI::update_progress_locked() { - if (show_text || !pagesIdentical) { - draw_screen_locked(); // Must redraw the whole screen - pagesIdentical = true; - } else { - draw_foreground_locked(); // Draw only the progress bar and overlays - } - gr_flip(); -} - -void ScreenRecoveryUI::ProgressThreadLoop() { - double interval = 1.0 / animation_fps_; - while (!progress_thread_stopped_) { - double start = now(); - bool redraw = false; - { - std::lock_guard lg(updateMutex); - - // update the installation animation, if active - // skip this if we have a text overlay (too expensive to update) - if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) { - if (!intro_done_) { - if (current_frame_ == intro_frames_.size() - 1) { - intro_done_ = true; - current_frame_ = 0; - } else { - ++current_frame_; - } - } else { - current_frame_ = (current_frame_ + 1) % loop_frames_.size(); - } - - 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(); - } - - double end = now(); - // minimum of 20ms delay between frames - double delay = interval - (end - start); - if (delay < 0.02) delay = 0.02; - usleep(static_cast(delay * 1000000)); - } -} - -std::unique_ptr ScreenRecoveryUI::LoadBitmap(const std::string& filename) { - GRSurface* surface; - if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) { - LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; - return nullptr; - } - return std::unique_ptr(surface); -} - -std::unique_ptr ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) { - GRSurface* surface; - if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); - result < 0) { - LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; - return nullptr; - } - return std::unique_ptr(surface); -} - -static char** Alloc2d(size_t rows, size_t cols) { - char** result = new char*[rows]; - for (size_t i = 0; i < rows; ++i) { - result[i] = new char[cols]; - memset(result[i], 0, cols); - } - return result; -} - -// Choose the right background string to display during update. -void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { - if (security_update) { - installing_text_ = LoadLocalizedBitmap("installing_security_text"); - } else { - installing_text_ = LoadLocalizedBitmap("installing_text"); - } - Redraw(); -} - -bool ScreenRecoveryUI::InitTextParams() { - // gr_init() would return successfully on font initialization failure. - if (gr_sys_font() == nullptr) { - return false; - } - gr_font_size(gr_sys_font(), &char_width_, &char_height_); - text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_; - text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_; - return true; -} - -bool ScreenRecoveryUI::LoadWipeDataMenuText() { - // Ignores the errors since the member variables will stay as nullptr. - cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text"); - factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text"); - try_again_text_ = LoadLocalizedBitmap("try_again_text"); - wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text"); - wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text"); - return true; -} - -bool ScreenRecoveryUI::Init(const std::string& locale) { - RecoveryUI::Init(locale); - - if (gr_init() == -1) { - return false; - } - - if (!InitTextParams()) { - return false; - } - - // Are we portrait or landscape? - layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; - // Are we the large variant of our base layout? - if (gr_fb_height() > PixelsFromDp(800)) ++layout_; - - text_ = Alloc2d(text_rows_, text_cols_ + 1); - file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); - - text_col_ = text_row_ = 0; - - // Set up the locale info. - SetLocale(locale); - - error_icon_ = LoadBitmap("icon_error"); - - progress_bar_empty_ = LoadBitmap("progress_empty"); - progress_bar_fill_ = LoadBitmap("progress_fill"); - stage_marker_empty_ = LoadBitmap("stage_empty"); - stage_marker_fill_ = LoadBitmap("stage_fill"); - - erasing_text_ = LoadLocalizedBitmap("erasing_text"); - no_command_text_ = LoadLocalizedBitmap("no_command_text"); - error_text_ = LoadLocalizedBitmap("error_text"); - - if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { - fastbootd_logo_ = LoadBitmap("fastbootd"); - } - - // Background text for "installing_update" could be "installing update" or - // "installing security update". It will be set after Init() according to the commands in BCB. - installing_text_.reset(); - - LoadWipeDataMenuText(); - - LoadAnimation(); - - // Keep the progress bar updated, even when the process is otherwise busy. - progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this); - - return true; -} - -std::string ScreenRecoveryUI::GetLocale() const { - return locale_; -} - -void ScreenRecoveryUI::LoadAnimation() { - std::unique_ptr dir(opendir(Paths::Get().resource_dir().c_str()), - closedir); - dirent* de; - std::vector intro_frame_names; - std::vector loop_frame_names; - - while ((de = readdir(dir.get())) != nullptr) { - int value, num_chars; - if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { - intro_frame_names.emplace_back(de->d_name, num_chars); - } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { - loop_frame_names.emplace_back(de->d_name, num_chars); - } - } - - size_t intro_frames = intro_frame_names.size(); - size_t loop_frames = loop_frame_names.size(); - - // It's okay to not have an intro. - if (intro_frames == 0) intro_done_ = true; - // But you must have an animation. - if (loop_frames == 0) abort(); - - std::sort(intro_frame_names.begin(), intro_frame_names.end()); - std::sort(loop_frame_names.begin(), loop_frame_names.end()); - - intro_frames_.clear(); - intro_frames_.reserve(intro_frames); - for (const auto& frame_name : intro_frame_names) { - intro_frames_.emplace_back(LoadBitmap(frame_name)); - } - - loop_frames_.clear(); - loop_frames_.reserve(loop_frames); - for (const auto& frame_name : loop_frame_names) { - loop_frames_.emplace_back(LoadBitmap(frame_name)); - } -} - -void ScreenRecoveryUI::SetBackground(Icon icon) { - std::lock_guard lg(updateMutex); - - current_icon_ = icon; - update_screen_locked(); -} - -void ScreenRecoveryUI::SetProgressType(ProgressType type) { - std::lock_guard lg(updateMutex); - if (progressBarType != type) { - progressBarType = type; - } - progressScopeStart = 0; - progressScopeSize = 0; - progress = 0; - update_progress_locked(); -} - -void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { - std::lock_guard lg(updateMutex); - progressBarType = DETERMINATE; - progressScopeStart += progressScopeSize; - progressScopeSize = portion; - progressScopeTime = now(); - progressScopeDuration = seconds; - progress = 0; - update_progress_locked(); -} - -void ScreenRecoveryUI::SetProgress(float fraction) { - std::lock_guard lg(updateMutex); - if (fraction < 0.0) fraction = 0.0; - if (fraction > 1.0) fraction = 1.0; - if (progressBarType == DETERMINATE && fraction > progress) { - // Skip updates that aren't visibly different. - int width = gr_get_width(progress_bar_empty_.get()); - float scale = width * progressScopeSize; - if ((int)(progress * scale) != (int)(fraction * scale)) { - progress = fraction; - update_progress_locked(); - } - } -} - -void ScreenRecoveryUI::SetStage(int current, int max) { - std::lock_guard lg(updateMutex); - stage = current; - max_stage = max; -} - -void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { - std::string str; - android::base::StringAppendV(&str, fmt, ap); - - if (copy_to_stdout) { - fputs(str.c_str(), stdout); - } - - 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_) { - text_[text_row_][text_col_] = '\0'; - text_col_ = 0; - text_row_ = (text_row_ + 1) % text_rows_; - } - if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; - } - text_[text_row_][text_col_] = '\0'; - update_screen_locked(); - } -} - -void ScreenRecoveryUI::Print(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, true, ap); - va_end(ap); -} - -void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, false, ap); - va_end(ap); -} - -void ScreenRecoveryUI::PutChar(char ch) { - 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_; - } -} - -void ScreenRecoveryUI::ClearText() { - 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); - } -} - -void ScreenRecoveryUI::ShowFile(FILE* fp) { - std::vector offsets; - offsets.push_back(ftello(fp)); - ClearText(); - - struct stat sb; - fstat(fileno(fp), &sb); - - bool show_prompt = false; - while (true) { - if (show_prompt) { - PrintOnScreenOnly("--(%d%% of %d bytes)--", - static_cast(100 * (double(ftello(fp)) / double(sb.st_size))), - static_cast(sb.st_size)); - Redraw(); - while (show_prompt) { - show_prompt = false; - int key = WaitKey(); - if (key == static_cast(KeyError::INTERRUPTED)) return; - if (key == KEY_POWER || key == KEY_ENTER) { - return; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { - if (offsets.size() <= 1) { - show_prompt = true; - } else { - offsets.pop_back(); - fseek(fp, offsets.back(), SEEK_SET); - } - } else { - if (feof(fp)) { - return; - } - offsets.push_back(ftello(fp)); - } - } - ClearText(); - } - - int ch = getc(fp); - if (ch == EOF) { - while (text_row_ < text_rows_ - 1) PutChar('\n'); - show_prompt = true; - } else { - PutChar(ch); - if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { - show_prompt = true; - } - } - } -} - -void ScreenRecoveryUI::ShowFile(const std::string& filename) { - std::unique_ptr fp(fopen(filename.c_str(), "re"), fclose); - if (!fp) { - Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno)); - return; - } - - char** old_text = text_; - size_t old_text_col = text_col_; - size_t old_text_row = text_row_; - - // Swap in the alternate screen and clear it. - text_ = file_viewer_text_; - ClearText(); - - ShowFile(fp.get()); - - text_ = old_text; - text_col_ = old_text_col; - text_row_ = old_text_row; -} - -std::unique_ptr ScreenRecoveryUI::CreateMenu( - const GRSurface* graphic_header, const std::vector& graphic_items, - const std::vector& text_headers, const std::vector& text_items, - size_t initial_selection) const { - // horizontal unusable area: margin width + menu indent - size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent; - // vertical unusable area: margin height + title lines + helper message + high light bar. - // It is safe to reserve more space. - size_t max_height = ScreenHeight() - margin_height_ - char_height_ * (title_lines_.size() + 3); - if (GraphicMenu::Validate(max_width, max_height, graphic_header, graphic_items)) { - return std::make_unique(graphic_header, graphic_items, initial_selection, *this); - } - - fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n"); - - return CreateMenu(text_headers, text_items, initial_selection); -} - -std::unique_ptr ScreenRecoveryUI::CreateMenu(const std::vector& text_headers, - const std::vector& text_items, - size_t initial_selection) const { - if (text_rows_ > 0 && text_cols_ > 1) { - return std::make_unique(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers, - text_items, initial_selection, char_height_, *this); - } - - fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_, - text_cols_); - return nullptr; -} - -int ScreenRecoveryUI::SelectMenu(int sel) { - std::lock_guard lg(updateMutex); - if (menu_) { - int old_sel = menu_->selection(); - sel = menu_->Select(sel); - - if (sel != old_sel) { - update_screen_locked(); - } - } - return sel; -} - -size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr&& menu, bool menu_only, - const std::function& key_handler) { - // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. - FlushKeys(); - - // If there is a key interrupt in progress, return KeyError::INTERRUPTED without starting the - // menu. - if (IsKeyInterrupted()) return static_cast(KeyError::INTERRUPTED); - - CHECK(menu != nullptr); - - // Starts and displays the menu - menu_ = std::move(menu); - Redraw(); - - int selected = menu_->selection(); - int chosen_item = -1; - while (chosen_item < 0) { - int key = WaitKey(); - if (key == static_cast(KeyError::INTERRUPTED)) { // WaitKey() was interrupted. - return static_cast(KeyError::INTERRUPTED); - } - if (key == static_cast(KeyError::TIMED_OUT)) { // WaitKey() timed out. - if (WasTextEverVisible()) { - continue; - } else { - LOG(INFO) << "Timed out waiting for key input; rebooting."; - menu_.reset(); - Redraw(); - return static_cast(KeyError::TIMED_OUT); - } - } - - bool visible = IsTextVisible(); - int action = key_handler(key, visible); - if (action < 0) { - switch (action) { - case Device::kHighlightUp: - selected = SelectMenu(--selected); - break; - case Device::kHighlightDown: - selected = SelectMenu(++selected); - break; - case Device::kInvokeItem: - chosen_item = selected; - break; - case Device::kNoAction: - break; - } - } else if (!menu_only) { - chosen_item = action; - } - } - - menu_.reset(); - Redraw(); - - return chosen_item; -} - -size_t ScreenRecoveryUI::ShowMenu(const std::vector& headers, - const std::vector& items, size_t initial_selection, - bool menu_only, - const std::function& key_handler) { - auto menu = CreateMenu(headers, items, initial_selection); - if (menu == nullptr) { - return initial_selection; - } - - return ShowMenu(CreateMenu(headers, items, initial_selection), menu_only, key_handler); -} - -size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector& backup_headers, - const std::vector& backup_items, - const std::function& key_handler) { - auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(), - { try_again_text_.get(), factory_data_reset_text_.get() }, - backup_headers, backup_items, 0); - if (wipe_data_menu == nullptr) { - return 0; - } - - return ShowMenu(std::move(wipe_data_menu), true, key_handler); -} - -size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu( - const std::vector& backup_headers, const std::vector& backup_items, - const std::function& key_handler) { - auto confirmation_menu = - CreateMenu(wipe_data_confirmation_text_.get(), - { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers, - backup_items, 0); - if (confirmation_menu == nullptr) { - return 0; - } - - return ShowMenu(std::move(confirmation_menu), true, key_handler); -} - -bool ScreenRecoveryUI::IsTextVisible() { - std::lock_guard lg(updateMutex); - int visible = show_text; - return visible; -} - -bool ScreenRecoveryUI::WasTextEverVisible() { - std::lock_guard lg(updateMutex); - int ever_visible = show_text_ever; - return ever_visible; -} - -void ScreenRecoveryUI::ShowText(bool visible) { - std::lock_guard lg(updateMutex); - show_text = visible; - if (show_text) show_text_ever = true; - update_screen_locked(); -} - -void ScreenRecoveryUI::Redraw() { - std::lock_guard lg(updateMutex); - update_screen_locked(); -} - -void ScreenRecoveryUI::KeyLongPress(int) { - // Redraw so that if we're in the menu, the highlight - // will change color to indicate a successful long press. - Redraw(); -} - -void ScreenRecoveryUI::SetLocale(const std::string& new_locale) { - locale_ = new_locale; - rtl_locale_ = false; - - if (!new_locale.empty()) { - size_t separator = new_locale.find('-'); - // lang has the language prefix prior to the separator, or full string if none exists. - std::string lang = new_locale.substr(0, separator); - - // A bit cheesy: keep an explicit list of supported RTL languages. - if (lang == "ar" || // Arabic - lang == "fa" || // Persian (Farsi) - lang == "he" || // Hebrew (new language code) - lang == "iw" || // Hebrew (old language code) - lang == "ur") { // Urdu - rtl_locale_ = true; - } - } -} diff --git a/screen_ui.h b/screen_ui.h deleted file mode 100644 index 5cda2a2e5..000000000 --- a/screen_ui.h +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Copyright (C) 2011 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 RECOVERY_SCREEN_UI_H -#define RECOVERY_SCREEN_UI_H - -#include - -#include -#include -#include -#include -#include -#include - -#include "ui.h" - -// From minui/minui.h. -class GRSurface; - -enum class UIElement { - HEADER, - MENU, - MENU_SEL_BG, - MENU_SEL_BG_ACTIVE, - MENU_SEL_FG, - LOG, - TEXT_FILL, - INFO -}; - -// Interface to draw the UI elements on the screen. -class DrawInterface { - public: - virtual ~DrawInterface() = default; - - // Sets the color to the predefined value for |element|. - virtual void SetColor(UIElement element) const = 0; - - // Draws a highlight bar at (x, y) - (x + width, y + height). - virtual void DrawHighlightBar(int x, int y, int width, int height) const = 0; - - // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis. - virtual int DrawHorizontalRule(int y) const = 0; - - // Draws a line of text. Returns the offset it should be moving along Y-axis. - virtual int DrawTextLine(int x, int y, const std::string& line, bool bold) const = 0; - - // Draws surface portion (sx, sy, w, h) at screen location (dx, dy). - virtual void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, - int dy) const = 0; - - // Draws rectangle at (x, y) - (x + w, y + h). - virtual void DrawFill(int x, int y, int w, int h) const = 0; - - // Draws given surface (surface->pixel_bytes = 1) as text at (x, y). - virtual void DrawTextIcon(int x, int y, const GRSurface* surface) const = 0; - - // Draws multiple text lines. Returns the offset it should be moving along Y-axis. - virtual int DrawTextLines(int x, int y, const std::vector& lines) const = 0; - - // 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. - virtual int DrawWrappedTextLines(int x, int y, const std::vector& lines) const = 0; -}; - -// Interface for classes that maintain the menu selection and display. -class Menu { - public: - virtual ~Menu() = default; - // Returns the current menu selection. - size_t selection() const; - // Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is - // scrollable. - virtual int Select(int sel) = 0; - // Displays the menu headers on the screen at offset x, y - virtual int DrawHeader(int x, int y) const = 0; - // Iterates over the menu items and displays each of them at offset x, y. - virtual int DrawItems(int x, int y, int screen_width, bool long_press) const = 0; - - protected: - Menu(size_t initial_selection, const DrawInterface& draw_func); - // Current menu selection. - size_t selection_; - // Reference to the class that implements all the draw functions. - const DrawInterface& draw_funcs_; -}; - -// This class uses strings as the menu header and items. -class TextMenu : public Menu { - public: - // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial - // selection to |initial_selection|. - TextMenu(bool scrollable, size_t max_items, size_t max_length, - const std::vector& headers, const std::vector& items, - size_t initial_selection, int char_height, const DrawInterface& draw_funcs); - - int Select(int sel) override; - int DrawHeader(int x, int y) const override; - int DrawItems(int x, int y, int screen_width, bool long_press) const override; - - bool scrollable() const { - return scrollable_; - } - - // Returns count of menu items. - size_t ItemsCount() const; - - // Returns the index of the first menu item. - size_t MenuStart() const; - - // Returns the index of the last menu item + 1. - size_t MenuEnd() const; - - // Menu example: - // info: Android Recovery - // .... - // help messages: Swipe up/down to move - // Swipe left/right to select - // empty line (horizontal rule): - // menu headers: Select file to view - // menu items: /cache/recovery/last_log - // /cache/recovery/last_log.1 - // /cache/recovery/last_log.2 - // ... - const std::vector& text_headers() const; - std::string TextItem(size_t index) const; - - // Checks if the menu items fit vertically on the screen. Returns true and set the - // |cur_selection_str| if the items exceed the screen limit. - bool ItemsOverflow(std::string* cur_selection_str) const; - - private: - // The menu is scrollable to display more items. Used on wear devices who have smaller screens. - const bool scrollable_; - // The max number of menu items to fit vertically on a screen. - const size_t max_display_items_; - // The length of each item to fit horizontally on a screen. - const size_t max_item_length_; - // The menu headers. - std::vector text_headers_; - // The actual menu items trimmed to fit the given properties. - std::vector text_items_; - // The first item to display on the screen. - size_t menu_start_; - - // Height in pixels of each character. - int char_height_; -}; - -// This class uses GRSurface's as the menu header and items. -class GraphicMenu : public Menu { - public: - // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial - // selection to |initial_selection|. |headers| and |items| will be made local copies. - GraphicMenu(const GRSurface* graphic_headers, const std::vector& graphic_items, - size_t initial_selection, const DrawInterface& draw_funcs); - - int Select(int sel) override; - int DrawHeader(int x, int y) const override; - int DrawItems(int x, int y, int screen_width, bool long_press) const override; - - // Checks if all the header and items are valid GRSurface's; and that they can fit in the area - // defined by |max_width| and |max_height|. - static bool Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, - const std::vector& graphic_items); - - // Returns true if |surface| fits on the screen with a vertical offset |y|. - static bool ValidateGraphicSurface(size_t max_width, size_t max_height, int y, - const GRSurface* surface); - - private: - // Menu headers and items in graphic icons. These are the copies owned by the class instance. - std::unique_ptr graphic_headers_; - std::vector> graphic_items_; -}; - -// Implementation of RecoveryUI appropriate for devices with a screen -// (shows an icon + a progress bar, text logging, menu, etc.) -class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { - public: - ScreenRecoveryUI(); - explicit ScreenRecoveryUI(bool scrollable_menu); - ~ScreenRecoveryUI() override; - - bool Init(const std::string& locale) override; - std::string GetLocale() const override; - - // overall recovery state ("background image") - void SetBackground(Icon icon) override; - void SetSystemUpdateText(bool security_update) override; - - // progress indicator - void SetProgressType(ProgressType type) override; - void ShowProgress(float portion, float seconds) override; - void SetProgress(float fraction) override; - - void SetStage(int current, int max) override; - - // text log - void ShowText(bool visible) override; - bool IsTextVisible() override; - bool WasTextEverVisible() override; - - // printing messages - void Print(const char* fmt, ...) override __printflike(2, 3); - void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); - void ShowFile(const std::string& filename) override; - - // menu display - size_t ShowMenu(const std::vector& headers, const std::vector& items, - size_t initial_selection, bool menu_only, - const std::function& key_handler) override; - void SetTitle(const std::vector& lines) override; - - void KeyLongPress(int) override; - - void Redraw(); - - // 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(); - - // Displays the localized wipe data menu. - size_t ShowPromptWipeDataMenu(const std::vector& backup_headers, - const std::vector& backup_items, - const std::function& key_handler) override; - - // Displays the localized wipe data confirmation menu. - size_t ShowPromptWipeDataConfirmationMenu( - const std::vector& backup_headers, const std::vector& backup_items, - const std::function& key_handler) override; - - protected: - static constexpr int kMenuIndent = 4; - - // The margin that we don't want to use for showing texts (e.g. round screen, or screen with - // rounded corners). - const int margin_width_; - const int margin_height_; - - // Number of frames per sec (default: 30) for both parts of the animation. - const int animation_fps_; - - // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. - const float density_; - - virtual bool InitTextParams(); - - virtual bool LoadWipeDataMenuText(); - - // Creates a GraphicMenu with |graphic_header| and |graphic_items|. If the GraphicMenu isn't - // valid or it doesn't fit on the screen; falls back to create a TextMenu instead. If succeeds, - // returns a unique pointer to the created menu; otherwise returns nullptr. - virtual std::unique_ptr CreateMenu(const GRSurface* graphic_header, - const std::vector& graphic_items, - const std::vector& text_headers, - const std::vector& text_items, - size_t initial_selection) const; - - // Creates a TextMenu with |text_headers| and |text_items|; and sets the menu selection to - // |initial_selection|. - virtual std::unique_ptr CreateMenu(const std::vector& text_headers, - const std::vector& text_items, - size_t initial_selection) const; - - // Takes the ownership of |menu| and displays it. - virtual size_t ShowMenu(std::unique_ptr&& menu, bool menu_only, - const std::function& key_handler); - - // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item - // selected. - virtual int SelectMenu(int sel); - - virtual void draw_background_locked(); - virtual void draw_foreground_locked(); - virtual void draw_screen_locked(); - virtual void draw_menu_and_text_buffer_locked(const std::vector& help_message); - virtual void update_screen_locked(); - virtual void update_progress_locked(); - - const GRSurface* GetCurrentFrame() const; - const GRSurface* GetCurrentText() const; - - void ProgressThreadLoop(); - - virtual void ShowFile(FILE*); - virtual void PrintV(const char*, bool, va_list); - void PutChar(char); - void ClearText(); - - void LoadAnimation(); - std::unique_ptr LoadBitmap(const std::string& filename); - std::unique_ptr LoadLocalizedBitmap(const std::string& filename); - - int PixelsFromDp(int dp) const; - virtual int GetAnimationBaseline() const; - virtual int GetProgressBaseline() const; - virtual int GetTextBaseline() const; - - // Returns pixel width of draw buffer. - virtual int ScreenWidth() const; - // Returns pixel height of draw buffer. - virtual int ScreenHeight() const; - - // Implementation of the draw functions in DrawInterface. - void SetColor(UIElement e) const override; - void DrawHighlightBar(int x, int y, int width, int height) const override; - int DrawHorizontalRule(int y) const override; - void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, - int dy) const override; - void DrawFill(int x, int y, int w, int h) const override; - void DrawTextIcon(int x, int y, const GRSurface* surface) const override; - int DrawTextLine(int x, int y, const std::string& line, bool bold) const override; - int DrawTextLines(int x, int y, const std::vector& lines) const override; - int DrawWrappedTextLines(int x, int y, const std::vector& lines) const override; - - // The layout to use. - int layout_; - - // The images that contain localized texts. - std::unique_ptr erasing_text_; - std::unique_ptr error_text_; - std::unique_ptr installing_text_; - std::unique_ptr no_command_text_; - - // Localized text images for the wipe data menu. - std::unique_ptr cancel_wipe_data_text_; - std::unique_ptr factory_data_reset_text_; - std::unique_ptr try_again_text_; - std::unique_ptr wipe_data_confirmation_text_; - std::unique_ptr wipe_data_menu_header_text_; - - std::unique_ptr fastbootd_logo_; - - // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by - // current_frame_, or error_icon_. - Icon current_icon_; - std::unique_ptr error_icon_; - std::vector> intro_frames_; - std::vector> loop_frames_; - size_t current_frame_; - bool intro_done_; - - // progress_bar and stage_marker images. - std::unique_ptr progress_bar_empty_; - std::unique_ptr progress_bar_fill_; - std::unique_ptr stage_marker_empty_; - std::unique_ptr stage_marker_fill_; - - ProgressType progressBarType; - - float progressScopeStart, progressScopeSize, progress; - double progressScopeTime, progressScopeDuration; - - // true when both graphics pages are the same (except for the progress bar). - bool pagesIdentical; - - size_t text_cols_, text_rows_; - - // Log text overlay, displayed when a magic key is pressed. - char** text_; - size_t text_col_, text_row_; - - bool show_text; - bool show_text_ever; // has show_text ever been true? - - std::vector title_lines_; - - bool scrollable_menu_; - std::unique_ptr menu_; - - // An alternate text screen, swapped with 'text_' when we're viewing a log file. - char** file_viewer_text_; - - std::thread progress_thread_; - std::atomic progress_thread_stopped_{ false }; - - int stage, max_stage; - - int char_width_; - int char_height_; - - // The locale that's used to show the rendered texts. - std::string locale_; - bool rtl_locale_; - - std::mutex updateMutex; - - private: - void SetLocale(const std::string&); - - // Display the background texts for "erasing", "error", "no_command" and "installing" for the - // selected locale. - void SelectAndShowBackgroundText(const std::vector& locales_entries, size_t sel); -}; - -#endif // RECOVERY_UI_H diff --git a/stub_ui.h b/stub_ui.h deleted file mode 100644 index fb1d8c7a6..000000000 --- a/stub_ui.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2016 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 RECOVERY_STUB_UI_H -#define RECOVERY_STUB_UI_H - -#include -#include -#include - -#include "ui.h" - -// Stub implementation of RecoveryUI for devices without screen. -class StubRecoveryUI : public RecoveryUI { - public: - StubRecoveryUI() = default; - - std::string GetLocale() const override { - return ""; - } - void SetBackground(Icon /* icon */) override {} - void SetSystemUpdateText(bool /* security_update */) override {} - - // progress indicator - void SetProgressType(ProgressType /* type */) override {} - void ShowProgress(float /* portion */, float /* seconds */) override {} - void SetProgress(float /* fraction */) override {} - - void SetStage(int /* current */, int /* max */) override {} - - // text log - void ShowText(bool /* visible */) override {} - bool IsTextVisible() override { - return false; - } - bool WasTextEverVisible() override { - return false; - } - - // printing messages - void Print(const char* fmt, ...) override { - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - } - void PrintOnScreenOnly(const char* /* fmt */, ...) override {} - void ShowFile(const std::string& /* filename */) override {} - - // menu display - size_t ShowMenu(const std::vector& /* headers */, - const std::vector& /* items */, size_t initial_selection, - bool /* menu_only */, - const std::function& /* key_handler */) override { - return initial_selection; - } - - size_t ShowPromptWipeDataMenu(const std::vector& /* backup_headers */, - const std::vector& /* backup_items */, - const std::function& /* key_handle */) override { - return 0; - } - - size_t ShowPromptWipeDataConfirmationMenu( - const std::vector& /* backup_headers */, - const std::vector& /* backup_items */, - const std::function& /* key_handle */) override { - return 0; - } - - void SetTitle(const std::vector& /* lines */) override {} -}; - -#endif // RECOVERY_STUB_UI_H diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp index 647c7b2d3..883dfbde2 100644 --- a/tests/unit/screen_ui_test.cpp +++ b/tests/unit/screen_ui_test.cpp @@ -30,11 +30,11 @@ #include #include "common/test_constants.h" -#include "device.h" #include "minui/minui.h" #include "otautil/paths.h" #include "private/resources.h" -#include "screen_ui.h" +#include "recovery_ui/device.h" +#include "recovery_ui/screen_ui.h" static const std::vector HEADERS{ "header" }; static const std::vector ITEMS{ "item1", "item2", "item3", "item4", "1234567890" }; diff --git a/ui.cpp b/ui.cpp deleted file mode 100644 index c12a11b36..000000000 --- a/ui.cpp +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright (C) 2011 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 "ui.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "minui/minui.h" -#include "otautil/sysutil.h" -#include "roots.h" - -using namespace std::chrono_literals; - -constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120; -constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness"; -constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness"; -constexpr const char* BRIGHTNESS_FILE_SDM = "/sys/class/backlight/panel0-backlight/brightness"; -constexpr const char* MAX_BRIGHTNESS_FILE_SDM = - "/sys/class/backlight/panel0-backlight/max_brightness"; - -constexpr int kDefaultTouchLowThreshold = 50; -constexpr int kDefaultTouchHighThreshold = 90; - -RecoveryUI::RecoveryUI() - : brightness_normal_(50), - brightness_dimmed_(25), - brightness_file_(BRIGHTNESS_FILE), - max_brightness_file_(MAX_BRIGHTNESS_FILE), - touch_screen_allowed_(false), - fastbootd_logo_enabled_(false), - touch_low_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_low_threshold", - kDefaultTouchLowThreshold)), - touch_high_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_high_threshold", - kDefaultTouchHighThreshold)), - key_interrupted_(false), - key_queue_len(0), - key_last_down(-1), - key_long_press(false), - key_down_count(0), - enable_reboot(true), - consecutive_power_keys(0), - last_key(-1), - has_power_key(false), - has_up_key(false), - has_down_key(false), - has_touch_screen(false), - touch_slot_(0), - is_bootreason_recovery_ui_(false), - screensaver_state_(ScreensaverState::DISABLED) { - memset(key_pressed, 0, sizeof(key_pressed)); -} - -RecoveryUI::~RecoveryUI() { - ev_exit(); - input_thread_stopped_ = true; - if (input_thread_.joinable()) { - input_thread_.join(); - } -} - -void RecoveryUI::OnKeyDetected(int key_code) { - if (key_code == KEY_POWER) { - has_power_key = true; - } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { - has_down_key = true; - } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { - has_up_key = true; - } else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) { - has_touch_screen = true; - } -} - -bool RecoveryUI::InitScreensaver() { - // Disabled. - if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) { - return false; - } - if (access(brightness_file_.c_str(), R_OK | W_OK)) { - brightness_file_ = BRIGHTNESS_FILE_SDM; - } - if (access(max_brightness_file_.c_str(), R_OK)) { - max_brightness_file_ = MAX_BRIGHTNESS_FILE_SDM; - } - // Set the initial brightness level based on the max brightness. Note that reading the initial - // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so - // we don't have a good way to query the default value. - std::string content; - if (!android::base::ReadFileToString(max_brightness_file_, &content)) { - PLOG(WARNING) << "Failed to read max brightness"; - return false; - } - - unsigned int max_value; - if (!android::base::ParseUint(android::base::Trim(content), &max_value)) { - LOG(WARNING) << "Failed to parse max brightness: " << content; - return false; - } - - brightness_normal_value_ = max_value * brightness_normal_ / 100.0; - brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0; - if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_), - brightness_file_)) { - PLOG(WARNING) << "Failed to set brightness"; - return false; - } - - LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)"; - screensaver_state_ = ScreensaverState::NORMAL; - return true; -} - -bool RecoveryUI::Init(const std::string& /* locale */) { - ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2), - touch_screen_allowed_); - - ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); - - if (touch_screen_allowed_) { - ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); - - // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of - // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way - // to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or - // with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text - // mode will be turned on automatically on debuggable builds, even without a swipe. - std::string cmdline; - if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) { - is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos; - } else { - // Non-fatal, and won't affect Init() result. - PLOG(WARNING) << "Failed to read /proc/cmdline"; - } - } - - if (!InitScreensaver()) { - LOG(INFO) << "Screensaver disabled"; - } - - // 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; -} - -void RecoveryUI::OnTouchDetected(int dx, int dy) { - enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction; - - // We only consider a valid swipe if: - // - the delta along one axis is below touch_low_threshold_; - // - and the delta along the other axis is beyond touch_high_threshold_. - if (abs(dy) < touch_low_threshold_ && abs(dx) > touch_high_threshold_) { - direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT; - } else if (abs(dx) < touch_low_threshold_ && abs(dy) > touch_high_threshold_) { - direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN; - } else { - LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << touch_low_threshold_ - << ", high: " << touch_high_threshold_ << ")"; - return; - } - - // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui. - if (is_bootreason_recovery_ui_ && !IsTextVisible()) { - ShowText(true); - return; - } - - LOG(DEBUG) << "Swipe direction=" << direction; - switch (direction) { - case SwipeDirection::UP: - ProcessKey(KEY_UP, 1); // press up key - ProcessKey(KEY_UP, 0); // and release it - break; - - case SwipeDirection::DOWN: - ProcessKey(KEY_DOWN, 1); // press down key - ProcessKey(KEY_DOWN, 0); // and release it - break; - - case SwipeDirection::LEFT: - case SwipeDirection::RIGHT: - ProcessKey(KEY_POWER, 1); // press power key - ProcessKey(KEY_POWER, 0); // and release it - break; - }; -} - -int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { - struct input_event ev; - if (ev_get_input(fd, epevents, &ev) == -1) { - return -1; - } - - // Touch inputs handling. - // - // We handle the touch inputs by tracking the position changes between initial contacting and - // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon - // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes. - // - // Per the doc Multi-touch Protocol at below, there are two protocols. - // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt - // - // The main difference between the stateless type A protocol and the stateful type B slot protocol - // lies in the usage of identifiable contacts to reduce the amount of data sent to userspace. The - // slot protocol (i.e. type B) sends ABS_MT_TRACKING_ID with a unique id on initial contact, and - // sends ABS_MT_TRACKING_ID -1 upon lifting the contact. Protocol A doesn't send - // ABS_MT_TRACKING_ID -1 on lifting, but the driver may additionally report BTN_TOUCH event. - // - // For protocol A, we rely on BTN_TOUCH to recognize lifting, while for protocol B we look for - // ABS_MT_TRACKING_ID being -1. - // - // Touch input events will only be available if touch_screen_allowed_ is set. - - if (ev.type == EV_SYN) { - if (touch_screen_allowed_ && ev.code == SYN_REPORT) { - // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the - // contact. - if (touch_finger_down_ && !touch_swiping_) { - touch_start_X_ = touch_X_; - touch_start_Y_ = touch_Y_; - touch_swiping_ = true; - } else if (!touch_finger_down_ && touch_swiping_) { - touch_swiping_ = false; - OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_); - } - } - return 0; - } - - if (ev.type == EV_REL) { - if (ev.code == REL_Y) { - // accumulate the up or down motion reported by - // the trackball. When it exceeds a threshold - // (positive or negative), fake an up/down - // key event. - rel_sum += ev.value; - if (rel_sum > 3) { - ProcessKey(KEY_DOWN, 1); // press down key - ProcessKey(KEY_DOWN, 0); // and release it - rel_sum = 0; - } else if (rel_sum < -3) { - ProcessKey(KEY_UP, 1); // press up key - ProcessKey(KEY_UP, 0); // and release it - rel_sum = 0; - } - } - } else { - rel_sum = 0; - } - - if (touch_screen_allowed_ && ev.type == EV_ABS) { - if (ev.code == ABS_MT_SLOT) { - touch_slot_ = ev.value; - } - // Ignore other fingers. - if (touch_slot_ > 0) return 0; - - switch (ev.code) { - case ABS_MT_POSITION_X: - touch_X_ = ev.value; - touch_finger_down_ = true; - break; - - case ABS_MT_POSITION_Y: - touch_Y_ = ev.value; - touch_finger_down_ = true; - break; - - case ABS_MT_TRACKING_ID: - // Protocol B: -1 marks lifting the contact. - if (ev.value < 0) touch_finger_down_ = false; - break; - } - return 0; - } - - if (ev.type == EV_KEY && ev.code <= KEY_MAX) { - if (touch_screen_allowed_) { - if (ev.code == BTN_TOUCH) { - // A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means - // lifting the contact. - touch_finger_down_ = (ev.value == 1); - } - - // Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger - // additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than - // KEY_POWER and KEY_UP as KEY_DOWN). - if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) { - return 0; - } - } - - ProcessKey(ev.code, ev.value); - } - - return 0; -} - -// 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. -// -// 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; - - { - 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; - } - } - - bool reboot_enabled = enable_reboot; - if (register_key) { - switch (CheckKey(key_code, long_press)) { - case RecoveryUI::IGNORE: - break; - - case RecoveryUI::TOGGLE: - ShowText(!IsTextVisible()); - break; - - case RecoveryUI::REBOOT: - if (reboot_enabled) { - reboot("reboot,"); - while (true) { - pause(); - } - } - break; - - case RecoveryUI::ENQUEUE: - EnqueueKey(key_code); - break; - } - } -} - -void RecoveryUI::TimeKey(int key_code, int count) { - std::this_thread::sleep_for(750ms); // 750 ms == "long" - bool long_press = false; - { - std::lock_guard lg(key_queue_mutex); - if (key_last_down == key_code && key_down_count == count) { - long_press = key_long_press = true; - } - } - if (long_press) KeyLongPress(key_code); -} - -void RecoveryUI::EnqueueKey(int key_code) { - 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; - key_queue_cond.notify_one(); - } -} - -void RecoveryUI::SetScreensaverState(ScreensaverState state) { - switch (state) { - case ScreensaverState::NORMAL: - if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_), - brightness_file_)) { - screensaver_state_ = ScreensaverState::NORMAL; - LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ - << "%)"; - } else { - LOG(ERROR) << "Unable to set brightness to normal"; - } - break; - case ScreensaverState::DIMMED: - if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), - brightness_file_)) { - LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_ - << "%)"; - screensaver_state_ = ScreensaverState::DIMMED; - } else { - LOG(ERROR) << "Unable to set brightness to dim"; - } - break; - case ScreensaverState::OFF: - if (android::base::WriteStringToFile("0", brightness_file_)) { - LOG(INFO) << "Brightness: 0 (off)"; - screensaver_state_ = ScreensaverState::OFF; - } else { - LOG(ERROR) << "Unable to set brightness to off"; - } - break; - default: - LOG(ERROR) << "Invalid screensaver state"; - } -} - -int RecoveryUI::WaitKey() { - std::unique_lock lk(key_queue_mutex); - - // Check for a saved key queue interruption. - if (key_interrupted_) { - SetScreensaverState(ScreensaverState::NORMAL); - return static_cast(KeyError::INTERRUPTED); - } - - // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is plugged in. - do { - bool rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] { - return this->key_queue_len != 0 || key_interrupted_; - }); - if (key_interrupted_) { - SetScreensaverState(ScreensaverState::NORMAL); - return static_cast(KeyError::INTERRUPTED); - } - if (screensaver_state_ != ScreensaverState::DISABLED) { - if (!rc) { - // Must be after a timeout. Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF. - if (screensaver_state_ == ScreensaverState::NORMAL) { - SetScreensaverState(ScreensaverState::DIMMED); - } else if (screensaver_state_ == ScreensaverState::DIMMED) { - SetScreensaverState(ScreensaverState::OFF); - } - } else if (screensaver_state_ != ScreensaverState::NORMAL) { - // Drop the first key if it's changing from OFF to NORMAL. - if (screensaver_state_ == ScreensaverState::OFF) { - if (key_queue_len > 0) { - memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); - } - } - - // Reset the brightness to normal. - SetScreensaverState(ScreensaverState::NORMAL); - } - } - } while (IsUsbConnected() && key_queue_len == 0); - - int key = static_cast(KeyError::TIMED_OUT); - if (key_queue_len > 0) { - key = key_queue[0]; - memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); - } - return key; -} - -void RecoveryUI::InterruptKey() { - { - std::lock_guard lg(key_queue_mutex); - key_interrupted_ = true; - } - key_queue_cond.notify_one(); -} - -bool RecoveryUI::IsUsbConnected() { - int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); - if (fd < 0) { - printf("failed to open /sys/class/android_usb/android0/state: %s\n", strerror(errno)); - return 0; - } - - char buf; - // USB is connected if android_usb state is CONNECTED or CONFIGURED. - int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); - if (close(fd) < 0) { - printf("failed to close /sys/class/android_usb/android0/state: %s\n", strerror(errno)); - } - return connected; -} - -bool RecoveryUI::IsKeyPressed(int key) { - std::lock_guard lg(key_queue_mutex); - int pressed = key_pressed[key]; - return pressed; -} - -bool RecoveryUI::IsLongPress() { - std::lock_guard lg(key_queue_mutex); - bool result = key_long_press; - return result; -} - -bool RecoveryUI::HasThreeButtons() { - return has_power_key && has_up_key && has_down_key; -} - -bool RecoveryUI::HasPowerKey() const { - return has_power_key; -} - -bool RecoveryUI::HasTouchScreen() const { - return has_touch_screen; -} - -void RecoveryUI::FlushKeys() { - std::lock_guard lg(key_queue_mutex); - key_queue_len = 0; -} - -RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { - { - 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_)) { - if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) { - return TOGGLE; - } - } else { - // Otherwise long press of any button toggles to the text display, - // and there's no way to toggle back (but that's pretty useless anyway). - if (is_long_press && !IsTextVisible()) { - return TOGGLE; - } - - // Also, for button-limited devices, a long press is translated to KEY_ENTER. - if (is_long_press && IsTextVisible()) { - EnqueueKey(KEY_ENTER); - return IGNORE; - } - } - - // Press power seven times in a row to reboot. - if (key == KEY_POWER) { - bool reboot_enabled = enable_reboot; - - if (reboot_enabled) { - ++consecutive_power_keys; - if (consecutive_power_keys >= 7) { - return REBOOT; - } - } - } else { - consecutive_power_keys = 0; - } - - last_key = key; - return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; -} - -void RecoveryUI::KeyLongPress(int) { -} - -void RecoveryUI::SetEnableReboot(bool enabled) { - std::lock_guard lg(key_queue_mutex); - enable_reboot = enabled; -} diff --git a/ui.h b/ui.h deleted file mode 100644 index b387ae3c9..000000000 --- a/ui.h +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2011 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 RECOVERY_UI_H -#define RECOVERY_UI_H - -#include // KEY_MAX - -#include -#include -#include -#include -#include -#include -#include - -// Abstract class for controlling the user interface during recovery. -class RecoveryUI { - public: - enum Icon { - NONE, - INSTALLING_UPDATE, - ERASING, - NO_COMMAND, - ERROR - }; - - enum ProgressType { - EMPTY, - INDETERMINATE, - DETERMINATE - }; - - enum KeyAction { - ENQUEUE, - TOGGLE, - REBOOT, - IGNORE - }; - - enum class KeyError : int { - TIMED_OUT = -1, - INTERRUPTED = -2, - }; - - RecoveryUI(); - - virtual ~RecoveryUI(); - - // Initializes the object; called before anything else. UI texts will be initialized according to - // the given locale. Returns true on success. - virtual bool Init(const std::string& locale); - - virtual std::string GetLocale() const = 0; - - // Shows a stage indicator. Called immediately after Init(). - virtual void SetStage(int current, int max) = 0; - - // Sets the overall recovery state ("background image"). - virtual void SetBackground(Icon icon) = 0; - virtual void SetSystemUpdateText(bool security_update) = 0; - - // --- progress indicator --- - virtual void SetProgressType(ProgressType determinate) = 0; - - // Shows a progress bar and define the scope of the next operation: - // portion - fraction of the progress bar the next operation will use - // seconds - expected time interval (progress bar moves at this minimum rate) - virtual void ShowProgress(float portion, float seconds) = 0; - - // Sets progress bar position (0.0 - 1.0 within the scope defined by the last call to - // ShowProgress). - virtual void SetProgress(float fraction) = 0; - - // --- text log --- - - virtual void ShowText(bool visible) = 0; - - virtual bool IsTextVisible() = 0; - - virtual bool WasTextEverVisible() = 0; - - // Writes a message to the on-screen log (shown if the user has toggled on the text display). - // Print() will also dump the message to stdout / log file, while PrintOnScreenOnly() not. - virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; - virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; - - // Shows the contents of the given file. Caller ensures the patition that contains the file has - // been mounted. - virtual void ShowFile(const std::string& filename) = 0; - - // --- key handling --- - - // Waits for a key and return it. May return TIMED_OUT after timeout and - // KeyError::INTERRUPTED on a key interrupt. - virtual int WaitKey(); - - // Wakes up the UI if it is waiting on key input, causing WaitKey to return KeyError::INTERRUPTED. - virtual void InterruptKey(); - - virtual bool IsKeyPressed(int key); - virtual bool IsLongPress(); - - // Returns true if you have the volume up/down and power trio typical of phones and tablets, false - // otherwise. - virtual bool HasThreeButtons(); - - // Returns true if it has a power key. - virtual bool HasPowerKey() const; - - // Returns true if it supports touch inputs. - virtual bool HasTouchScreen() const; - - // Erases any queued-up keys. - virtual void FlushKeys(); - - // Called on each key press, even while operations are in progress. Return value indicates whether - // an immediate operation should be triggered (toggling the display, rebooting the device), or if - // the key should be enqueued for use by the main thread. - virtual KeyAction CheckKey(int key, bool is_long_press); - - // Called when a key is held down long enough to have been a long-press (but before the key is - // released). This means that if the key is eventually registered (released without any other keys - // being pressed in the meantime), CheckKey will be called with 'is_long_press' true. - virtual void KeyLongPress(int key); - - // Normally in recovery there's a key sequence that triggers immediate reboot of the device, - // regardless of what recovery is doing (with the default CheckKey implementation, it's pressing - // the power button 7 times in row). Call this to enable or disable that feature. It is enabled by - // default. - virtual void SetEnableReboot(bool enabled); - - // --- menu display --- - - virtual void SetTitle(const std::vector& lines) = 0; - - // Displays a menu with the given 'headers' and 'items'. The supplied 'key_handler' callback, - // which is typically bound to Device::HandleMenuKey(), should return the expected action for the - // given key code and menu visibility (e.g. to move the cursor or to select an item). Caller sets - // 'menu_only' to true to ensure only a menu item gets selected and returned. Otherwise if - // 'menu_only' is false, ShowMenu() will forward any non-negative value returned from the - // key_handler, which may be beyond the range of menu items. This could be used to trigger a - // device-specific action, even without that being listed in the menu. Caller needs to handle - // such a case accordingly (e.g. by calling Device::InvokeMenuItem() to process the action). - // Returns a non-negative value (the chosen item number or device-specific action code), or - // static_cast(TIMED_OUT) if timed out waiting for input or - // static_cast(ERR_KEY_INTERTUPT) if interrupted, such as by InterruptKey(). - virtual size_t ShowMenu(const std::vector& headers, - const std::vector& items, size_t initial_selection, - bool menu_only, const std::function& key_handler) = 0; - - // Displays the localized wipe data menu with pre-generated graphs. If there's an issue - // with the graphs, falls back to use the backup string headers and items instead. The initial - // selection is the 0th item in the menu, which is expected to reboot the device without a wipe. - virtual size_t ShowPromptWipeDataMenu(const std::vector& backup_headers, - const std::vector& backup_items, - const std::function& key_handler) = 0; - // Displays the localized wipe data confirmation menu with pre-generated images. Falls back to - // the text strings upon failures. The initial selection is the 0th item, which returns to the - // upper level menu. - virtual size_t ShowPromptWipeDataConfirmationMenu( - const std::vector& backup_headers, const std::vector& backup_items, - const std::function& key_handler) = 0; - - // Set whether or not the fastbootd logo is displayed. - void SetEnableFastbootdLogo(bool enable) { - fastbootd_logo_enabled_ = enable; - } - - // Resets the key interrupt status. - void ResetKeyInterruptStatus() { - key_interrupted_ = false; - } - - // Returns the key interrupt status. - bool IsKeyInterrupted() const { - return key_interrupted_; - } - - protected: - void EnqueueKey(int key_code); - - // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of - // the max_brightness). Because the absolute values may vary across devices. These two values can - // be configured via subclassing. Setting brightness_normal_ to 0 to disable screensaver. - unsigned int brightness_normal_; - unsigned int brightness_dimmed_; - std::string brightness_file_; - std::string max_brightness_file_; - - // Whether we should listen for touch inputs (default: false). - bool touch_screen_allowed_; - - bool fastbootd_logo_enabled_; - - private: - enum class ScreensaverState { - DISABLED, - NORMAL, - DIMMED, - OFF - }; - - // The sensitivity when detecting a swipe. - const int touch_low_threshold_; - const int touch_high_threshold_; - - void OnKeyDetected(int key_code); - 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(); - - bool InitScreensaver(); - void SetScreensaverState(ScreensaverState state); - // Key event input queue - std::mutex key_queue_mutex; - std::condition_variable key_queue_cond; - bool key_interrupted_; - 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 - bool key_long_press; // under key_queue_mutex - int key_down_count; // under key_queue_mutex - bool enable_reboot; // under key_queue_mutex - int rel_sum; - - int consecutive_power_keys; - int last_key; - - bool has_power_key; - bool has_up_key; - bool has_down_key; - bool has_touch_screen; - - // Touch event related variables. See the comments in RecoveryUI::OnInputEvent(). - int touch_slot_; - int touch_X_; - int touch_Y_; - int touch_start_X_; - int touch_start_Y_; - bool touch_finger_down_; - bool touch_swiping_; - bool is_bootreason_recovery_ui_; - - std::thread input_thread_; - std::atomic input_thread_stopped_{ false }; - - ScreensaverState screensaver_state_; - - // The following two contain the absolute values computed from brightness_normal_ and - // brightness_dimmed_ respectively. - unsigned int brightness_normal_value_; - unsigned int brightness_dimmed_value_; -}; - -#endif // RECOVERY_UI_H diff --git a/vr_device.cpp b/vr_device.cpp deleted file mode 100644 index 61e15cbb6..000000000 --- a/vr_device.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2017 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 "device.h" -#include "vr_ui.h" - -Device* make_device() { - return new Device(new VrRecoveryUI); -} - diff --git a/vr_ui.cpp b/vr_ui.cpp deleted file mode 100644 index 1f0292c30..000000000 --- a/vr_ui.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2017 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 "vr_ui.h" - -#include - -#include "minui/minui.h" - -constexpr int kDefaultStereoOffset = 0; - -VrRecoveryUI::VrRecoveryUI() - : stereo_offset_( - android::base::GetIntProperty("ro.recovery.ui.stereo_offset", kDefaultStereoOffset)) {} - -int VrRecoveryUI::ScreenWidth() const { - return gr_fb_width() / 2; -} - -int VrRecoveryUI::ScreenHeight() const { - return gr_fb_height(); -} - -void VrRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, - int dy) const { - gr_blit(surface, sx, sy, w, h, dx + stereo_offset_, dy); - gr_blit(surface, sx, sy, w, h, dx - stereo_offset_ + ScreenWidth(), dy); -} - -void VrRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { - gr_texticon(x + stereo_offset_, y, surface); - gr_texticon(x - stereo_offset_ + ScreenWidth(), y, surface); -} - -int VrRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { - gr_text(gr_sys_font(), x + stereo_offset_, y, line.c_str(), bold); - gr_text(gr_sys_font(), x - stereo_offset_ + ScreenWidth(), y, line.c_str(), bold); - return char_height_ + 4; -} - -int VrRecoveryUI::DrawHorizontalRule(int y) const { - y += 4; - gr_fill(margin_width_ + stereo_offset_, y, ScreenWidth() - margin_width_ + stereo_offset_, y + 2); - gr_fill(ScreenWidth() + margin_width_ - stereo_offset_, y, - gr_fb_width() - margin_width_ - stereo_offset_, y + 2); - return y + 4; -} - -void VrRecoveryUI::DrawHighlightBar(int /* x */, int y, int /* width */, int height) const { - gr_fill(margin_width_ + stereo_offset_, y, ScreenWidth() - margin_width_ + stereo_offset_, - y + height); - gr_fill(ScreenWidth() + margin_width_ - stereo_offset_, y, - gr_fb_width() - margin_width_ - stereo_offset_, y + height); -} - -void VrRecoveryUI::DrawFill(int x, int y, int w, int h) const { - gr_fill(x + stereo_offset_, y, w, h); - gr_fill(x - stereo_offset_ + ScreenWidth(), y, w, h); -} diff --git a/vr_ui.h b/vr_ui.h deleted file mode 100644 index 2e8ac5921..000000000 --- a/vr_ui.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2017 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 RECOVERY_VR_UI_H -#define RECOVERY_VR_UI_H - -#include - -#include "screen_ui.h" - -class VrRecoveryUI : public ScreenRecoveryUI { - public: - VrRecoveryUI(); - - protected: - // Pixel offsets to move drawing functions to visible range. - // Can vary per device depending on screen size and lens distortion. - const int stereo_offset_; - - int ScreenWidth() const override; - int ScreenHeight() const override; - - void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, - int dy) const override; - int DrawHorizontalRule(int y) const override; - void DrawHighlightBar(int x, int y, int width, int height) const override; - void DrawFill(int x, int y, int w, int h) const override; - void DrawTextIcon(int x, int y, const GRSurface* surface) const override; - int DrawTextLine(int x, int y, const std::string& line, bool bold) const override; -}; - -#endif // RECOVERY_VR_UI_H diff --git a/wear_device.cpp b/wear_device.cpp deleted file mode 100644 index 3268130b0..000000000 --- a/wear_device.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2017 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 "device.h" -#include "wear_ui.h" - -Device* make_device() { - return new Device(new WearRecoveryUI); -} - diff --git a/wear_ui.cpp b/wear_ui.cpp deleted file mode 100644 index 6da84c924..000000000 --- a/wear_ui.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2014 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 "wear_ui.h" - -#include - -#include -#include - -#include -#include -#include - -constexpr int kDefaultProgressBarBaseline = 259; -constexpr int kDefaultMenuUnusableRows = 9; - -WearRecoveryUI::WearRecoveryUI() - : ScreenRecoveryUI(true), - progress_bar_baseline_(android::base::GetIntProperty("ro.recovery.ui.progress_bar_baseline", - kDefaultProgressBarBaseline)), - menu_unusable_rows_(android::base::GetIntProperty("ro.recovery.ui.menu_unusable_rows", - kDefaultMenuUnusableRows)) { - // TODO: menu_unusable_rows_ should be computed based on the lines in draw_screen_locked(). - - touch_screen_allowed_ = true; -} - -int WearRecoveryUI::GetProgressBaseline() const { - return progress_bar_baseline_; -} - -// Draw background frame on the screen. Does not flip pages. -// Should only be called with updateMutex locked. -// TODO merge drawing routines with screen_ui -void WearRecoveryUI::draw_background_locked() { - pagesIdentical = false; - gr_color(0, 0, 0, 255); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - if (current_icon_ != NONE) { - const auto& frame = GetCurrentFrame(); - int frame_width = gr_get_width(frame); - int frame_height = gr_get_height(frame); - int frame_x = (gr_fb_width() - frame_width) / 2; - int frame_y = (gr_fb_height() - frame_height) / 2; - gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); - - // Draw recovery text on screen above progress bar. - const auto& text = GetCurrentText(); - int text_x = (ScreenWidth() - gr_get_width(text)) / 2; - int text_y = GetProgressBaseline() - gr_get_height(text) - 10; - gr_color(255, 255, 255, 255); - gr_texticon(text_x, text_y, text); - } -} - -void WearRecoveryUI::draw_screen_locked() { - draw_background_locked(); - if (!show_text) { - draw_foreground_locked(); - } else { - SetColor(UIElement::TEXT_FILL); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - // clang-format off - static std::vector SWIPE_HELP = { - "Swipe up/down to move.", - "Swipe left/right to select.", - "", - }; - // clang-format on - draw_menu_and_text_buffer_locked(SWIPE_HELP); - } -} - -// TODO merge drawing routines with screen_ui -void WearRecoveryUI::update_progress_locked() { - draw_screen_locked(); - gr_flip(); -} - -void WearRecoveryUI::SetStage(int /* current */, int /* max */) {} - -std::unique_ptr WearRecoveryUI::CreateMenu(const std::vector& text_headers, - const std::vector& text_items, - size_t initial_selection) const { - if (text_rows_ > 0 && text_cols_ > 0) { - return std::make_unique(scrollable_menu_, text_rows_ - menu_unusable_rows_ - 1, - text_cols_ - 1, text_headers, text_items, initial_selection, - char_height_, *this); - } - - return nullptr; -} diff --git a/wear_ui.h b/wear_ui.h deleted file mode 100644 index 429af69d2..000000000 --- a/wear_ui.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2014 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 RECOVERY_WEAR_UI_H -#define RECOVERY_WEAR_UI_H - -#include -#include - -#include "screen_ui.h" - -class WearRecoveryUI : public ScreenRecoveryUI { - public: - WearRecoveryUI(); - - void SetStage(int current, int max) override; - - protected: - // progress bar vertical position, it's centered horizontally - const int progress_bar_baseline_; - - // Unusable rows when displaying the recovery menu, including the lines for headers (Android - // Recovery, build id and etc) and the bottom lines that may otherwise go out of the screen. - const int menu_unusable_rows_; - - std::unique_ptr CreateMenu(const std::vector& text_headers, - const std::vector& text_items, - size_t initial_selection) const override; - - int GetProgressBaseline() const override; - - void update_progress_locked() override; - - private: - void draw_background_locked() override; - void draw_screen_locked() override; -}; - -#endif // RECOVERY_WEAR_UI_H -- cgit v1.2.3 From 2f8afba7073e6339287ba48b9af5a8868613e409 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 8 Apr 2019 11:26:11 -0700 Subject: DO NOT MERGE: Build libinstall as a static library. It was once considered to be shared between recovery and minadbd, so that the latter can start an install on its own. The plan has been changed, since package install -- including device wipe operations -- could be device-specific, which should be done by recovery only. This CL moves libinstall back to a static library, which also saves the overall size (reducing from 140256 + 660576 to 555880 bytes on aosp_taimen-userdebug). Bug: 130166585 Test: Run recovery_component_test. Test: `adb sideload` on taimen. Change-Id: Ib1f5f79f235df4682c0bd104425c9c122f6091ba --- Android.bp | 3 +-- CleanSpec.mk | 10 +++++++--- install/Android.bp | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Android.bp b/Android.bp index a44a2c625..f2230ae00 100644 --- a/Android.bp +++ b/Android.bp @@ -69,6 +69,7 @@ cc_defaults { ], static_libs: [ + "libinstall", "librecovery_fastboot", "libminui", "libotautil", @@ -93,7 +94,6 @@ cc_library_static { ], shared_libs: [ - "libinstall", "librecovery_ui", ], } @@ -113,7 +113,6 @@ cc_binary { ], shared_libs: [ - "libinstall", "libminadbd_services", "librecovery_ui", ], diff --git a/CleanSpec.mk b/CleanSpec.mk index fec823e7e..a7ab0d9be 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -44,9 +44,13 @@ #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) -# ************************************************ -# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST -# ************************************************ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sbin) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinstall.recovery_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/system/lib64/libinstall.so) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ diff --git a/install/Android.bp b/install/Android.bp index aa47990ae..221ce72a0 100644 --- a/install/Android.bp +++ b/install/Android.bp @@ -47,7 +47,7 @@ cc_defaults { ], } -cc_library { +cc_library_static { name: "libinstall", recovery_available: true, -- cgit v1.2.3 From 95d67323a423db036a43a8c2de1072d514f3717f Mon Sep 17 00:00:00 2001 From: xunchang Date: Fri, 5 Apr 2019 16:16:07 -0700 Subject: DO NOT MERGE: Add socket communication between recovery and minadbd This cl adds a socket pair to support the communication between recovery and minadbd. Therefore, minadbd will be able to issue multiple commands to recovery and get back the status of each command. This cl also switches the adb sideload from the recovery menu to use this protocol; and moves minadbd to a separate binary. Bug: 130166585 Test: sideload a package Change-Id: I80d36d5c4e6fe1ae3ea23640907bc50c0dc0d482 (cherry picked from commit 34690ced91e22f5d9b5dd19c33b11c8e0b4bafa0) --- Android.bp | 2 +- install/Android.bp | 4 + install/adb_install.cpp | 285 +++++++++++++++++++++++++++++++++++-------- minadbd/Android.bp | 31 ++++- minadbd/minadbd.cpp | 48 ++++++-- minadbd/minadbd.h | 22 ---- minadbd/minadbd_services.cpp | 73 +++++++++-- minadbd/minadbd_services.h | 19 +++ minadbd/minadbd_types.h | 53 ++++++++ recovery_main.cpp | 11 -- 10 files changed, 446 insertions(+), 102 deletions(-) delete mode 100644 minadbd/minadbd.h create mode 100644 minadbd/minadbd_services.h create mode 100644 minadbd/minadbd_types.h diff --git a/Android.bp b/Android.bp index f2230ae00..dd18a89ad 100644 --- a/Android.bp +++ b/Android.bp @@ -113,7 +113,6 @@ cc_binary { ], shared_libs: [ - "libminadbd_services", "librecovery_ui", ], @@ -125,6 +124,7 @@ cc_binary { required: [ "e2fsdroid.recovery", "librecovery_ui_ext", + "minadbd", "mke2fs.conf.recovery", "mke2fs.recovery", "recovery_deps", diff --git a/install/Android.bp b/install/Android.bp index 221ce72a0..ce4244ca4 100644 --- a/install/Android.bp +++ b/install/Android.bp @@ -19,6 +19,10 @@ cc_defaults { "recovery_defaults", ], + header_libs: [ + "libminadbd_headers", + ], + shared_libs: [ "libbase", "libbootloader_message", diff --git a/install/adb_install.cpp b/install/adb_install.cpp index 5296ff608..dc7ee0b32 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -21,97 +21,286 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include +#include + +#include #include +#include #include +#include +#include #include "fuse_sideload.h" #include "install/install.h" +#include "minadbd_types.h" #include "recovery_ui/ui.h" +using CommandFunction = std::function; + static bool SetUsbConfig(const std::string& state) { android::base::SetProperty("sys.usb.config", state); return android::base::WaitForProperty("sys.usb.state", state); } -int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { - // Save the usb state to restore after the sideload operation. - std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); - // Clean up state and stop adbd. - if (usb_state != "none" && !SetUsbConfig("none")) { - LOG(ERROR) << "Failed to clear USB config"; - return INSTALL_ERROR; +// Parses the minadbd command in |message|; returns MinadbdCommands::kError upon errors. +static MinadbdCommands ParseMinadbdCommands(const std::string& message) { + if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) { + LOG(ERROR) << "Failed to parse command in message " << message; + return MinadbdCommands::kError; } - ui->Print( - "\n\nNow send the package you want to apply\n" - "to the device with \"adb sideload \"...\n"); - - pid_t child; - if ((child = fork()) == 0) { - execl("/system/bin/recovery", "recovery", "--adbd", nullptr); - _exit(EXIT_FAILURE); + auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix)); + auto cmd_code = android::base::get_unaligned(cmd_code_string.c_str()); + if (cmd_code >= static_cast(MinadbdCommands::kError)) { + LOG(ERROR) << "Unsupported command code: " << cmd_code; + return MinadbdCommands::kError; } - if (!SetUsbConfig("sideload")) { - LOG(ERROR) << "Failed to set usb config to sideload"; - return INSTALL_ERROR; - } + return static_cast(cmd_code); +} - // How long (in seconds) we wait for the host to start sending us a package, before timing out. - static constexpr int ADB_INSTALL_TIMEOUT = 300; +static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { + char message[kMinadbdMessageSize]; + memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix)); + android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status); - // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host connects and starts serving a - // package. Poll for its appearance. (Note that inotify doesn't work with FUSE.) - int result = INSTALL_ERROR; - int status; - bool waited = false; - for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { - if (waitpid(child, &status, WNOHANG) != 0) { - result = INSTALL_ERROR; - waited = true; - break; - } + if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to write message " << message; + return false; + } + return true; +} +// Installs the package from FUSE. Returns true if the installation succeeds, and false otherwise. +static bool AdbInstallPackageHandler(bool* wipe_cache, RecoveryUI* ui, int* result) { + // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long + // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME + // will start to exist once the host connects and starts serving a package. Poll for its + // appearance. (Note that inotify doesn't work with FUSE.) + constexpr int ADB_INSTALL_TIMEOUT = 15; + *result = INSTALL_ERROR; + for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { struct stat st; if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) { sleep(1); continue; } else { - ui->Print("\nTimed out waiting for package.\n\n"); - result = INSTALL_ERROR; - kill(child, SIGKILL); + ui->Print("\nTimed out waiting for fuse to be ready.\n\n"); break; } } - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, false, 0, ui); + *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, false, 0, ui); break; } - if (!waited) { - // Calling stat() on this magic filename signals the minadbd subprocess to shut down. - struct stat st; - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + // Calling stat() on this magic filename signals the FUSE to exit. + struct stat st; + stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + return *result == INSTALL_SUCCESS; +} + +// Parses and executes the command from minadbd. Returns false if we enter an invalid state so that +// the caller can kill the minadbd service properly. +static bool HandleMessageFromMinadbd( + int socket_fd, const std::map& command_map) { + char buffer[kMinadbdMessageSize]; + if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to read message from minadbd"; + return false; + } + + std::string message(buffer, buffer + kMinadbdMessageSize); + auto command_type = ParseMinadbdCommands(message); + if (command_type == MinadbdCommands::kError) { + return false; + } + if (command_map.find(command_type) == command_map.end()) { + LOG(ERROR) << "Unsupported command: " + << android::base::get_unaligned( + message.substr(strlen(kMinadbdCommandPrefix)).c_str()); + return false; + } + + // We have received a valid command, execute the corresponding function. + const auto& command_func = command_map.at(command_type); + if (!command_func()) { + LOG(ERROR) << "Failed to execute command " << static_cast(command_type); + return WriteStatusToFd(MinadbdCommandStatus::kFailure, socket_fd); + } + return WriteStatusToFd(MinadbdCommandStatus::kSuccess, socket_fd); +} + +// TODO(xunchang) add a wrapper function and kill the minadbd service there. +static void ListenAndExecuteMinadbdCommands( + pid_t minadbd_pid, android::base::unique_fd&& socket_fd, + const std::map& command_map) { + android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC)); + if (epoll_fd == -1) { + PLOG(ERROR) << "Failed to create epoll"; + kill(minadbd_pid, SIGKILL); + return; + } + + constexpr int EPOLL_MAX_EVENTS = 10; + struct epoll_event ev = {}; + ev.events = EPOLLIN | EPOLLHUP; + ev.data.fd = socket_fd.get(); + struct epoll_event events[EPOLL_MAX_EVENTS]; + if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, socket_fd.get(), &ev) == -1) { + PLOG(ERROR) << "Failed to add socket fd to epoll"; + kill(minadbd_pid, SIGKILL); + return; + } + + // Set the timeout to be 300s when waiting for minadbd commands. + constexpr int TIMEOUT_MILLIS = 300 * 1000; + while (true) { + // Poll for the status change of the socket_fd, and handle the message if the fd is ready to + // read. + int event_count = + TEMP_FAILURE_RETRY(epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, TIMEOUT_MILLIS)); + if (event_count == -1) { + PLOG(ERROR) << "Failed to wait for epoll events"; + kill(minadbd_pid, SIGKILL); + return; + } + if (event_count == 0) { + LOG(ERROR) << "Timeout waiting for messages from minadbd"; + kill(minadbd_pid, SIGKILL); + return; + } + + for (int n = 0; n < event_count; n++) { + if (events[n].events & EPOLLHUP) { + LOG(INFO) << "Socket has been closed"; + kill(minadbd_pid, SIGKILL); + return; + } + if (!HandleMessageFromMinadbd(socket_fd.get(), command_map)) { + kill(minadbd_pid, SIGKILL); + return; + } + } + } +} + +// Recovery starts minadbd service as a child process, and spawns another thread to listen for the +// message from minadbd through a socket pair. Here is an example to execute one command from adb +// host. +// a. recovery b. listener thread c. minadbd service +// +// a1. create socket pair +// a2. fork minadbd service +// c3. wait for the adb commands +// from host +// c4. after receiving host commands: +// 1) set up pre-condition (i.e. +// start fuse for adb sideload) +// 2) issue command through +// socket. +// 3) wait for result +// a5. start listener thread +// b6. listen for message from +// minadbd in a loop. +// b7. After receiving a minadbd +// command from socket +// 1) execute the command function +// 2) send the result back to +// minadbd +// ...... +// c8. exit upon receiving the +// result +// a9. wait for listener thread +// to exit. +// +// a10. wait for minadbd to +// exit +// b11. exit the listening loop +// +static void CreateMinadbdServiceAndExecuteCommands( + const std::map& command_map) { + signal(SIGPIPE, SIG_IGN); + + android::base::unique_fd recovery_socket; + android::base::unique_fd minadbd_socket; + if (!android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &recovery_socket, &minadbd_socket)) { + PLOG(ERROR) << "Failed to create socket"; + return; + } + + pid_t child = fork(); + if (child == -1) { + PLOG(ERROR) << "Failed to fork child process"; + return; + } + if (child == 0) { + recovery_socket.reset(); + execl("/system/bin/minadbd", "minadbd", "--socket_fd", + std::to_string(minadbd_socket.release()).c_str(), nullptr); + + _exit(EXIT_FAILURE); + } + + minadbd_socket.reset(); + + // We need to call SetUsbConfig() after forking minadbd service. Because the function waits for + // the usb state to be updated, which depends on sys.usb.ffs.ready=1 set in the adb daemon. + if (!SetUsbConfig("sideload")) { + LOG(ERROR) << "Failed to set usb config to sideload"; + return; + } + + std::thread listener_thread(ListenAndExecuteMinadbdCommands, child, std::move(recovery_socket), + std::ref(command_map)); - // TODO: there should be a way to cancel waiting for a package (by pushing some button combo on - // the device). For now you just have to 'adb sideload' a file that's not a valid package, like - // "/dev/null". - waitpid(child, &status, 0); + if (listener_thread.joinable()) { + listener_thread.join(); } + int status; + waitpid(child, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - if (WEXITSTATUS(status) == 3) { - ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); + if (WEXITSTATUS(status) == MinadbdErrorCode::kMinadbdAdbVersionError) { + LOG(ERROR) << "\nYou need adb 1.0.32 or newer to sideload\nto this device.\n"; } else if (!WIFSIGNALED(status)) { - ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status)); + LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")"; } } + signal(SIGPIPE, SIG_DFL); +} + +int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { + // Save the usb state to restore after the sideload operation. + std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); + // Clean up state and stop adbd. + if (usb_state != "none" && !SetUsbConfig("none")) { + LOG(ERROR) << "Failed to clear USB config"; + return INSTALL_ERROR; + } + + ui->Print( + "\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload \"...\n"); + + int install_result = INSTALL_ERROR; + std::map command_map{ + { MinadbdCommands::kInstall, + std::bind(&AdbInstallPackageHandler, wipe_cache, ui, &install_result) }, + }; + + CreateMinadbdServiceAndExecuteCommands(command_map); + // Clean up before switching to the older state, for example setting the state // to none sets sys/class/android_usb/android0/enable to 0. if (!SetUsbConfig("none")) { @@ -124,5 +313,5 @@ int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { } } - return result; + return install_result; } diff --git a/minadbd/Android.bp b/minadbd/Android.bp index 9b889f4a2..e4f7712e5 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -40,7 +40,6 @@ cc_library { srcs: [ "fuse_adb_provider.cpp", - "minadbd.cpp", "minadbd_services.cpp", ], @@ -52,6 +51,36 @@ cc_library { ], } +cc_library_headers { + name: "libminadbd_headers", + recovery_available: true, + // TODO create a include dir + export_include_dirs: [ + ".", + ], +} + +cc_binary { + name: "minadbd", + recovery: true, + + defaults: [ + "minadbd_defaults", + ], + + srcs: [ + "minadbd.cpp", + ], + + shared_libs: [ + "libadbd", + "libbase", + "libcrypto", + "libfusesideload", + "libminadbd_services", + ], +} + cc_test { name: "minadbd_test", isolated: true, diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp index 349189cc7..57158ad57 100644 --- a/minadbd/minadbd.cpp +++ b/minadbd/minadbd.cpp @@ -14,30 +14,54 @@ * limitations under the License. */ -#include "minadbd.h" - #include +#include #include #include #include +#include + +#include +#include #include "adb.h" #include "adb_auth.h" #include "transport.h" -int minadbd_main() { - adb_device_banner = "sideload"; +#include "minadbd_services.h" +#include "minadbd_types.h" + +int main(int argc, char** argv) { + android::base::InitLogging(argv, &android::base::StderrLogger); + // TODO(xunchang) implement a command parser + if (argc != 3 || strcmp("--socket_fd", argv[1]) != 0) { + LOG(ERROR) << "minadbd has invalid arguments, argc: " << argc; + exit(kMinadbdArgumentsParsingError); + } + + int socket_fd; + if (!android::base::ParseInt(argv[2], &socket_fd)) { + LOG(ERROR) << "Failed to parse int in " << argv[2]; + exit(kMinadbdArgumentsParsingError); + } + if (fcntl(socket_fd, F_GETFD, 0) == -1) { + PLOG(ERROR) << "Failed to get minadbd socket"; + exit(kMinadbdSocketIOError); + } + SetMinadbdSocketFd(socket_fd); + + adb_device_banner = "sideload"; - signal(SIGPIPE, SIG_IGN); + signal(SIGPIPE, SIG_IGN); - // We can't require authentication for sideloading. http://b/22025550. - auth_required = false; + // We can't require authentication for sideloading. http://b/22025550. + auth_required = false; - init_transport_registration(); - usb_init(); + init_transport_registration(); + usb_init(); - VLOG(ADB) << "Event loop starting"; - fdevent_loop(); + VLOG(ADB) << "Event loop starting"; + fdevent_loop(); - return 0; + return 0; } diff --git a/minadbd/minadbd.h b/minadbd/minadbd.h deleted file mode 100644 index 3570a5da5..000000000 --- a/minadbd/minadbd.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2016 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 MINADBD_H__ -#define MINADBD_H__ - -int minadbd_main(); - -#endif diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 6fe5c79bc..79e6fc4e0 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "minadbd_services.h" + #include #include #include @@ -27,38 +29,95 @@ #include #include +#include +#include +#include +#include +#include + #include "adb.h" #include "adb_unique_fd.h" #include "fdevent.h" #include "fuse_adb_provider.h" #include "fuse_sideload.h" +#include "minadbd_types.h" #include "services.h" #include "sysdeps.h" +static int minadbd_socket = -1; +void SetMinadbdSocketFd(int socket_fd) { + minadbd_socket = socket_fd; +} + +static bool WriteCommandToFd(MinadbdCommands cmd, int fd) { + char message[kMinadbdMessageSize]; + memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); + android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd); + + if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to write message " << message; + return false; + } + return true; +} + +// Blocks and reads the command status from |fd|. Returns false if the received message has a +// format error. +static bool WaitForCommandStatus(int fd, MinadbdCommandStatus* status) { + char buffer[kMinadbdMessageSize]; + if (!android::base::ReadFully(fd, buffer, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to response status from socket"; + exit(kMinadbdSocketIOError); + } + + std::string message(buffer, buffer + kMinadbdMessageSize); + if (!android::base::StartsWith(message, kMinadbdStatusPrefix)) { + LOG(ERROR) << "Failed to parse status in " << message; + return false; + } + + *status = android::base::get_unaligned( + message.substr(strlen(kMinadbdStatusPrefix)).c_str()); + return true; +} + static void sideload_host_service(unique_fd sfd, const std::string& args) { int64_t file_size; int block_size; if ((sscanf(args.c_str(), "%" SCNd64 ":%d", &file_size, &block_size) != 2) || file_size <= 0 || block_size <= 0) { - printf("bad sideload-host arguments: %s\n", args.c_str()); - exit(1); + LOG(ERROR) << "bad sideload-host arguments: " << args; + exit(kMinadbdPackageSizeError); } - printf("sideload-host file size %" PRId64 " block size %d\n", file_size, block_size); + LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size; + + if (!WriteCommandToFd(MinadbdCommands::kInstall, minadbd_socket)) { + exit(kMinadbdSocketIOError); + } auto adb_data_reader = std::make_unique(std::move(sfd), file_size, block_size); - int result = run_fuse_sideload(std::move(adb_data_reader)); + if (int result = run_fuse_sideload(std::move(adb_data_reader)); result != 0) { + LOG(ERROR) << "Failed to start fuse"; + exit(kMinadbdFuseStartError); + } + + MinadbdCommandStatus status; + if (!WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } + LOG(INFO) << "Got command status: " << static_cast(status); - printf("sideload_host finished\n"); - exit(result == 0 ? 0 : 1); + LOG(INFO) << "sideload_host finished"; + exit(kMinadbdSuccess); } unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) { if (name.starts_with("sideload:")) { // This exit status causes recovery to print a special error message saying to use a newer adb // (that supports sideload-host). - exit(3); + exit(kMinadbdAdbVersionError); } else if (name.starts_with("sideload-host:")) { std::string arg(name.substr(strlen("sideload-host:"))); return create_service_thread("sideload-host", diff --git a/minadbd/minadbd_services.h b/minadbd/minadbd_services.h new file mode 100644 index 000000000..6835bd770 --- /dev/null +++ b/minadbd/minadbd_services.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2016 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 + +void SetMinadbdSocketFd(int socket_fd); diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h new file mode 100644 index 000000000..7bd69096a --- /dev/null +++ b/minadbd/minadbd_types.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 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 + +// The message between recovery and minadbd is 8 bytes in size unless the length is explicitly +// specified. Both the command and status has the format |prefix(4 bytes) + encoded enum(4 bytes)|. +constexpr size_t kMinadbdMessageSize = 8; +constexpr char const kMinadbdCommandPrefix[] = "COMD"; +constexpr char const kMinadbdStatusPrefix[] = "STAT"; + +enum MinadbdErrorCode : int { + kMinadbdSuccess = 0, + kMinadbdArgumentsParsingError = 1, + kMinadbdSocketIOError = 2, + kMinadbdMessageFormatError = 3, + kMinadbdAdbVersionError = 4, + kMinadbdPackageSizeError = 5, + kMinadbdFuseStartError = 6, + kMinadbdUnsupportedCommandError = 7, + kMinadbdCommandExecutionError = 8, + kMinadbdErrorUnknown = 9, +}; + +enum class MinadbdCommandStatus : uint32_t { + kSuccess = 0, + kFailure = 1, +}; + +enum class MinadbdCommands : uint32_t { + kInstall = 0, + kUiPrint = 1, + kError = 2, +}; + +static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommands)); +static_assert(kMinadbdMessageSize == + sizeof(kMinadbdStatusPrefix) - 1 + sizeof(MinadbdCommandStatus)); diff --git a/recovery_main.cpp b/recovery_main.cpp index b41368d7b..8b4ad5a32 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -51,7 +51,6 @@ #include "common.h" #include "fastboot/fastboot.h" #include "logging.h" -#include "minadbd/minadbd.h" #include "otautil/paths.h" #include "otautil/roots.h" #include "otautil/sysutil.h" @@ -322,16 +321,6 @@ int main(int argc, char** argv) { // Take action to refresh pmsg contents __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate); - // If this binary is started with the single argument "--adbd", instead of being the normal - // recovery binary, it turns into kind of a stripped-down version of adbd that only supports the - // 'sideload' command. Note this must be a real argument, not anything in the command file or - // bootloader control block; the only way recovery should be run with this argument is when it - // starts a copy of itself from the apply_from_adb() function. - if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { - minadbd_main(); - return 0; - } - time_t start = time(nullptr); // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger -- cgit v1.2.3 From 388d253b9cf4d12ca7879d29dc6711ad44541ccd Mon Sep 17 00:00:00 2001 From: xunchang Date: Fri, 12 Apr 2019 16:22:15 -0700 Subject: DO NOT MERGE: Move wipe cache|data to libinstall Therefore, libinstall becomes the sole owner to handle the request from minadbd service. The change also includes 1. move logging.cpp out of librecovery 2. drop the dependency on common.h 3. now it's more sensible to move the wipe_cache as part of install_package. move the wipe_cache to the end of the function. Bug: 130166585 Test: wipe data and cache from menu Change-Id: I6f356dccdb38015c50acf756bac246f87c30fc1f (cherry picked from commit 316e9717461890dd319dc370970069fe4532a561) --- Android.bp | 3 - install/Android.bp | 1 + install/adb_install.cpp | 9 +- install/fuse_sdcard_install.cpp | 4 +- install/include/install/adb_install.h | 2 +- install/include/install/fuse_sdcard_install.h | 2 +- install/include/install/install.h | 9 +- install/include/install/wipe_data.h | 32 ++++ install/install.cpp | 18 +- install/wipe_data.cpp | 190 ++++++++++++++++++++ logging.cpp | 244 ------------------------- logging.h | 50 ------ otautil/Android.bp | 1 + otautil/include/otautil/logging.h | 51 ++++++ otautil/logging.cpp | 245 ++++++++++++++++++++++++++ recovery-persist.cpp | 2 +- recovery-refresh.cpp | 2 +- recovery.cpp | 217 ++++------------------- recovery_main.cpp | 5 +- 19 files changed, 583 insertions(+), 504 deletions(-) create mode 100644 install/include/install/wipe_data.h create mode 100644 install/wipe_data.cpp delete mode 100644 logging.cpp delete mode 100644 logging.h create mode 100644 otautil/include/otautil/logging.h create mode 100644 otautil/logging.cpp diff --git a/Android.bp b/Android.bp index dd18a89ad..f92078256 100644 --- a/Android.bp +++ b/Android.bp @@ -108,7 +108,6 @@ cc_binary { ], srcs: [ - "logging.cpp", "recovery_main.cpp", ], @@ -140,7 +139,6 @@ cc_binary { ], srcs: [ - "logging.cpp", "recovery-persist.cpp", ], @@ -169,7 +167,6 @@ cc_binary { ], srcs: [ - "logging.cpp", "recovery-refresh.cpp", ], diff --git a/install/Android.bp b/install/Android.bp index ce4244ca4..ea893a075 100644 --- a/install/Android.bp +++ b/install/Android.bp @@ -66,6 +66,7 @@ cc_library_static { "install.cpp", "package.cpp", "verifier.cpp", + "wipe_data.cpp", ], shared_libs: [ diff --git a/install/adb_install.cpp b/install/adb_install.cpp index dc7ee0b32..548b6e5b9 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -81,7 +81,7 @@ static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { } // Installs the package from FUSE. Returns true if the installation succeeds, and false otherwise. -static bool AdbInstallPackageHandler(bool* wipe_cache, RecoveryUI* ui, int* result) { +static bool AdbInstallPackageHandler(RecoveryUI* ui, int* result) { // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME // will start to exist once the host connects and starts serving a package. Poll for its @@ -99,7 +99,7 @@ static bool AdbInstallPackageHandler(bool* wipe_cache, RecoveryUI* ui, int* resu break; } } - *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, false, 0, ui); + *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui); break; } @@ -280,7 +280,7 @@ static void CreateMinadbdServiceAndExecuteCommands( signal(SIGPIPE, SIG_DFL); } -int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { +int apply_from_adb(RecoveryUI* ui) { // Save the usb state to restore after the sideload operation. std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); // Clean up state and stop adbd. @@ -295,8 +295,7 @@ int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { int install_result = INSTALL_ERROR; std::map command_map{ - { MinadbdCommands::kInstall, - std::bind(&AdbInstallPackageHandler, wipe_cache, ui, &install_result) }, + { MinadbdCommands::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, }; CreateMinadbdServiceAndExecuteCommands(command_map); diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_sdcard_install.cpp index dde289f80..1aa8768e7 100644 --- a/install/fuse_sdcard_install.cpp +++ b/install/fuse_sdcard_install.cpp @@ -133,7 +133,7 @@ static bool StartSdcardFuse(const std::string& path) { return run_fuse_sideload(std::move(file_data_reader)) == 0; } -int ApplyFromSdcard(Device* device, bool* wipe_cache, RecoveryUI* ui) { +int ApplyFromSdcard(Device* device, RecoveryUI* ui) { if (ensure_path_mounted(SDCARD_ROOT) != 0) { LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n"; return INSTALL_ERROR; @@ -184,7 +184,7 @@ int ApplyFromSdcard(Device* device, bool* wipe_cache, RecoveryUI* ui) { } } - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, false, 0 /*retry_count*/, ui); + result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui); break; } diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h index dbc824501..f7b065b64 100644 --- a/install/include/install/adb_install.h +++ b/install/include/install/adb_install.h @@ -18,4 +18,4 @@ #include -int apply_from_adb(bool* wipe_cache, RecoveryUI* ui); +int apply_from_adb(RecoveryUI* ui); diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/fuse_sdcard_install.h index 345aea45b..d9214ca3b 100644 --- a/install/include/install/fuse_sdcard_install.h +++ b/install/include/install/fuse_sdcard_install.h @@ -19,4 +19,4 @@ #include "recovery_ui/device.h" #include "recovery_ui/ui.h" -int ApplyFromSdcard(Device* device, bool* wipe_cache, RecoveryUI* ui); +int ApplyFromSdcard(Device* device, RecoveryUI* ui); diff --git a/install/include/install/install.h b/install/include/install/install.h index 74fb3d170..1e41b4843 100644 --- a/install/include/install/install.h +++ b/install/include/install/install.h @@ -43,10 +43,11 @@ enum class OtaType { BRICK, }; -// Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on -// exit, caller should wipe the cache partition. -int install_package(const std::string& package, bool* wipe_cache, bool needs_mount, int retry_count, - RecoveryUI* ui); +// Installs the given update package. This function should also wipe the cache partition after a +// successful installation if |should_wipe_cache| is true or an updater command asks to wipe the +// cache. +int install_package(const std::string& package, bool should_wipe_cache, bool needs_mount, + int retry_count, RecoveryUI* ui); // Verifies the package by ota keys. Returns true if the package is verified successfully, // otherwise returns false. diff --git a/install/include/install/wipe_data.h b/install/include/install/wipe_data.h new file mode 100644 index 000000000..06b1b95d4 --- /dev/null +++ b/install/include/install/wipe_data.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 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 + +#include "recovery_ui/device.h" +#include "recovery_ui/ui.h" + +struct selabel_handle; + +void SetWipeDataSehandle(selabel_handle* handle); + +// Returns true on success. +bool WipeCache(RecoveryUI* ui, const std::function& confirm); + +// Returns true on success. +bool WipeData(Device* device, bool convert_fbe); diff --git a/install/install.cpp b/install/install.cpp index a1124c361..e2d470096 100644 --- a/install/install.cpp +++ b/install/install.cpp @@ -48,6 +48,7 @@ #include "install/package.h" #include "install/verifier.h" +#include "install/wipe_data.h" #include "otautil/error_code.h" #include "otautil/paths.h" #include "otautil/roots.h" @@ -631,10 +632,9 @@ static int really_install_package(const std::string& path, bool* wipe_cache, boo return result; } -int install_package(const std::string& path, bool* wipe_cache, bool needs_mount, int retry_count, - RecoveryUI* ui) { +int install_package(const std::string& path, bool should_wipe_cache, bool needs_mount, + int retry_count, RecoveryUI* ui) { CHECK(!path.empty()); - CHECK(wipe_cache != nullptr); auto start = std::chrono::system_clock::now(); @@ -647,8 +647,10 @@ int install_package(const std::string& path, bool* wipe_cache, bool needs_mount, LOG(ERROR) << "failed to set up expected mounts for install; aborting"; result = INSTALL_ERROR; } else { - result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count, - &max_temperature, ui); + bool updater_wipe_cache = false; + result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer, + retry_count, &max_temperature, ui); + should_wipe_cache = should_wipe_cache || updater_wipe_cache; } // Measure the time spent to apply OTA update in seconds. @@ -703,6 +705,12 @@ int install_package(const std::string& path, bool* wipe_cache, bool needs_mount, // Write a copy into last_log. LOG(INFO) << log_content; + if (result == INSTALL_SUCCESS && should_wipe_cache) { + if (!WipeCache(ui, nullptr)) { + result = INSTALL_ERROR; + } + } + return result; } diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp new file mode 100644 index 000000000..7db79048a --- /dev/null +++ b/install/wipe_data.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019 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 "install/wipe_data.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "otautil/dirutil.h" +#include "otautil/logging.h" +#include "otautil/roots.h" +#include "recovery_ui/ui.h" + +constexpr const char* CACHE_ROOT = "/cache"; +constexpr const char* DATA_ROOT = "/data"; +constexpr const char* METADATA_ROOT = "/metadata"; + +constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; + +static struct selabel_handle* sehandle; + +void SetWipeDataSehandle(selabel_handle* handle) { + sehandle = handle; +} + +struct saved_log_file { + std::string name; + struct stat sb; + std::string data; +}; + +static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) { + bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); + bool is_data = (strcmp(volume, DATA_ROOT) == 0); + + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); + + std::vector log_files; + + if (is_cache) { + // If we're reformatting /cache, we load any past logs + // (i.e. "/cache/recovery/last_*") and the current log + // ("/cache/recovery/log") into memory, so we can restore them after + // the reformat. + + ensure_path_mounted(volume); + + struct dirent* de; + std::unique_ptr d(opendir(CACHE_LOG_DIR), closedir); + if (d) { + while ((de = readdir(d.get())) != nullptr) { + if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { + std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); + + struct stat sb; + if (stat(path.c_str(), &sb) == 0) { + // truncate files to 512kb + if (sb.st_size > (1 << 19)) { + sb.st_size = 1 << 19; + } + + std::string data(sb.st_size, '\0'); + FILE* f = fopen(path.c_str(), "rbe"); + fread(&data[0], 1, data.size(), f); + fclose(f); + + log_files.emplace_back(saved_log_file{ path, sb, data }); + } + } + } + } else { + if (errno != ENOENT) { + PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; + } + } + } + + ui->Print("Formatting %s...\n", volume); + + ensure_path_unmounted(volume); + + int result; + if (is_data && convert_fbe) { + constexpr const char* CONVERT_FBE_DIR = "/tmp/convert_fbe"; + constexpr const char* CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; + // Create convert_fbe breadcrumb file to signal init to convert to file based encryption, not + // full disk encryption. + if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { + PLOG(ERROR) << "Failed to mkdir " << CONVERT_FBE_DIR; + return false; + } + FILE* f = fopen(CONVERT_FBE_FILE, "wbe"); + if (!f) { + PLOG(ERROR) << "Failed to convert to file encryption"; + return false; + } + fclose(f); + result = format_volume(volume, CONVERT_FBE_DIR); + remove(CONVERT_FBE_FILE); + rmdir(CONVERT_FBE_DIR); + } else { + result = format_volume(volume); + } + + if (is_cache) { + // Re-create the log dir and write back the log entries. + if (ensure_path_mounted(CACHE_LOG_DIR) == 0 && + mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle) == 0) { + for (const auto& log : log_files) { + if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, + log.sb.st_gid)) { + PLOG(ERROR) << "Failed to write to " << log.name; + } + } + } else { + PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR; + } + + // Any part of the log we'd copied to cache is now gone. + // Reset the pointer so we copy from the beginning of the temp + // log. + reset_tmplog_offset(); + copy_logs(true /* save_current_log */, true /* has_cache */, sehandle); + } + + return (result == 0); +} + +bool WipeCache(RecoveryUI* ui, const std::function& confirm_func) { + bool has_cache = volume_for_mount_point("/cache") != nullptr; + if (!has_cache) { + ui->Print("No /cache partition found.\n"); + return false; + } + + if (confirm_func && !confirm_func()) { + return false; + } + + ui->Print("\n-- Wiping cache...\n"); + bool success = EraseVolume("/cache", ui, false); + ui->Print("Cache wipe %s.\n", success ? "complete" : "failed"); + return success; +} + +bool WipeData(Device* device, bool convert_fbe) { + RecoveryUI* ui = device->GetUI(); + ui->Print("\n-- Wiping data...\n"); + bool success = device->PreWipeData(); + if (success) { + success &= EraseVolume(DATA_ROOT, ui, convert_fbe); + bool has_cache = volume_for_mount_point("/cache") != nullptr; + if (has_cache) { + success &= EraseVolume(CACHE_ROOT, ui, false); + } + if (volume_for_mount_point(METADATA_ROOT) != nullptr) { + success &= EraseVolume(METADATA_ROOT, ui, false); + } + } + if (success) { + success &= device->PostWipeData(); + } + ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); + return success; +} \ No newline at end of file diff --git a/logging.cpp b/logging.cpp deleted file mode 100644 index 48f9ec317..000000000 --- a/logging.cpp +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2016 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 "logging.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include /* for AID_SYSTEM */ -#include /* private pmsg functions */ - -#include "common.h" -#include "otautil/dirutil.h" -#include "otautil/paths.h" -#include "otautil/roots.h" - -static constexpr const char* LOG_FILE = "/cache/recovery/log"; -static constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; -static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; -static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; - -static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg"; -static const std::string LAST_LOG_FILTER = "recovery/last_log"; - -// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the -// file pointer, or nullptr on error. -static FILE* fopen_path(const std::string& path, const char* mode) { - if (ensure_path_mounted(path) != 0) { - LOG(ERROR) << "Can't mount " << path; - return nullptr; - } - - // When writing, try to create the containing directory, if necessary. Use generous permissions, - // the system (init.rc) will reset them. - if (strchr("wa", mode[0])) { - mkdir_recursively(path, 0777, true, sehandle); - } - return fopen(path.c_str(), mode); -} - -void check_and_fclose(FILE* fp, const std::string& name) { - fflush(fp); - if (fsync(fileno(fp)) == -1) { - PLOG(ERROR) << "Failed to fsync " << name; - } - if (ferror(fp)) { - PLOG(ERROR) << "Error in " << name; - } - fclose(fp); -} - -// close a file, log an error if the error indicator is set -ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */, - size_t len, void* arg) { - bool* do_rotate = static_cast(arg); - if (LAST_KMSG_FILTER.find(filename) != std::string::npos || - LAST_LOG_FILTER.find(filename) != std::string::npos) { - *do_rotate = true; - } - return len; -} - -ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, - void* arg) { - bool* do_rotate = static_cast(arg); - if (!*do_rotate) { - return __android_log_pmsg_file_write(id, prio, filename, buf, len); - } - - std::string name(filename); - size_t dot = name.find_last_of('.'); - std::string sub = name.substr(0, dot); - - if (LAST_KMSG_FILTER.find(sub) == std::string::npos && - LAST_LOG_FILTER.find(sub) == std::string::npos) { - return __android_log_pmsg_file_write(id, prio, filename, buf, len); - } - - // filename rotation - if (dot == std::string::npos) { - name += ".1"; - } else { - std::string number = name.substr(dot + 1); - if (!isdigit(number[0])) { - name += ".1"; - } else { - size_t i; - if (!android::base::ParseUint(number, &i)) { - LOG(ERROR) << "failed to parse uint in " << number; - return -1; - } - name = sub + "." + std::to_string(i + 1); - } - } - - return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len); -} - -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -void rotate_logs(const char* last_log_file, const char* last_kmsg_file) { - // Logs should only be rotated once. - static bool rotated = false; - if (rotated) { - return; - } - rotated = true; - - for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) { - std::string old_log = android::base::StringPrintf("%s", last_log_file); - if (i > 0) { - old_log += "." + std::to_string(i); - } - std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1); - // Ignore errors if old_log doesn't exist. - rename(old_log.c_str(), new_log.c_str()); - - std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file); - if (i > 0) { - old_kmsg += "." + std::to_string(i); - } - std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1); - rename(old_kmsg.c_str(), new_kmsg.c_str()); - } -} - -// Writes content to the current pmsg session. -static ssize_t __pmsg_write(const std::string& filename, const std::string& buf) { - return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filename.c_str(), - buf.data(), buf.size()); -} - -void copy_log_file_to_pmsg(const std::string& source, const std::string& destination) { - std::string content; - android::base::ReadFileToString(source, &content); - __pmsg_write(destination, content); -} - -// How much of the temp log we have copied to the copy in cache. -static off_t tmplog_offset = 0; - -void reset_tmplog_offset() { - tmplog_offset = 0; -} - -void copy_log_file(const std::string& source, const std::string& destination, bool append) { - FILE* dest_fp = fopen_path(destination, append ? "ae" : "we"); - if (dest_fp == nullptr) { - PLOG(ERROR) << "Can't open " << destination; - } else { - FILE* source_fp = fopen(source.c_str(), "re"); - if (source_fp != nullptr) { - if (append) { - fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write - } - char buf[4096]; - size_t bytes; - while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { - fwrite(buf, 1, bytes, dest_fp); - } - if (append) { - tmplog_offset = ftello(source_fp); - } - check_and_fclose(source_fp, source); - } - check_and_fclose(dest_fp, destination); - } -} - -void copy_logs(bool modified_flash, bool has_cache) { - // We only rotate and record the log of the current session if there are actual attempts to modify - // the flash, such as wipes, installs from BCB or menu selections. This is to avoid unnecessary - // rotation (and possible deletion) of log files, if it does not do anything loggable. - if (!modified_flash) { - return; - } - - // Always write to pmsg, this allows the OTA logs to be caught in `logcat -L`. - copy_log_file_to_pmsg(Paths::Get().temporary_log_file(), LAST_LOG_FILE); - copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE); - - // We can do nothing for now if there's no /cache partition. - if (!has_cache) { - return; - } - - ensure_path_mounted(LAST_LOG_FILE); - ensure_path_mounted(LAST_KMSG_FILE); - rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); - - // Copy logs to cache so the system can find out what happened. - copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true); - copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false); - copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false); - save_kernel_log(LAST_KMSG_FILE); - chmod(LOG_FILE, 0600); - chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); - chmod(LAST_KMSG_FILE, 0600); - chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); - chmod(LAST_LOG_FILE, 0640); - chmod(LAST_INSTALL_FILE, 0644); - chown(LAST_INSTALL_FILE, AID_SYSTEM, AID_SYSTEM); - sync(); -} - -// Read from kernel log into buffer and write out to file. -void save_kernel_log(const char* destination) { - int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); - if (klog_buf_len <= 0) { - PLOG(ERROR) << "Error getting klog size"; - return; - } - - std::string buffer(klog_buf_len, 0); - int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); - if (n == -1) { - PLOG(ERROR) << "Error in reading klog"; - return; - } - buffer.resize(n); - android::base::WriteStringToFile(buffer, destination); -} diff --git a/logging.h b/logging.h deleted file mode 100644 index 3cfbc7af6..000000000 --- a/logging.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2016 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 _LOGGING_H -#define _LOGGING_H - -#include -#include - -#include - -#include - -static constexpr int KEEP_LOG_COUNT = 10; - -ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len, - void* arg); - -ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, - void* arg); - -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -void rotate_logs(const char* last_log_file, const char* last_kmsg_file); - -// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream. -void check_and_fclose(FILE* fp, const std::string& name); - -void copy_log_file_to_pmsg(const std::string& source, const std::string& destination); -void copy_log_file(const std::string& source, const std::string& destination, bool append); -void copy_logs(bool modified_flash, bool has_cache); -void reset_tmplog_offset(); - -void save_kernel_log(const char* destination); - -#endif //_LOGGING_H diff --git a/otautil/Android.bp b/otautil/Android.bp index b4936c08b..0a21731e8 100644 --- a/otautil/Android.bp +++ b/otautil/Android.bp @@ -40,6 +40,7 @@ cc_library_static { android: { srcs: [ "dirutil.cpp", + "logging.cpp", "mounts.cpp", "parse_install_logs.cpp", "roots.cpp", diff --git a/otautil/include/otautil/logging.h b/otautil/include/otautil/logging.h new file mode 100644 index 000000000..c4f13292b --- /dev/null +++ b/otautil/include/otautil/logging.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 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 _LOGGING_H +#define _LOGGING_H + +#include +#include + +#include + +#include + +static constexpr int KEEP_LOG_COUNT = 10; + +struct selabel_handle; + +ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len, + void* arg); + +ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, + void* arg); + +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. +// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. +// Overwrite any existing last_log.$max and last_kmsg.$max. +void rotate_logs(const char* last_log_file, const char* last_kmsg_file); + +// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream. +void check_and_fclose(FILE* fp, const std::string& name); + +void copy_log_file_to_pmsg(const std::string& source, const std::string& destination); +void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle); +void reset_tmplog_offset(); + +void save_kernel_log(const char* destination); + +#endif //_LOGGING_H diff --git a/otautil/logging.cpp b/otautil/logging.cpp new file mode 100644 index 000000000..7c330ac61 --- /dev/null +++ b/otautil/logging.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2016 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 "otautil/logging.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include /* for AID_SYSTEM */ +#include /* private pmsg functions */ +#include + +#include "otautil/dirutil.h" +#include "otautil/paths.h" +#include "otautil/roots.h" + +static constexpr const char* LOG_FILE = "/cache/recovery/log"; +static constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; +static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; +static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; + +static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg"; +static const std::string LAST_LOG_FILTER = "recovery/last_log"; + +// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the +// file pointer, or nullptr on error. +static FILE* fopen_path(const std::string& path, const char* mode, const selabel_handle* sehandle) { + if (ensure_path_mounted(path) != 0) { + LOG(ERROR) << "Can't mount " << path; + return nullptr; + } + + // When writing, try to create the containing directory, if necessary. Use generous permissions, + // the system (init.rc) will reset them. + if (strchr("wa", mode[0])) { + mkdir_recursively(path, 0777, true, sehandle); + } + return fopen(path.c_str(), mode); +} + +void check_and_fclose(FILE* fp, const std::string& name) { + fflush(fp); + if (fsync(fileno(fp)) == -1) { + PLOG(ERROR) << "Failed to fsync " << name; + } + if (ferror(fp)) { + PLOG(ERROR) << "Error in " << name; + } + fclose(fp); +} + +// close a file, log an error if the error indicator is set +ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */, + size_t len, void* arg) { + bool* do_rotate = static_cast(arg); + if (LAST_KMSG_FILTER.find(filename) != std::string::npos || + LAST_LOG_FILTER.find(filename) != std::string::npos) { + *do_rotate = true; + } + return len; +} + +ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len, + void* arg) { + bool* do_rotate = static_cast(arg); + if (!*do_rotate) { + return __android_log_pmsg_file_write(id, prio, filename, buf, len); + } + + std::string name(filename); + size_t dot = name.find_last_of('.'); + std::string sub = name.substr(0, dot); + + if (LAST_KMSG_FILTER.find(sub) == std::string::npos && + LAST_LOG_FILTER.find(sub) == std::string::npos) { + return __android_log_pmsg_file_write(id, prio, filename, buf, len); + } + + // filename rotation + if (dot == std::string::npos) { + name += ".1"; + } else { + std::string number = name.substr(dot + 1); + if (!isdigit(number[0])) { + name += ".1"; + } else { + size_t i; + if (!android::base::ParseUint(number, &i)) { + LOG(ERROR) << "failed to parse uint in " << number; + return -1; + } + name = sub + "." + std::to_string(i + 1); + } + } + + return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len); +} + +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. +// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. +// Overwrite any existing last_log.$max and last_kmsg.$max. +void rotate_logs(const char* last_log_file, const char* last_kmsg_file) { + // Logs should only be rotated once. + static bool rotated = false; + if (rotated) { + return; + } + rotated = true; + + for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) { + std::string old_log = android::base::StringPrintf("%s", last_log_file); + if (i > 0) { + old_log += "." + std::to_string(i); + } + std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1); + // Ignore errors if old_log doesn't exist. + rename(old_log.c_str(), new_log.c_str()); + + std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file); + if (i > 0) { + old_kmsg += "." + std::to_string(i); + } + std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1); + rename(old_kmsg.c_str(), new_kmsg.c_str()); + } +} + +// Writes content to the current pmsg session. +static ssize_t __pmsg_write(const std::string& filename, const std::string& buf) { + return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filename.c_str(), + buf.data(), buf.size()); +} + +void copy_log_file_to_pmsg(const std::string& source, const std::string& destination) { + std::string content; + android::base::ReadFileToString(source, &content); + __pmsg_write(destination, content); +} + +// How much of the temp log we have copied to the copy in cache. +static off_t tmplog_offset = 0; + +void reset_tmplog_offset() { + tmplog_offset = 0; +} + +static void copy_log_file(const std::string& source, const std::string& destination, bool append, + const selabel_handle* sehandle) { + FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", sehandle); + if (dest_fp == nullptr) { + PLOG(ERROR) << "Can't open " << destination; + } else { + FILE* source_fp = fopen(source.c_str(), "re"); + if (source_fp != nullptr) { + if (append) { + fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write + } + char buf[4096]; + size_t bytes; + while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) { + fwrite(buf, 1, bytes, dest_fp); + } + if (append) { + tmplog_offset = ftello(source_fp); + } + check_and_fclose(source_fp, source); + } + check_and_fclose(dest_fp, destination); + } +} + +void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle) { + // We only rotate and record the log of the current session if explicitly requested. This usually + // happens after wipes, installation from BCB or menu selections. This is to avoid unnecessary + // rotation (and possible deletion) of log files, if it does not do anything loggable. + if (!save_current_log) { + return; + } + + // Always write to pmsg, this allows the OTA logs to be caught in `logcat -L`. + copy_log_file_to_pmsg(Paths::Get().temporary_log_file(), LAST_LOG_FILE); + copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE); + + // We can do nothing for now if there's no /cache partition. + if (!has_cache) { + return; + } + + ensure_path_mounted(LAST_LOG_FILE); + ensure_path_mounted(LAST_KMSG_FILE); + rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); + + // Copy logs to cache so the system can find out what happened. + copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true, sehandle); + copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false, sehandle); + copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false, sehandle); + save_kernel_log(LAST_KMSG_FILE); + chmod(LOG_FILE, 0600); + chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); + chmod(LAST_KMSG_FILE, 0600); + chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); + chmod(LAST_LOG_FILE, 0640); + chmod(LAST_INSTALL_FILE, 0644); + chown(LAST_INSTALL_FILE, AID_SYSTEM, AID_SYSTEM); + sync(); +} + +// Read from kernel log into buffer and write out to file. +void save_kernel_log(const char* destination) { + int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); + if (klog_buf_len <= 0) { + PLOG(ERROR) << "Error getting klog size"; + return; + } + + std::string buffer(klog_buf_len, 0); + int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); + if (n == -1) { + PLOG(ERROR) << "Error in reading klog"; + return; + } + buffer.resize(n); + android::base::WriteStringToFile(buffer, destination); +} diff --git a/recovery-persist.cpp b/recovery-persist.cpp index e2a6699f6..294017a12 100644 --- a/recovery-persist.cpp +++ b/recovery-persist.cpp @@ -43,7 +43,7 @@ #include #include /* private pmsg functions */ -#include "logging.h" +#include "otautil/logging.h" #include "otautil/parse_install_logs.h" constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log"; diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp index aee1ca592..d41755d0a 100644 --- a/recovery-refresh.cpp +++ b/recovery-refresh.cpp @@ -42,7 +42,7 @@ #include /* private pmsg functions */ -#include "logging.h" +#include "otautil/logging.h" int main(int argc, char **argv) { static const char filter[] = "recovery/"; diff --git a/recovery.cpp b/recovery.cpp index 034918498..0e6e4976d 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -55,30 +54,27 @@ #include "install/fuse_sdcard_install.h" #include "install/install.h" #include "install/package.h" -#include "logging.h" -#include "otautil/dirutil.h" +#include "install/wipe_data.h" #include "otautil/error_code.h" +#include "otautil/logging.h" #include "otautil/paths.h" #include "otautil/roots.h" #include "otautil/sysutil.h" #include "recovery_ui/screen_ui.h" #include "recovery_ui/ui.h" -static constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; static constexpr const char* CACHE_ROOT = "/cache"; -static constexpr const char* DATA_ROOT = "/data"; -static constexpr const char* METADATA_ROOT = "/metadata"; // We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed // 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 bool modified_flash = false; +static bool save_current_log = false; std::string stage; const char* reason = nullptr; @@ -148,7 +144,7 @@ static void finish_recovery() { } } - copy_logs(modified_flash, has_cache); + copy_logs(save_current_log, has_cache, sehandle); // Reset to normal system boot so recovery won't cycle indefinitely. std::string err; @@ -167,110 +163,6 @@ static void finish_recovery() { sync(); // For good measure. } -struct saved_log_file { - std::string name; - struct stat sb; - std::string data; -}; - -static bool erase_volume(const char* volume) { - bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); - bool is_data = (strcmp(volume, DATA_ROOT) == 0); - - ui->SetBackground(RecoveryUI::ERASING); - ui->SetProgressType(RecoveryUI::INDETERMINATE); - - std::vector log_files; - - if (is_cache) { - // If we're reformatting /cache, we load any past logs - // (i.e. "/cache/recovery/last_*") and the current log - // ("/cache/recovery/log") into memory, so we can restore them after - // the reformat. - - ensure_path_mounted(volume); - - struct dirent* de; - std::unique_ptr d(opendir(CACHE_LOG_DIR), closedir); - if (d) { - while ((de = readdir(d.get())) != nullptr) { - if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { - std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); - - struct stat sb; - if (stat(path.c_str(), &sb) == 0) { - // truncate files to 512kb - if (sb.st_size > (1 << 19)) { - sb.st_size = 1 << 19; - } - - std::string data(sb.st_size, '\0'); - FILE* f = fopen(path.c_str(), "rbe"); - fread(&data[0], 1, data.size(), f); - fclose(f); - - log_files.emplace_back(saved_log_file{ path, sb, data }); - } - } - } - } else { - if (errno != ENOENT) { - PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; - } - } - } - - ui->Print("Formatting %s...\n", volume); - - ensure_path_unmounted(volume); - - int result; - if (is_data && reason && strcmp(reason, "convert_fbe") == 0) { - static constexpr const char* CONVERT_FBE_DIR = "/tmp/convert_fbe"; - static constexpr const char* CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; - // Create convert_fbe breadcrumb file to signal init to convert to file based encryption, not - // full disk encryption. - if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { - PLOG(ERROR) << "Failed to mkdir " << CONVERT_FBE_DIR; - return false; - } - FILE* f = fopen(CONVERT_FBE_FILE, "wbe"); - if (!f) { - PLOG(ERROR) << "Failed to convert to file encryption"; - return false; - } - fclose(f); - result = format_volume(volume, CONVERT_FBE_DIR); - remove(CONVERT_FBE_FILE); - rmdir(CONVERT_FBE_DIR); - } else { - result = format_volume(volume); - } - - if (is_cache) { - // Re-create the log dir and write back the log entries. - if (ensure_path_mounted(CACHE_LOG_DIR) == 0 && - mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle) == 0) { - for (const auto& log : log_files) { - if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, - log.sb.st_gid)) { - PLOG(ERROR) << "Failed to write to " << log.name; - } - } - } else { - PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR; - } - - // Any part of the log we'd copied to cache is now gone. - // Reset the pointer so we copy from the beginning of the temp - // log. - reset_tmplog_offset(); - copy_logs(modified_flash, has_cache); - } - - return (result == 0); -} - static bool yes_no(Device* device, const char* question1, const char* question2) { std::vector headers{ question1, question2 }; std::vector items{ " No", " Yes" }; @@ -292,28 +184,6 @@ static bool ask_to_wipe_data(Device* device) { return (chosen_item == 1); } -// Return true on success. -static bool wipe_data(Device* device) { - modified_flash = true; - - ui->Print("\n-- Wiping data...\n"); - bool success = device->PreWipeData(); - if (success) { - success &= erase_volume(DATA_ROOT); - if (has_cache) { - success &= erase_volume(CACHE_ROOT); - } - if (volume_for_mount_point(METADATA_ROOT) != nullptr) { - success &= erase_volume(METADATA_ROOT); - } - } - if (success) { - success &= device->PostWipeData(); - } - ui->Print("Data wipe %s.\n", success ? "complete" : "failed"); - return success; -} - static InstallResult prompt_and_wipe_data(Device* device) { // Use a single string and let ScreenRecoveryUI handles the wrapping. std::vector wipe_data_menu_headers{ @@ -341,7 +211,8 @@ static InstallResult prompt_and_wipe_data(Device* device) { } if (ask_to_wipe_data(device)) { - if (wipe_data(device)) { + bool convert_fbe = reason && strcmp(reason, "convert_fbe") == 0; + if (WipeData(device, convert_fbe)) { return INSTALL_SUCCESS; } else { return INSTALL_ERROR; @@ -350,25 +221,6 @@ static InstallResult prompt_and_wipe_data(Device* device) { } } -// Return true on success. -static bool wipe_cache(bool should_confirm, Device* device) { - if (!has_cache) { - ui->Print("No /cache partition found.\n"); - return false; - } - - if (should_confirm && !yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!")) { - return false; - } - - modified_flash = true; - - ui->Print("\n-- Wiping cache...\n"); - bool success = erase_volume("/cache"); - ui->Print("Cache wipe %s.\n", success ? "complete" : "failed"); - return success; -} - // Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with // BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT. static bool secure_wipe_partition(const std::string& partition) { @@ -653,7 +505,6 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { ? Device::REBOOT : device->InvokeMenuItem(chosen_item); - bool should_wipe_cache = false; switch (chosen_action) { case Device::NO_ACTION: break; @@ -666,41 +517,40 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { return chosen_action; case Device::WIPE_DATA: + save_current_log = true; if (ui->IsTextVisible()) { if (ask_to_wipe_data(device)) { - wipe_data(device); + WipeData(device, false); } } else { - wipe_data(device); + WipeData(device, false); return Device::NO_ACTION; } break; - case Device::WIPE_CACHE: - wipe_cache(ui->IsTextVisible(), device); + case Device::WIPE_CACHE: { + save_current_log = true; + std::function confirm_func = [&device]() { + return yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!"); + }; + WipeCache(ui, ui->IsTextVisible() ? confirm_func : nullptr); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; - + } case Device::APPLY_ADB_SIDELOAD: case Device::APPLY_SDCARD: { - modified_flash = true; + save_current_log = true; bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); if (adb) { - status = apply_from_adb(&should_wipe_cache, ui); + status = apply_from_adb(ui); } else { - status = ApplyFromSdcard(device, &should_wipe_cache, ui); - } - - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; - } + status = ApplyFromSdcard(device, ui); } if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); - copy_logs(modified_flash, has_cache); + copy_logs(save_current_log, has_cache, sehandle); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { @@ -987,7 +837,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorPrint("Installation aborted.\n"); @@ -1021,7 +867,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorShowText(true); ui->SetBackground(RecoveryUI::ERROR); @@ -1059,7 +907,8 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorShowText(false); } } else if (should_wipe_cache) { - if (!wipe_cache(false, device)) { + save_current_log = true; + if (!WipeCache(ui, nullptr)) { status = INSTALL_ERROR; } } else if (should_wipe_ab) { @@ -1073,15 +922,11 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorShowText(true); } - status = apply_from_adb(&should_wipe_cache, ui); - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; - } - } + status = apply_from_adb(ui); ui->Print("\nInstall from ADB complete (status: %d).\n", status); if (sideload_auto_reboot) { ui->Print("Rebooting automatically.\n"); diff --git a/recovery_main.cpp b/recovery_main.cpp index 8b4ad5a32..8f3f2a781 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -50,7 +50,8 @@ #include "common.h" #include "fastboot/fastboot.h" -#include "logging.h" +#include "install/wipe_data.h" +#include "otautil/logging.h" #include "otautil/paths.h" #include "otautil/roots.h" #include "otautil/sysutil.h" @@ -434,6 +435,8 @@ int main(int argc, char** argv) { ui->Print("Warning: No file_contexts\n"); } + SetWipeDataSehandle(sehandle); + std::atomic action; std::thread listener_thread(ListenRecoverySocket, ui, std::ref(action)); listener_thread.detach(); -- cgit v1.2.3 From cd780b456f2fcb108ccbe42d83cb8b03fb097513 Mon Sep 17 00:00:00 2001 From: xunchang Date: Mon, 15 Apr 2019 15:24:24 -0700 Subject: DO NOT MERGE: Move load & restore logs to logging.cpp We perform these steps to perserve the recovery logs when wiping /cache partition. Move them to logging.cpp to keep the actually EraseVolume function concise. Bug: 130166585 Test: unit tests pass, mount cache and check last log after cache Change-Id: Idc52833817a446f3a0148a3dd2112f911c9ef48d (cherry picked from commit 2239b9e4dd08e307ad74dc44b597fd53d2d17de8) --- install/include/install/wipe_data.h | 2 - install/wipe_data.cpp | 75 ++------------------------- otautil/include/otautil/logging.h | 14 +++++ otautil/logging.cpp | 100 ++++++++++++++++++++++++++++++++---- recovery_main.cpp | 2 +- 5 files changed, 109 insertions(+), 84 deletions(-) diff --git a/install/include/install/wipe_data.h b/install/include/install/wipe_data.h index 06b1b95d4..b34891f3d 100644 --- a/install/include/install/wipe_data.h +++ b/install/include/install/wipe_data.h @@ -23,8 +23,6 @@ struct selabel_handle; -void SetWipeDataSehandle(selabel_handle* handle); - // Returns true on success. bool WipeCache(RecoveryUI* ui, const std::function& confirm); diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp index 7db79048a..765a8152b 100644 --- a/install/wipe_data.cpp +++ b/install/wipe_data.cpp @@ -16,14 +16,11 @@ #include "install/wipe_data.h" -#include -#include #include #include #include #include -#include #include #include @@ -39,20 +36,6 @@ constexpr const char* CACHE_ROOT = "/cache"; constexpr const char* DATA_ROOT = "/data"; constexpr const char* METADATA_ROOT = "/metadata"; -constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; - -static struct selabel_handle* sehandle; - -void SetWipeDataSehandle(selabel_handle* handle) { - sehandle = handle; -} - -struct saved_log_file { - std::string name; - struct stat sb; - std::string data; -}; - static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) { bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); bool is_data = (strcmp(volume, DATA_ROOT) == 0); @@ -61,43 +44,10 @@ static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) { ui->SetProgressType(RecoveryUI::INDETERMINATE); std::vector log_files; - if (is_cache) { - // If we're reformatting /cache, we load any past logs - // (i.e. "/cache/recovery/last_*") and the current log - // ("/cache/recovery/log") into memory, so we can restore them after - // the reformat. - - ensure_path_mounted(volume); - - struct dirent* de; - std::unique_ptr d(opendir(CACHE_LOG_DIR), closedir); - if (d) { - while ((de = readdir(d.get())) != nullptr) { - if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { - std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); - - struct stat sb; - if (stat(path.c_str(), &sb) == 0) { - // truncate files to 512kb - if (sb.st_size > (1 << 19)) { - sb.st_size = 1 << 19; - } - - std::string data(sb.st_size, '\0'); - FILE* f = fopen(path.c_str(), "rbe"); - fread(&data[0], 1, data.size(), f); - fclose(f); - - log_files.emplace_back(saved_log_file{ path, sb, data }); - } - } - } - } else { - if (errno != ENOENT) { - PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; - } - } + // If we're reformatting /cache, we load any past logs (i.e. "/cache/recovery/last_*") and the + // current log ("/cache/recovery/log") into memory, so we can restore them after the reformat. + log_files = ReadLogFilesToMemory(); } ui->Print("Formatting %s...\n", volume); @@ -128,24 +78,7 @@ static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) { } if (is_cache) { - // Re-create the log dir and write back the log entries. - if (ensure_path_mounted(CACHE_LOG_DIR) == 0 && - mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle) == 0) { - for (const auto& log : log_files) { - if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, - log.sb.st_gid)) { - PLOG(ERROR) << "Failed to write to " << log.name; - } - } - } else { - PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR; - } - - // Any part of the log we'd copied to cache is now gone. - // Reset the pointer so we copy from the beginning of the temp - // log. - reset_tmplog_offset(); - copy_logs(true /* save_current_log */, true /* has_cache */, sehandle); + RestoreLogFilesAfterFormat(log_files); } return (result == 0); diff --git a/otautil/include/otautil/logging.h b/otautil/include/otautil/logging.h index c4f13292b..608349785 100644 --- a/otautil/include/otautil/logging.h +++ b/otautil/include/otautil/logging.h @@ -18,9 +18,11 @@ #define _LOGGING_H #include +#include #include #include +#include #include @@ -28,6 +30,14 @@ static constexpr int KEEP_LOG_COUNT = 10; struct selabel_handle; +struct saved_log_file { + std::string name; + struct stat sb; + std::string data; +}; + +void SetLoggingSehandle(selabel_handle* handle); + ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len, void* arg); @@ -48,4 +58,8 @@ void reset_tmplog_offset(); void save_kernel_log(const char* destination); +std::vector ReadLogFilesToMemory(); + +bool RestoreLogFilesAfterFormat(const std::vector& log_files); + #endif //_LOGGING_H diff --git a/otautil/logging.cpp b/otautil/logging.cpp index 7c330ac61..484f1150f 100644 --- a/otautil/logging.cpp +++ b/otautil/logging.cpp @@ -16,17 +16,22 @@ #include "otautil/logging.h" +#include +#include #include #include #include #include +#include +#include #include #include #include #include #include +#include #include /* for AID_SYSTEM */ #include /* private pmsg functions */ #include @@ -35,13 +40,21 @@ #include "otautil/paths.h" #include "otautil/roots.h" -static constexpr const char* LOG_FILE = "/cache/recovery/log"; -static constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; -static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; -static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; +constexpr const char* LOG_FILE = "/cache/recovery/log"; +constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install"; +constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; +constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log"; -static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg"; -static const std::string LAST_LOG_FILTER = "recovery/last_log"; +constexpr const char* LAST_KMSG_FILTER = "recovery/last_kmsg"; +constexpr const char* LAST_LOG_FILTER = "recovery/last_log"; + +constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; + +static struct selabel_handle* logging_sehandle; + +void SetLoggingSehandle(selabel_handle* handle) { + logging_sehandle = handle; +} // fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the // file pointer, or nullptr on error. @@ -74,8 +87,8 @@ void check_and_fclose(FILE* fp, const std::string& name) { ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */, size_t len, void* arg) { bool* do_rotate = static_cast(arg); - if (LAST_KMSG_FILTER.find(filename) != std::string::npos || - LAST_LOG_FILTER.find(filename) != std::string::npos) { + if (std::string(LAST_KMSG_FILTER).find(filename) != std::string::npos || + std::string(LAST_LOG_FILTER).find(filename) != std::string::npos) { *do_rotate = true; } return len; @@ -92,8 +105,8 @@ ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t dot = name.find_last_of('.'); std::string sub = name.substr(0, dot); - if (LAST_KMSG_FILTER.find(sub) == std::string::npos && - LAST_LOG_FILTER.find(sub) == std::string::npos) { + if (std::string(LAST_KMSG_FILTER).find(sub) == std::string::npos && + std::string(LAST_LOG_FILTER).find(sub) == std::string::npos) { return __android_log_pmsg_file_write(id, prio, filename, buf, len); } @@ -243,3 +256,70 @@ void save_kernel_log(const char* destination) { buffer.resize(n); android::base::WriteStringToFile(buffer, destination); } + +std::vector ReadLogFilesToMemory() { + ensure_path_mounted("/cache"); + + struct dirent* de; + std::unique_ptr d(opendir(CACHE_LOG_DIR), closedir); + if (!d) { + if (errno != ENOENT) { + PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; + } + return {}; + } + + std::vector log_files; + while ((de = readdir(d.get())) != nullptr) { + if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { + std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); + + struct stat sb; + if (stat(path.c_str(), &sb) != 0) { + PLOG(ERROR) << "Failed to stat " << path; + continue; + } + // Truncate files to 512kb + size_t read_size = std::min(sb.st_size, 1 << 19); + std::string data(read_size, '\0'); + + android::base::unique_fd log_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY))); + if (log_fd == -1 || !android::base::ReadFully(log_fd, data.data(), read_size)) { + PLOG(ERROR) << "Failed to read log file " << path; + continue; + } + + log_files.emplace_back(saved_log_file{ path, sb, data }); + } + } + + return log_files; +} + +bool RestoreLogFilesAfterFormat(const std::vector& log_files) { + // Re-create the log dir and write back the log entries. + if (ensure_path_mounted(CACHE_LOG_DIR) != 0) { + PLOG(ERROR) << "Failed to mount " << CACHE_LOG_DIR; + return false; + } + + if (mkdir_recursively(CACHE_LOG_DIR, 0777, false, logging_sehandle) != 0) { + PLOG(ERROR) << "Failed to create " << CACHE_LOG_DIR; + return false; + } + + for (const auto& log : log_files) { + if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, + log.sb.st_gid)) { + PLOG(ERROR) << "Failed to write to " << log.name; + } + } + + // Any part of the log we'd copied to cache is now gone. + // Reset the pointer so we copy from the beginning of the temp + // log. + reset_tmplog_offset(); + copy_logs(true /* save_current_log */, true /* has_cache */, logging_sehandle); + + return true; +} diff --git a/recovery_main.cpp b/recovery_main.cpp index 8f3f2a781..5f3ab76dd 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -435,7 +435,7 @@ int main(int argc, char** argv) { ui->Print("Warning: No file_contexts\n"); } - SetWipeDataSehandle(sehandle); + SetLoggingSehandle(sehandle); std::atomic action; std::thread listener_thread(ListenRecoverySocket, ui, std::ref(action)); -- cgit v1.2.3 From d0052c30cef1202fff6bb0f87d64eddb98cfbc99 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 16 Apr 2019 19:59:20 -0700 Subject: Import translations. DO NOT MERGE Auto-generated-cl: translation import Bug: 64712476 Change-Id: Ie58e59d8d2731350f58bc9733e4599f75a3aca61 --- tools/recovery_l10n/res/values-gl/strings.xml | 4 ++-- tools/recovery_l10n/res/values-ja/strings.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml index e51b36dfb..e6f2ffd84 100644 --- a/tools/recovery_l10n/res/values-gl/strings.xml +++ b/tools/recovery_l10n/res/values-gl/strings.xml @@ -6,9 +6,9 @@ "Non hai ningún comando" "Erro" "Instalando actualización de seguranza" - "Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos de usuario almacenados neste dispositivo." + "Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos do usuario almacenados neste dispositivo." "Tentar de novo" "Restablecemento dos datos de fábrica" - "Queres borrar todos os datos de usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER." + "Queres borrar todos os datos do usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER." "Cancelar" diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml index 3d6637278..2d6c0abc4 100644 --- a/tools/recovery_l10n/res/values-ja/strings.xml +++ b/tools/recovery_l10n/res/values-ja/strings.xml @@ -6,7 +6,7 @@ "コマンドが指定されていません" "エラーが発生しました。" "セキュリティ アップデートをインストールしています" - "Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、この端末に保存されているすべてのユーザー データを消去することが必要な場合があります。" + "Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、このデバイスに保存されているすべてのユーザー データを消去することが必要な場合があります。" "再試行" "データの初期化" "すべてのユーザー データをワイプしますか?\n\nこの操作は元に戻せません。" -- cgit v1.2.3 From 378bfbfc5c6f268c16b7f1a254de0ed36da52012 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Tue, 16 Apr 2019 14:22:25 -0700 Subject: Allow entering rescue mode via recovery UI. Only enabled on debuggable builds. Bug: 128415917 Test: Sideload package on taimen. Test: Choose "Enter rescue" from recovery UI. Change-Id: I913dbdbcffd3179e6fa72ca862f74ca8f1364b02 Merged-In: I913dbdbcffd3179e6fa72ca862f74ca8f1364b02 (cherry picked from commit c6dc325e88a25201aa3856e6532c3ed14203a376) --- install/adb_install.cpp | 31 ++++++++++++++++++++++--------- install/include/install/adb_install.h | 2 +- minadbd/minadbd.cpp | 12 ++++++++++-- minadbd/minadbd_services.cpp | 6 ++++++ minadbd/minadbd_services.h | 2 ++ recovery.cpp | 18 +++++++++++++----- recovery_main.cpp | 4 ++++ recovery_ui/device.cpp | 1 + recovery_ui/include/recovery_ui/device.h | 1 + 9 files changed, 60 insertions(+), 17 deletions(-) diff --git a/install/adb_install.cpp b/install/adb_install.cpp index 548b6e5b9..f430920a4 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ #include "fuse_sideload.h" #include "install/install.h" #include "minadbd_types.h" +#include "otautil/sysutil.h" #include "recovery_ui/ui.h" using CommandFunction = std::function; @@ -228,7 +230,7 @@ static void ListenAndExecuteMinadbdCommands( // b11. exit the listening loop // static void CreateMinadbdServiceAndExecuteCommands( - const std::map& command_map) { + const std::map& command_map, bool rescue_mode) { signal(SIGPIPE, SIG_IGN); android::base::unique_fd recovery_socket; @@ -245,9 +247,16 @@ static void CreateMinadbdServiceAndExecuteCommands( } if (child == 0) { recovery_socket.reset(); - execl("/system/bin/minadbd", "minadbd", "--socket_fd", - std::to_string(minadbd_socket.release()).c_str(), nullptr); - + std::vector minadbd_commands = { + "/system/bin/minadbd", + "--socket_fd", + std::to_string(minadbd_socket.release()), + }; + if (rescue_mode) { + minadbd_commands.push_back("--rescue"); + } + auto exec_args = StringVectorToNullTerminatedArray(minadbd_commands); + execv(exec_args[0], exec_args.data()); _exit(EXIT_FAILURE); } @@ -280,7 +289,7 @@ static void CreateMinadbdServiceAndExecuteCommands( signal(SIGPIPE, SIG_DFL); } -int apply_from_adb(RecoveryUI* ui) { +int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode) { // Save the usb state to restore after the sideload operation. std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); // Clean up state and stop adbd. @@ -289,16 +298,20 @@ int apply_from_adb(RecoveryUI* ui) { return INSTALL_ERROR; } - ui->Print( - "\n\nNow send the package you want to apply\n" - "to the device with \"adb sideload \"...\n"); + if (!rescue_mode) { + ui->Print( + "\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload \"...\n"); + } else { + ui->Print("\n\nWaiting for rescue commands...\n"); + } int install_result = INSTALL_ERROR; std::map command_map{ { MinadbdCommands::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, }; - CreateMinadbdServiceAndExecuteCommands(command_map); + CreateMinadbdServiceAndExecuteCommands(command_map, rescue_mode); // Clean up before switching to the older state, for example setting the state // to none sets sys/class/android_usb/android0/enable to 0. diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h index f7b065b64..208d0c780 100644 --- a/install/include/install/adb_install.h +++ b/install/include/install/adb_install.h @@ -18,4 +18,4 @@ #include -int apply_from_adb(RecoveryUI* ui); +int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode); diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp index 57158ad57..c80d5490a 100644 --- a/minadbd/minadbd.cpp +++ b/minadbd/minadbd.cpp @@ -31,10 +31,13 @@ #include "minadbd_services.h" #include "minadbd_types.h" +using namespace std::string_literals; + int main(int argc, char** argv) { android::base::InitLogging(argv, &android::base::StderrLogger); // TODO(xunchang) implement a command parser - if (argc != 3 || strcmp("--socket_fd", argv[1]) != 0) { + if ((argc != 3 && argc != 4) || argv[1] != "--socket_fd"s || + (argc == 4 && argv[3] != "--rescue"s)) { LOG(ERROR) << "minadbd has invalid arguments, argc: " << argc; exit(kMinadbdArgumentsParsingError); } @@ -50,7 +53,12 @@ int main(int argc, char** argv) { } SetMinadbdSocketFd(socket_fd); - adb_device_banner = "sideload"; + if (argc == 4) { + SetMinadbdRescueMode(true); + adb_device_banner = "rescue"; + } else { + adb_device_banner = "sideload"; + } signal(SIGPIPE, SIG_IGN); diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 79e6fc4e0..f0bb70af1 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -45,10 +45,16 @@ #include "sysdeps.h" static int minadbd_socket = -1; +static bool rescue_mode = false; + void SetMinadbdSocketFd(int socket_fd) { minadbd_socket = socket_fd; } +void SetMinadbdRescueMode(bool rescue) { + rescue_mode = rescue; +} + static bool WriteCommandToFd(MinadbdCommands cmd, int fd) { char message[kMinadbdMessageSize]; memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); diff --git a/minadbd/minadbd_services.h b/minadbd/minadbd_services.h index 6835bd770..20e3410c0 100644 --- a/minadbd/minadbd_services.h +++ b/minadbd/minadbd_services.h @@ -17,3 +17,5 @@ #pragma once void SetMinadbdSocketFd(int socket_fd); + +void SetMinadbdRescueMode(bool); diff --git a/recovery.cpp b/recovery.cpp index 0e6e4976d..ce29cb27b 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -538,12 +538,20 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { break; } case Device::APPLY_ADB_SIDELOAD: - case Device::APPLY_SDCARD: { + case Device::APPLY_SDCARD: + case Device::ENTER_RESCUE: { save_current_log = true; - bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); - if (adb) { - status = apply_from_adb(ui); + + bool adb = true; + if (chosen_action == Device::ENTER_RESCUE) { + // Switch to graphics screen. + ui->ShowText(false); + status = ApplyFromAdb(ui, true /* rescue_mode */); + ui->ShowText(true); + } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) { + status = ApplyFromAdb(ui, false /* rescue_mode */); } else { + adb = false; status = ApplyFromSdcard(device, ui); } @@ -926,7 +934,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorShowText(true); } - status = apply_from_adb(ui); + status = ApplyFromAdb(ui, false /* rescue_mode */); ui->Print("\nInstall from ADB complete (status: %d).\n", status); if (sideload_auto_reboot) { ui->Print("Rebooting automatically.\n"); diff --git a/recovery_main.cpp b/recovery_main.cpp index 5f3ab76dd..38e1db73b 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -423,6 +423,10 @@ int main(int argc, char** argv) { device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT); } + if (!is_ro_debuggable()) { + device->RemoveMenuItemForAction(Device::ENTER_RESCUE); + } + ui->SetBackground(RecoveryUI::NONE); if (show_text) ui->ShowText(true); diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp index ddb0118db..e7ae1a3e1 100644 --- a/recovery_ui/device.cpp +++ b/recovery_ui/device.cpp @@ -37,6 +37,7 @@ static std::vector> g_menu_actions { "View recovery logs", Device::VIEW_RECOVERY_LOGS }, { "Run graphics test", Device::RUN_GRAPHICS_TEST }, { "Run locale test", Device::RUN_LOCALE_TEST }, + { "Enter rescue", Device::ENTER_RESCUE }, { "Power off", Device::SHUTDOWN }, }; diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h index 3c44510c8..8f17639d6 100644 --- a/recovery_ui/include/recovery_ui/device.h +++ b/recovery_ui/include/recovery_ui/device.h @@ -50,6 +50,7 @@ class Device { KEY_INTERRUPTED = 13, ENTER_FASTBOOT = 14, ENTER_RECOVERY = 15, + ENTER_RESCUE = 16, }; explicit Device(RecoveryUI* ui); -- cgit v1.2.3 From 178cdd4f5c6ae59d5aaae2614f22cd783eba60d8 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 15 Apr 2019 12:45:50 -0700 Subject: Remove the FD parameter from FuseDataProvider ctor. This leaves the FD implementation details to subclasses. In particular, it allows minadbd to do additional works with the FD after sideloading. Bug: 128415917 Test: atest recovery_component_test Test: atest minadbd_test Test: Sideload package on taimen. Change-Id: I106bbaad05201227bbc5fe28890bbbb06fdcb67e Merged-In: I106bbaad05201227bbc5fe28890bbbb06fdcb67e (cherry picked from commit 2be9737cf449dd0650c85ee5168d09b12d386077) --- fuse_sideload/include/fuse_provider.h | 23 +++++++++++------------ minadbd/fuse_adb_provider.h | 15 +++++++-------- minadbd/minadbd_services.cpp | 3 +-- tests/component/sideload_test.cpp | 22 +++++++++++++++------- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h index 499d57aa0..59059cf9b 100644 --- a/fuse_sideload/include/fuse_provider.h +++ b/fuse_sideload/include/fuse_provider.h @@ -25,8 +25,8 @@ // This is the base class to read data from source and provide the data to FUSE. class FuseDataProvider { public: - FuseDataProvider(android::base::unique_fd&& fd, uint64_t file_size, uint32_t block_size) - : fd_(std::move(fd)), file_size_(file_size), fuse_block_size_(block_size) {} + FuseDataProvider(uint64_t file_size, uint32_t block_size) + : file_size_(file_size), fuse_block_size_(block_size) {} virtual ~FuseDataProvider() = default; @@ -37,21 +37,15 @@ class FuseDataProvider { return fuse_block_size_; } - bool Valid() const { - return fd_ != -1; - } - // Reads |fetch_size| bytes data starting from |start_block|. Puts the result in |buffer|. virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const = 0; - virtual void Close() = 0; + virtual void Close() {} protected: FuseDataProvider() = default; - // The underlying source to read data from. - android::base::unique_fd fd_; // Size in bytes of the file to read. uint64_t file_size_ = 0; // Block size passed to the fuse, this is different from the block size of the block device. @@ -61,13 +55,18 @@ class FuseDataProvider { // This class reads data from a file. class FuseFileDataProvider : public FuseDataProvider { public: - FuseFileDataProvider(android::base::unique_fd&& fd, uint64_t file_size, uint32_t block_size) - : FuseDataProvider(std::move(fd), file_size, block_size) {} - FuseFileDataProvider(const std::string& path, uint32_t block_size); bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const override; + bool Valid() const { + return fd_ != -1; + } + void Close() override; + + private: + // The underlying source to read data from. + android::base::unique_fd fd_; }; diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h index 3fb689bd4..24a463d9b 100644 --- a/minadbd/fuse_adb_provider.h +++ b/minadbd/fuse_adb_provider.h @@ -14,25 +14,24 @@ * limitations under the License. */ -#ifndef __FUSE_ADB_PROVIDER_H -#define __FUSE_ADB_PROVIDER_H +#pragma once #include -#include "android-base/unique_fd.h" - #include "fuse_provider.h" // This class reads data from adb server. class FuseAdbDataProvider : public FuseDataProvider { public: - FuseAdbDataProvider(android::base::unique_fd&& fd, uint64_t file_size, uint32_t block_size) - : FuseDataProvider(std::move(fd), file_size, block_size) {} + FuseAdbDataProvider(int fd, uint64_t file_size, uint32_t block_size) + : FuseDataProvider(file_size, block_size), fd_(fd) {} bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const override; void Close() override; -}; -#endif + private: + // The underlying source to read data from (i.e. the one that talks to the host). + int fd_; +}; diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index f0bb70af1..eaf88ecc2 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -102,8 +102,7 @@ static void sideload_host_service(unique_fd sfd, const std::string& args) { exit(kMinadbdSocketIOError); } - auto adb_data_reader = - std::make_unique(std::move(sfd), file_size, block_size); + auto adb_data_reader = std::make_unique(sfd, file_size, block_size); if (int result = run_fuse_sideload(std::move(adb_data_reader)); result != 0) { LOG(ERROR) << "Failed to start fuse"; exit(kMinadbdFuseStartError); diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp index f5981acbd..6add99f41 100644 --- a/tests/component/sideload_test.cpp +++ b/tests/component/sideload_test.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include "fuse_provider.h" @@ -32,17 +31,26 @@ TEST(SideloadTest, fuse_device) { ASSERT_EQ(0, access("/dev/fuse", R_OK | W_OK)); } +class FuseTestDataProvider : public FuseDataProvider { + public: + FuseTestDataProvider(uint64_t file_size, uint32_t block_size) + : FuseDataProvider(file_size, block_size) {} + + private: + bool ReadBlockAlignedData(uint8_t*, uint32_t, uint32_t) const override { + return true; + } +}; + TEST(SideloadTest, run_fuse_sideload_wrong_parameters) { - auto provider_small_block = - std::make_unique(android::base::unique_fd(), 4096, 4095); + auto provider_small_block = std::make_unique(4096, 4095); ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_small_block))); - auto provider_large_block = - std::make_unique(android::base::unique_fd(), 4096, (1 << 22) + 1); + auto provider_large_block = std::make_unique(4096, (1 << 22) + 1); ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_large_block))); - auto provider_too_many_blocks = std::make_unique( - android::base::unique_fd(), ((1 << 18) + 1) * 4096, 4096); + auto provider_too_many_blocks = + std::make_unique(((1 << 18) + 1) * 4096, 4096); ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_too_many_blocks))); } -- cgit v1.2.3 From e5c6446a10291eaca258fcae3ce2654d6224dcb9 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 4 Apr 2019 18:37:58 -0700 Subject: minadbd: Support rescue install and getprop commands. Bug: 128415917 Test: Enter rescue mode on taimen. Send the following commands: `adb rescue getprop ro.build.fingerprint` `adb rescue getprop ro.build.date.utc` `adb rescue install /path/to/package.zip` Test: Sideload on taimen w/ `adb sideload /path/to/package.zip`. Change-Id: Ibc25daf9fd13f7002e54789f67aaf85d06976bb8 Merged-In: Ibc25daf9fd13f7002e54789f67aaf85d06976bb8 (cherry picked from commit ed717ca17d0b1a35f2d2e57802e2381a6004fdd1) --- minadbd/Android.bp | 1 - minadbd/fuse_adb_provider.cpp | 4 -- minadbd/fuse_adb_provider.h | 2 - minadbd/minadbd_services.cpp | 95 ++++++++++++++++++++++++++++++++++++------- minadbd/minadbd_types.h | 1 + 5 files changed, 82 insertions(+), 21 deletions(-) diff --git a/minadbd/Android.bp b/minadbd/Android.bp index e4f7712e5..b1c68ca92 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -76,7 +76,6 @@ cc_binary { "libadbd", "libbase", "libcrypto", - "libfusesideload", "libminadbd_services", ], } diff --git a/minadbd/fuse_adb_provider.cpp b/minadbd/fuse_adb_provider.cpp index 9d19a1ec3..47719b07a 100644 --- a/minadbd/fuse_adb_provider.cpp +++ b/minadbd/fuse_adb_provider.cpp @@ -37,7 +37,3 @@ bool FuseAdbDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_s return true; } - -void FuseAdbDataProvider::Close() { - WriteFdExactly(fd_, "DONEDONE"); -} diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h index 24a463d9b..c5561e57d 100644 --- a/minadbd/fuse_adb_provider.h +++ b/minadbd/fuse_adb_provider.h @@ -29,8 +29,6 @@ class FuseAdbDataProvider : public FuseDataProvider { bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const override; - void Close() override; - private: // The underlying source to read data from (i.e. the one that talks to the host). int fd_; diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index eaf88ecc2..136392a64 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -28,15 +28,19 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include "adb.h" #include "adb_unique_fd.h" +#include "adb_utils.h" #include "fdevent.h" #include "fuse_adb_provider.h" #include "fuse_sideload.h" @@ -87,46 +91,109 @@ static bool WaitForCommandStatus(int fd, MinadbdCommandStatus* status) { return true; } -static void sideload_host_service(unique_fd sfd, const std::string& args) { +static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, + MinadbdCommandStatus* status) { + auto pieces = android::base::Split(args, ":"); int64_t file_size; int block_size; - if ((sscanf(args.c_str(), "%" SCNd64 ":%d", &file_size, &block_size) != 2) || file_size <= 0 || - block_size <= 0) { + if (pieces.size() != 2 || !android::base::ParseInt(pieces[0], &file_size) || file_size <= 0 || + !android::base::ParseInt(pieces[1], &block_size) || block_size <= 0) { LOG(ERROR) << "bad sideload-host arguments: " << args; - exit(kMinadbdPackageSizeError); + return kMinadbdPackageSizeError; } LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size; if (!WriteCommandToFd(MinadbdCommands::kInstall, minadbd_socket)) { - exit(kMinadbdSocketIOError); + return kMinadbdSocketIOError; } auto adb_data_reader = std::make_unique(sfd, file_size, block_size); if (int result = run_fuse_sideload(std::move(adb_data_reader)); result != 0) { LOG(ERROR) << "Failed to start fuse"; - exit(kMinadbdFuseStartError); + return kMinadbdFuseStartError; + } + + if (!WaitForCommandStatus(minadbd_socket, status)) { + return kMinadbdMessageFormatError; + } + + // Signal host-side adb to stop. For sideload mode, we always send kSideloadServiceExitSuccess + // (i.e. "DONEDONE") regardless of the install result. For rescue mode, we send failure message on + // install error. + if (!rescue_mode || *status == MinadbdCommandStatus::kSuccess) { + if (!android::base::WriteFully(sfd, kSideloadServiceExitSuccess, + strlen(kSideloadServiceExitSuccess))) { + return kMinadbdHostSocketIOError; + } + } else { + if (!android::base::WriteFully(sfd, kSideloadServiceExitFailure, + strlen(kSideloadServiceExitFailure))) { + return kMinadbdHostSocketIOError; + } } + return kMinadbdSuccess; +} + +// Sideload service always exits after serving an install command. +static void SideloadHostService(unique_fd sfd, const std::string& args) { MinadbdCommandStatus status; - if (!WaitForCommandStatus(minadbd_socket, &status)) { - exit(kMinadbdMessageFormatError); + exit(RunAdbFuseSideload(sfd.get(), args, &status)); +} + +// Rescue service waits for the next command after an install command. +static void RescueInstallHostService(unique_fd sfd, const std::string& args) { + MinadbdCommandStatus status; + if (auto result = RunAdbFuseSideload(sfd.get(), args, &status); result != kMinadbdSuccess) { + exit(result); + } +} + +static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { + static const std::unordered_set kGetpropAllowedProps = { + "ro.build.fingerprint", + "ro.build.date.utc", + }; + auto allowed = kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end(); + if (!allowed) { + return; } - LOG(INFO) << "Got command status: " << static_cast(status); - LOG(INFO) << "sideload_host finished"; - exit(kMinadbdSuccess); + auto result = android::base::GetProperty(prop, ""); + if (result.empty()) { + return; + } + if (!android::base::WriteFully(sfd, result.data(), result.size())) { + exit(kMinadbdHostSocketIOError); + } } unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) { + if (rescue_mode) { + if (ConsumePrefix(&name, "rescue-install:")) { + // rescue-install:: + std::string args(name); + return create_service_thread( + "rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args)); + } else if (ConsumePrefix(&name, "rescue-getprop:")) { + // rescue-getprop: + std::string args(name); + return create_service_thread( + "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args)); + } + return unique_fd{}; + } + if (name.starts_with("sideload:")) { // This exit status causes recovery to print a special error message saying to use a newer adb // (that supports sideload-host). exit(kMinadbdAdbVersionError); - } else if (name.starts_with("sideload-host:")) { - std::string arg(name.substr(strlen("sideload-host:"))); + } else if (ConsumePrefix(&name, "sideload-host:")) { + // sideload-host:: + std::string args(name); return create_service_thread("sideload-host", - std::bind(sideload_host_service, std::placeholders::_1, arg)); + std::bind(SideloadHostService, std::placeholders::_1, args)); } return unique_fd{}; } diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h index 7bd69096a..5fb7803e7 100644 --- a/minadbd/minadbd_types.h +++ b/minadbd/minadbd_types.h @@ -35,6 +35,7 @@ enum MinadbdErrorCode : int { kMinadbdUnsupportedCommandError = 7, kMinadbdCommandExecutionError = 8, kMinadbdErrorUnknown = 9, + kMinadbdHostSocketIOError = 10, }; enum class MinadbdCommandStatus : uint32_t { -- cgit v1.2.3 From 23f15fcfafe3aeab13d4deeaca48917fa0dc415c Mon Sep 17 00:00:00 2001 From: xunchang Date: Wed, 17 Apr 2019 14:43:58 -0700 Subject: Add test for minadbd Ass some unit tests to check if the minadbd service exit correctly in the failure case. Also start the fuse and verify the socket communication between minadbd with adb host, and minadbd with recovery. Bug: 131037235 Test: run unit tests repeatedly, injects some errors and test fails without dangling process. Change-Id: I2f073b701b25d7f1aafc59868a7a91a8cbefaf49 Merged-In: I2f073b701b25d7f1aafc59868a7a91a8cbefaf49 (cherry picked from commit 9c04eb46b7492033e4675bd303a8bb20548081b5) --- minadbd/Android.bp | 2 + minadbd/minadbd_services.cpp | 8 +- minadbd/minadbd_services.h | 4 + minadbd/minadbd_services_test.cpp | 213 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 minadbd/minadbd_services_test.cpp diff --git a/minadbd/Android.bp b/minadbd/Android.bp index b1c68ca92..007e5057b 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -90,12 +90,14 @@ cc_test { srcs: [ "fuse_adb_provider_test.cpp", + "minadbd_services_test.cpp", ], static_libs: [ "libminadbd_services", "libfusesideload", "libadbd", + "libcrypto", ], shared_libs: [ diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 136392a64..f6aff71f8 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -50,6 +50,7 @@ static int minadbd_socket = -1; static bool rescue_mode = false; +static std::string sideload_mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT; void SetMinadbdSocketFd(int socket_fd) { minadbd_socket = socket_fd; @@ -59,6 +60,10 @@ void SetMinadbdRescueMode(bool rescue) { rescue_mode = rescue; } +void SetSideloadMountPoint(const std::string& path) { + sideload_mount_point = path; +} + static bool WriteCommandToFd(MinadbdCommands cmd, int fd) { char message[kMinadbdMessageSize]; memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); @@ -109,7 +114,8 @@ static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, } auto adb_data_reader = std::make_unique(sfd, file_size, block_size); - if (int result = run_fuse_sideload(std::move(adb_data_reader)); result != 0) { + if (int result = run_fuse_sideload(std::move(adb_data_reader), sideload_mount_point.c_str()); + result != 0) { LOG(ERROR) << "Failed to start fuse"; return kMinadbdFuseStartError; } diff --git a/minadbd/minadbd_services.h b/minadbd/minadbd_services.h index 20e3410c0..5575c6b8e 100644 --- a/minadbd/minadbd_services.h +++ b/minadbd/minadbd_services.h @@ -16,6 +16,10 @@ #pragma once +#include + void SetMinadbdSocketFd(int socket_fd); void SetMinadbdRescueMode(bool); + +void SetSideloadMountPoint(const std::string& path); diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp new file mode 100644 index 000000000..413ba0df6 --- /dev/null +++ b/minadbd/minadbd_services_test.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2019 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "adb.h" +#include "adb_io.h" +#include "fuse_adb_provider.h" +#include "fuse_sideload.h" +#include "minadbd_services.h" +#include "minadbd_types.h" +#include "socket.h" + +class MinadbdServicesTest : public ::testing::Test { + protected: + static constexpr int EXIT_TIME_OUT = 10; + + void SetUp() override { + ASSERT_TRUE( + android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &minadbd_socket_, &recovery_socket_)); + SetMinadbdSocketFd(minadbd_socket_); + SetSideloadMountPoint(mount_point_.path); + + package_path_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME; + exit_flag_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG; + + signal(SIGPIPE, SIG_IGN); + } + + void TearDown() override { + // Umount in case the test fails. Ignore the result. + umount(mount_point_.path); + + signal(SIGPIPE, SIG_DFL); + } + + void ReadAndCheckCommandMessage(int fd, MinadbdCommands expected_command) { + std::vector received(kMinadbdMessageSize, '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize)); + + std::vector expected(kMinadbdMessageSize, '\0'); + memcpy(expected.data(), kMinadbdCommandPrefix, strlen(kMinadbdCommandPrefix)); + memcpy(expected.data() + strlen(kMinadbdCommandPrefix), &expected_command, + sizeof(expected_command)); + ASSERT_EQ(expected, received); + } + + void WaitForFusePath() { + constexpr int TIME_OUT = 10; + for (int i = 0; i < TIME_OUT; ++i) { + struct stat sb; + if (stat(package_path_.c_str(), &sb) == 0) { + return; + } + + if (errno == ENOENT) { + sleep(1); + continue; + } + FAIL() << "Timed out waiting for the fuse-provided package " << strerror(errno); + } + } + + void StatExitFlagAndExitProcess(int exit_code) { + struct stat sb; + if (stat(exit_flag_.c_str(), &sb) != 0) { + PLOG(ERROR) << "Failed to stat " << exit_flag_; + } + + exit(exit_code); + } + + void WriteMinadbdCommandStatus(MinadbdCommandStatus status) { + std::string status_message(kMinadbdMessageSize, '\0'); + memcpy(status_message.data(), kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix)); + memcpy(status_message.data() + strlen(kMinadbdStatusPrefix), &status, sizeof(status)); + ASSERT_TRUE( + android::base::WriteFully(recovery_socket_, status_message.data(), kMinadbdMessageSize)); + } + + void ExecuteCommandAndWaitForExit(const std::string& command) { + unique_fd fd = daemon_service_to_fd(command, nullptr); + ASSERT_NE(-1, fd); + sleep(EXIT_TIME_OUT); + } + + android::base::unique_fd minadbd_socket_; + android::base::unique_fd recovery_socket_; + + TemporaryDir mount_point_; + std::string package_path_; + std::string exit_flag_; +}; + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_size_argument) { + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:abc:4096"), + ::testing::ExitedWithCode(kMinadbdPackageSizeError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_block_size) { + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:10:20"), + ::testing::ExitedWithCode(kMinadbdFuseStartError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_broken_minadbd_socket) { + SetMinadbdSocketFd(-1); + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdSocketIOError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_broken_recovery_socket) { + recovery_socket_.reset(); + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdSocketIOError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_command_format) { + auto test_body = [&](const std::string& command) { + unique_fd fd = daemon_service_to_fd(command, nullptr); + ASSERT_NE(-1, fd); + WaitForFusePath(); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommands::kInstall); + + struct stat sb; + ASSERT_EQ(0, stat(exit_flag_.c_str(), &sb)); + ASSERT_TRUE(android::base::WriteStringToFd("12345678", recovery_socket_)); + sleep(EXIT_TIME_OUT); + }; + + ASSERT_EXIT(test_body("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdMessageFormatError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_read_data_from_fuse) { + auto test_body = [&]() { + std::vector content(4096, 'a'); + // Start a new process instead of a thread to read from the package mounted by FUSE. Because + // the test may not exit and report failures correctly when the thread blocks by a syscall. + pid_t pid = fork(); + if (pid == 0) { + WaitForFusePath(); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(package_path_.c_str(), O_RDONLY))); + // Do not use assertion here because we want to stat the exit flag and exit the process. + // Otherwise the test will wait for the time out instead of failing immediately. + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << package_path_; + StatExitFlagAndExitProcess(1); + } + std::vector content_from_fuse(4096); + if (!android::base::ReadFully(fd, content_from_fuse.data(), 4096)) { + PLOG(ERROR) << "Failed to read from " << package_path_; + StatExitFlagAndExitProcess(1); + } + if (content_from_fuse != content) { + LOG(ERROR) << "Content read from fuse doesn't match with the expected value"; + StatExitFlagAndExitProcess(1); + } + StatExitFlagAndExitProcess(0); + } + + unique_fd fd = daemon_service_to_fd("sideload-host:4096:4096", nullptr); + ASSERT_NE(-1, fd); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommands::kInstall); + + // Mimic the response from adb host. + std::string adb_message(8, '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, adb_message.data(), 8)); + ASSERT_EQ(android::base::StringPrintf("%08u", 0), adb_message); + ASSERT_TRUE(android::base::WriteFully(fd, content.data(), 4096)); + + // Check that we read the correct data from fuse. + int child_status; + waitpid(pid, &child_status, 0); + ASSERT_TRUE(WIFEXITED(child_status)); + ASSERT_EQ(0, WEXITSTATUS(child_status)); + + WriteMinadbdCommandStatus(MinadbdCommandStatus::kSuccess); + + // TODO(xunchang) check if adb host-side receives "DONEDONE", there's a race condition between + // receiving the message and exit of test body (by detached thread in minadbd service). + exit(kMinadbdSuccess); + }; + + ASSERT_EXIT(test_body(), ::testing::ExitedWithCode(kMinadbdSuccess), ""); +} -- cgit v1.2.3 From 7b9b7db877f082a9adaa8b8a8572940cb5720a11 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Fri, 19 Apr 2019 15:22:15 -0700 Subject: minadbd: Support `adb reboot` under sideload/rescue modes. Bug: 128415917 Test: Run the following commands under sideload and rescue modes respectively. $ adb reboot $ adb reboot bootloader $ adb reboot recovery $ adb reboot rescue $ adb reboot invalid Change-Id: I84daf63e3360b7b4a0af5e055149a4f54e10ba90 Merged-In: I84daf63e3360b7b4a0af5e055149a4f54e10ba90 (cherry picked from commit 10f441a9dbb91be3124f455439631abcf8e96cde) --- install/adb_install.cpp | 98 +++++++++++++++++++++++--------- install/include/install/adb_install.h | 6 +- install/include/install/install.h | 3 +- minadbd/minadbd_services.cpp | 43 +++++++++++++- minadbd/minadbd_services_test.cpp | 6 +- minadbd/minadbd_types.h | 13 ++++- recovery.cpp | 58 +++++++++++-------- recovery_main.cpp | 5 ++ recovery_ui/include/recovery_ui/device.h | 4 ++ 9 files changed, 176 insertions(+), 60 deletions(-) diff --git a/install/adb_install.cpp b/install/adb_install.cpp index f430920a4..d79f6f4b0 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -44,30 +45,34 @@ #include "install/install.h" #include "minadbd_types.h" #include "otautil/sysutil.h" +#include "recovery_ui/device.h" #include "recovery_ui/ui.h" -using CommandFunction = std::function; +// A CommandFunction returns a pair of (result, should_continue), which indicates the command +// execution result and whether it should proceed to the next iteration. The execution result will +// always be sent to the minadbd side. +using CommandFunction = std::function()>; static bool SetUsbConfig(const std::string& state) { android::base::SetProperty("sys.usb.config", state); return android::base::WaitForProperty("sys.usb.state", state); } -// Parses the minadbd command in |message|; returns MinadbdCommands::kError upon errors. -static MinadbdCommands ParseMinadbdCommands(const std::string& message) { +// Parses the minadbd command in |message|; returns MinadbdCommand::kError upon errors. +static MinadbdCommand ParseMinadbdCommand(const std::string& message) { if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) { LOG(ERROR) << "Failed to parse command in message " << message; - return MinadbdCommands::kError; + return MinadbdCommand::kError; } auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix)); auto cmd_code = android::base::get_unaligned(cmd_code_string.c_str()); - if (cmd_code >= static_cast(MinadbdCommands::kError)) { + if (cmd_code >= static_cast(MinadbdCommand::kError)) { LOG(ERROR) << "Unsupported command code: " << cmd_code; - return MinadbdCommands::kError; + return MinadbdCommand::kError; } - return static_cast(cmd_code); + return static_cast(cmd_code); } static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { @@ -82,13 +87,15 @@ static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { return true; } -// Installs the package from FUSE. Returns true if the installation succeeds, and false otherwise. -static bool AdbInstallPackageHandler(RecoveryUI* ui, int* result) { +// Installs the package from FUSE. Returns the installation result and whether it should continue +// waiting for new commands. +static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) { // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME // will start to exist once the host connects and starts serving a package. Poll for its // appearance. (Note that inotify doesn't work with FUSE.) constexpr int ADB_INSTALL_TIMEOUT = 15; + bool should_continue = true; *result = INSTALL_ERROR; for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { struct stat st; @@ -97,6 +104,7 @@ static bool AdbInstallPackageHandler(RecoveryUI* ui, int* result) { sleep(1); continue; } else { + should_continue = false; ui->Print("\nTimed out waiting for fuse to be ready.\n\n"); break; } @@ -108,13 +116,39 @@ static bool AdbInstallPackageHandler(RecoveryUI* ui, int* result) { // Calling stat() on this magic filename signals the FUSE to exit. struct stat st; stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); - return *result == INSTALL_SUCCESS; + return std::make_pair(*result == INSTALL_SUCCESS, should_continue); } -// Parses and executes the command from minadbd. Returns false if we enter an invalid state so that -// the caller can kill the minadbd service properly. -static bool HandleMessageFromMinadbd( - int socket_fd, const std::map& command_map) { +static auto AdbRebootHandler(MinadbdCommand command, int* result, + Device::BuiltinAction* reboot_action) { + switch (command) { + case MinadbdCommand::kRebootBootloader: + *reboot_action = Device::REBOOT_BOOTLOADER; + break; + case MinadbdCommand::kRebootFastboot: + *reboot_action = Device::ENTER_FASTBOOT; + break; + case MinadbdCommand::kRebootRecovery: + *reboot_action = Device::ENTER_RECOVERY; + break; + case MinadbdCommand::kRebootRescue: + // Use Device::REBOOT_RESCUE instead of Device::ENTER_RESCUE. This allows rebooting back into + // rescue mode (potentially using a newly installed recovery image). + *reboot_action = Device::REBOOT_RESCUE; + break; + case MinadbdCommand::kRebootAndroid: + default: + *reboot_action = Device::REBOOT; + break; + } + *result = INSTALL_REBOOT; + return std::make_pair(true, false); +} + +// Parses and executes the command from minadbd. Returns whether the caller should keep waiting for +// next command. +static bool HandleMessageFromMinadbd(int socket_fd, + const std::map& command_map) { char buffer[kMinadbdMessageSize]; if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) { PLOG(ERROR) << "Failed to read message from minadbd"; @@ -122,8 +156,8 @@ static bool HandleMessageFromMinadbd( } std::string message(buffer, buffer + kMinadbdMessageSize); - auto command_type = ParseMinadbdCommands(message); - if (command_type == MinadbdCommands::kError) { + auto command_type = ParseMinadbdCommand(message); + if (command_type == MinadbdCommand::kError) { return false; } if (command_map.find(command_type) == command_map.end()) { @@ -135,17 +169,19 @@ static bool HandleMessageFromMinadbd( // We have received a valid command, execute the corresponding function. const auto& command_func = command_map.at(command_type); - if (!command_func()) { - LOG(ERROR) << "Failed to execute command " << static_cast(command_type); - return WriteStatusToFd(MinadbdCommandStatus::kFailure, socket_fd); + const auto [result, should_continue] = command_func(); + LOG(INFO) << "Command " << static_cast(command_type) << " finished with " << result; + if (!WriteStatusToFd(result ? MinadbdCommandStatus::kSuccess : MinadbdCommandStatus::kFailure, + socket_fd)) { + return false; } - return WriteStatusToFd(MinadbdCommandStatus::kSuccess, socket_fd); + return should_continue; } // TODO(xunchang) add a wrapper function and kill the minadbd service there. static void ListenAndExecuteMinadbdCommands( pid_t minadbd_pid, android::base::unique_fd&& socket_fd, - const std::map& command_map) { + const std::map& command_map) { android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC)); if (epoll_fd == -1) { PLOG(ERROR) << "Failed to create epoll"; @@ -230,7 +266,7 @@ static void ListenAndExecuteMinadbdCommands( // b11. exit the listening loop // static void CreateMinadbdServiceAndExecuteCommands( - const std::map& command_map, bool rescue_mode) { + const std::map& command_map, bool rescue_mode) { signal(SIGPIPE, SIG_IGN); android::base::unique_fd recovery_socket; @@ -271,7 +307,6 @@ static void CreateMinadbdServiceAndExecuteCommands( std::thread listener_thread(ListenAndExecuteMinadbdCommands, child, std::move(recovery_socket), std::ref(command_map)); - if (listener_thread.joinable()) { listener_thread.join(); } @@ -289,7 +324,7 @@ static void CreateMinadbdServiceAndExecuteCommands( signal(SIGPIPE, SIG_DFL); } -int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode) { +int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action) { // Save the usb state to restore after the sideload operation. std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); // Clean up state and stop adbd. @@ -307,8 +342,19 @@ int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode) { } int install_result = INSTALL_ERROR; - std::map command_map{ - { MinadbdCommands::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, + std::map command_map{ + { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, + { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootBootloader, + std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result, + reboot_action) }, + { MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootRescue, + std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) }, }; CreateMinadbdServiceAndExecuteCommands(command_map, rescue_mode); diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h index 208d0c780..49b32b54f 100644 --- a/install/include/install/adb_install.h +++ b/install/include/install/adb_install.h @@ -16,6 +16,10 @@ #pragma once +#include #include -int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode); +// Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum +// InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with +// the reboot target set in reboot_action. +int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action); diff --git a/install/include/install/install.h b/install/include/install/install.h index 1e41b4843..c0a8f1f4c 100644 --- a/install/include/install/install.h +++ b/install/include/install/install.h @@ -34,7 +34,8 @@ enum InstallResult { INSTALL_NONE, INSTALL_SKIPPED, INSTALL_RETRY, - INSTALL_KEY_INTERRUPTED + INSTALL_KEY_INTERRUPTED, + INSTALL_REBOOT, }; enum class OtaType { diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index f6aff71f8..9b1999d90 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -64,7 +64,7 @@ void SetSideloadMountPoint(const std::string& path) { sideload_mount_point = path; } -static bool WriteCommandToFd(MinadbdCommands cmd, int fd) { +static bool WriteCommandToFd(MinadbdCommand cmd, int fd) { char message[kMinadbdMessageSize]; memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd); @@ -109,7 +109,7 @@ static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size; - if (!WriteCommandToFd(MinadbdCommands::kInstall, minadbd_socket)) { + if (!WriteCommandToFd(MinadbdCommand::kInstall, minadbd_socket)) { return kMinadbdSocketIOError; } @@ -175,7 +175,45 @@ static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { } } +// Reboots into the given target. We don't reboot directly from minadbd, but going through recovery +// instead. This allows recovery to finish all the pending works (clear BCB, save logs etc) before +// the reboot. +static void RebootHostService(unique_fd /* sfd */, const std::string& target) { + MinadbdCommand command; + if (target == "bootloader") { + command = MinadbdCommand::kRebootBootloader; + } else if (target == "rescue") { + command = MinadbdCommand::kRebootRescue; + } else if (target == "recovery") { + command = MinadbdCommand::kRebootRecovery; + } else if (target == "fastboot") { + command = MinadbdCommand::kRebootFastboot; + } else { + command = MinadbdCommand::kRebootAndroid; + } + if (!WriteCommandToFd(command, minadbd_socket)) { + exit(kMinadbdSocketIOError); + } + MinadbdCommandStatus status; + if (!WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } +} + unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) { + // Common services that are supported both in sideload and rescue modes. + if (ConsumePrefix(&name, "reboot:")) { + // "reboot:", where target must be one of the following. + std::string args(name); + if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" || + args == "fastboot") { + return create_service_thread("reboot", + std::bind(RebootHostService, std::placeholders::_1, args)); + } + return unique_fd{}; + } + + // Rescue-specific services. if (rescue_mode) { if (ConsumePrefix(&name, "rescue-install:")) { // rescue-install:: @@ -191,6 +229,7 @@ unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport * return unique_fd{}; } + // Sideload-specific services. if (name.starts_with("sideload:")) { // This exit status causes recovery to print a special error message saying to use a newer adb // (that supports sideload-host). diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp index 413ba0df6..593180bb3 100644 --- a/minadbd/minadbd_services_test.cpp +++ b/minadbd/minadbd_services_test.cpp @@ -62,7 +62,7 @@ class MinadbdServicesTest : public ::testing::Test { signal(SIGPIPE, SIG_DFL); } - void ReadAndCheckCommandMessage(int fd, MinadbdCommands expected_command) { + void ReadAndCheckCommandMessage(int fd, MinadbdCommand expected_command) { std::vector received(kMinadbdMessageSize, '\0'); ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize)); @@ -147,7 +147,7 @@ TEST_F(MinadbdServicesTest, SideloadHostService_wrong_command_format) { unique_fd fd = daemon_service_to_fd(command, nullptr); ASSERT_NE(-1, fd); WaitForFusePath(); - ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommands::kInstall); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); struct stat sb; ASSERT_EQ(0, stat(exit_flag_.c_str(), &sb)); @@ -188,7 +188,7 @@ TEST_F(MinadbdServicesTest, SideloadHostService_read_data_from_fuse) { unique_fd fd = daemon_service_to_fd("sideload-host:4096:4096", nullptr); ASSERT_NE(-1, fd); - ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommands::kInstall); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); // Mimic the response from adb host. std::string adb_message(8, '\0'); diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h index 5fb7803e7..b370b7952 100644 --- a/minadbd/minadbd_types.h +++ b/minadbd/minadbd_types.h @@ -43,12 +43,19 @@ enum class MinadbdCommandStatus : uint32_t { kFailure = 1, }; -enum class MinadbdCommands : uint32_t { +enum class MinadbdCommand : uint32_t { kInstall = 0, kUiPrint = 1, - kError = 2, + kRebootAndroid = 2, + kRebootBootloader = 3, + kRebootFastboot = 4, + kRebootRecovery = 5, + kRebootRescue = 6, + + // Last but invalid command. + kError, }; -static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommands)); +static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommand)); static_assert(kMinadbdMessageSize == sizeof(kMinadbdStatusPrefix) - 1 + sizeof(MinadbdCommandStatus)); diff --git a/recovery.cpp b/recovery.cpp index ce29cb27b..5bd9b1728 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -512,6 +512,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case Device::REBOOT: case Device::SHUTDOWN: case Device::REBOOT_BOOTLOADER: + case Device::REBOOT_RESCUE: case Device::ENTER_FASTBOOT: case Device::ENTER_RECOVERY: return chosen_action; @@ -537,32 +538,36 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { if (!ui->IsTextVisible()) return Device::NO_ACTION; break; } + case Device::APPLY_ADB_SIDELOAD: case Device::APPLY_SDCARD: case Device::ENTER_RESCUE: { save_current_log = true; bool adb = true; + Device::BuiltinAction reboot_action; if (chosen_action == Device::ENTER_RESCUE) { // Switch to graphics screen. ui->ShowText(false); - status = ApplyFromAdb(ui, true /* rescue_mode */); - ui->ShowText(true); + status = ApplyFromAdb(ui, true /* rescue_mode */, &reboot_action); } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) { - status = ApplyFromAdb(ui, false /* rescue_mode */); + status = ApplyFromAdb(ui, false /* rescue_mode */, &reboot_action); } else { adb = false; status = ApplyFromSdcard(device, ui); } + ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status); + if (status == INSTALL_REBOOT) { + return reboot_action; + } + if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); copy_logs(save_current_log, has_cache, sehandle); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); } break; } @@ -841,6 +846,9 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorPrint("Supported API: %d\n", kRecoveryApiVersion); int status = INSTALL_SUCCESS; + // next_action indicates the next target to reboot into upon finishing the install. It could be + // overridden to a different reboot target per user request. + Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; if (update_package != nullptr) { // It's not entirely true that we will modify the flash. But we want @@ -924,19 +932,18 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorShowText(true); } - status = ApplyFromAdb(ui, false /* rescue_mode */); + status = ApplyFromAdb(ui, false /* rescue_mode */, &next_action); ui->Print("\nInstall from ADB complete (status: %d).\n", status); if (sideload_auto_reboot) { + status = INSTALL_REBOOT; ui->Print("Rebooting automatically.\n"); } } else if (fsck_unshare_blocks) { @@ -961,23 +968,26 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorIsTextVisible()) { - Device::BuiltinAction temp = prompt_and_wait(device, status); - if (temp != Device::NO_ACTION) { - after = temp; + // Determine the next action. + // - If the state is INSTALL_REBOOT, device will reboot into the target as specified in + // `next_action`. + // - If the recovery menu is visible, prompt and wait for commands. + // - If the state is INSTALL_NONE, wait for commands (e.g. in user build, one manually boots + // into recovery to sideload a package or to wipe the device). + // - In all other cases, reboot the device. Therefore, normal users will observe the device + // rebooting a) immediately upon successful finish (INSTALL_SUCCESS); or b) an "error" screen + // for 5s followed by an automatic reboot. + if (status != INSTALL_REBOOT) { + if (status == INSTALL_NONE || ui->IsTextVisible()) { + Device::BuiltinAction temp = prompt_and_wait(device, status); + if (temp != Device::NO_ACTION) { + next_action = temp; + } } } // Save logs and clean up before rebooting or shutting down. finish_recovery(); - return after; + return next_action; } diff --git a/recovery_main.cpp b/recovery_main.cpp index 38e1db73b..18abff765 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -478,6 +478,11 @@ int main(int argc, char** argv) { android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; + case Device::REBOOT_RESCUE: + ui->Print("Rebooting to rescue...\n"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,rescue"); + break; + case Device::ENTER_FASTBOOT: if (logical_partitions_mapped()) { ui->Print("Partitions may be mounted - rebooting to enter fastboot."); diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h index 8f17639d6..09b5d1f4d 100644 --- a/recovery_ui/include/recovery_ui/device.h +++ b/recovery_ui/include/recovery_ui/device.h @@ -50,7 +50,11 @@ class Device { KEY_INTERRUPTED = 13, ENTER_FASTBOOT = 14, ENTER_RECOVERY = 15, + // ENTER vs REBOOT: The latter will trigger a reboot that uses `rescue` as the reboot target. + // So it goes from rescue -> bootloader -> rescue, whereas ENTER_RESCUE switches from recovery + // -> rescue directly. ENTER_RESCUE = 16, + REBOOT_RESCUE = 17, }; explicit Device(RecoveryUI* ui); -- cgit v1.2.3 From 75321ade8733317bfe7bf0b1850c94b055c8a1c1 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Tue, 23 Apr 2019 11:46:25 -0700 Subject: Parse BCB command to enter rescue mode. bootloader will set `boot-rescue` in BCB command field to indicate booting into rescue mode. This CL adds the matching parsing code. This CL changes the on-screen UI to display the default image while waiting for each sideload / rescue command. It also changes the minadbd reboot handlers to use REBOOT_ instead of the previous ENTER_ actions. This ensures a reboot going through bootloader, which may load a newly installed bootloader/recovery. Bug: 128505466 Bug: 128415917 Test: Boot into rescue mode. Run `adb rescue getprop` and `adb rescue install`. Check the UI. Then run `adb reboot rescue`. Change-Id: I5b7de9dfd898ed8e14bea0d4ad7385a9bae26e94 Merged-In: I5b7de9dfd898ed8e14bea0d4ad7385a9bae26e94 (cherry picked from commit d9cb014d431fee946308bdb8979c8e8fa74b582f) --- install/adb_install.cpp | 24 +++++++++++++++--------- recovery.cpp | 16 +++++++++++++--- recovery_main.cpp | 32 ++++++++++++++++++++++++++++---- recovery_ui/include/recovery_ui/device.h | 11 +++++++---- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/install/adb_install.cpp b/install/adb_install.cpp index d79f6f4b0..9dfe0407f 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -121,19 +121,20 @@ static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) { static auto AdbRebootHandler(MinadbdCommand command, int* result, Device::BuiltinAction* reboot_action) { + // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows + // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly + // installed bootloader/recovery image. switch (command) { case MinadbdCommand::kRebootBootloader: *reboot_action = Device::REBOOT_BOOTLOADER; break; case MinadbdCommand::kRebootFastboot: - *reboot_action = Device::ENTER_FASTBOOT; + *reboot_action = Device::REBOOT_FASTBOOT; break; case MinadbdCommand::kRebootRecovery: - *reboot_action = Device::ENTER_RECOVERY; + *reboot_action = Device::REBOOT_RECOVERY; break; case MinadbdCommand::kRebootRescue: - // Use Device::REBOOT_RESCUE instead of Device::ENTER_RESCUE. This allows rebooting back into - // rescue mode (potentially using a newly installed recovery image). *reboot_action = Device::REBOOT_RESCUE; break; case MinadbdCommand::kRebootAndroid: @@ -180,7 +181,7 @@ static bool HandleMessageFromMinadbd(int socket_fd, // TODO(xunchang) add a wrapper function and kill the minadbd service there. static void ListenAndExecuteMinadbdCommands( - pid_t minadbd_pid, android::base::unique_fd&& socket_fd, + RecoveryUI* ui, pid_t minadbd_pid, android::base::unique_fd&& socket_fd, const std::map& command_map) { android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC)); if (epoll_fd == -1) { @@ -203,6 +204,10 @@ static void ListenAndExecuteMinadbdCommands( // Set the timeout to be 300s when waiting for minadbd commands. constexpr int TIMEOUT_MILLIS = 300 * 1000; while (true) { + // Reset the progress bar and the background image before each command. + ui->SetProgressType(RecoveryUI::EMPTY); + ui->SetBackground(RecoveryUI::NO_COMMAND); + // Poll for the status change of the socket_fd, and handle the message if the fd is ready to // read. int event_count = @@ -266,7 +271,8 @@ static void ListenAndExecuteMinadbdCommands( // b11. exit the listening loop // static void CreateMinadbdServiceAndExecuteCommands( - const std::map& command_map, bool rescue_mode) { + RecoveryUI* ui, const std::map& command_map, + bool rescue_mode) { signal(SIGPIPE, SIG_IGN); android::base::unique_fd recovery_socket; @@ -305,8 +311,8 @@ static void CreateMinadbdServiceAndExecuteCommands( return; } - std::thread listener_thread(ListenAndExecuteMinadbdCommands, child, std::move(recovery_socket), - std::ref(command_map)); + std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child, + std::move(recovery_socket), std::ref(command_map)); if (listener_thread.joinable()) { listener_thread.join(); } @@ -357,7 +363,7 @@ int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) }, }; - CreateMinadbdServiceAndExecuteCommands(command_map, rescue_mode); + CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode); // Clean up before switching to the older state, for example setting the state // to none sets sys/class/android_usb/android0/enable to 0. diff --git a/recovery.cpp b/recovery.cpp index 5bd9b1728..f9b3bfc0b 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -509,12 +509,14 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case Device::NO_ACTION: break; + case Device::ENTER_FASTBOOT: + case Device::ENTER_RECOVERY: case Device::REBOOT: - case Device::SHUTDOWN: case Device::REBOOT_BOOTLOADER: + case Device::REBOOT_FASTBOOT: + case Device::REBOOT_RECOVERY: case Device::REBOOT_RESCUE: - case Device::ENTER_FASTBOOT: - case Device::ENTER_RECOVERY: + case Device::SHUTDOWN: return chosen_action; case Device::WIPE_DATA: @@ -728,6 +730,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorPrint("Rebooting automatically.\n"); } + } else if (rescue) { + save_current_log = true; + status = ApplyFromAdb(ui, true /* rescue_mode */, &next_action); + ui->Print("\nInstall from ADB complete (status: %d).\n", status); } else if (fsck_unshare_blocks) { if (!do_fsck_unshare_blocks()) { status = INSTALL_ERROR; diff --git a/recovery_main.cpp b/recovery_main.cpp index 18abff765..de8ac1f42 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -155,9 +155,11 @@ static std::vector get_args(const int argc, char** const argv) { } // Finally, if no arguments were specified, check whether we should boot - // into fastboot. + // into fastboot or rescue mode. if (args.size() == 1 && boot_command == "boot-fastboot") { args.emplace_back("--fastboot"); + } else if (args.size() == 1 && boot_command == "boot-rescue") { + args.emplace_back("--rescue"); } return args; @@ -470,6 +472,7 @@ int main(int argc, char** argv) { switch (ret) { case Device::SHUTDOWN: ui->Print("Shutting down...\n"); + // TODO: Move all the reboots to reboot(), which should conditionally set quiescent flag. android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); break; @@ -478,11 +481,32 @@ int main(int argc, char** argv) { android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; - case Device::REBOOT_RESCUE: - ui->Print("Rebooting to rescue...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,rescue"); + case Device::REBOOT_FASTBOOT: + ui->Print("Rebooting to recovery/fastboot...\n"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot"); break; + case Device::REBOOT_RECOVERY: + ui->Print("Rebooting to recovery...\n"); + reboot("reboot,recovery"); + break; + + case Device::REBOOT_RESCUE: { + // Not using `reboot("reboot,rescue")`, as it requires matching support in kernel and/or + // bootloader. + bootloader_message boot = {}; + strlcpy(boot.command, "boot-rescue", sizeof(boot.command)); + std::string err; + if (!write_bootloader_message(boot, &err)) { + LOG(ERROR) << "Failed to write bootloader message: " << err; + // Stay under recovery on failure. + continue; + } + ui->Print("Rebooting to recovery/rescue...\n"); + reboot("reboot,recovery"); + break; + } + case Device::ENTER_FASTBOOT: if (logical_partitions_mapped()) { ui->Print("Partitions may be mounted - rebooting to enter fastboot."); diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h index 09b5d1f4d..7c76cdb0a 100644 --- a/recovery_ui/include/recovery_ui/device.h +++ b/recovery_ui/include/recovery_ui/device.h @@ -33,6 +33,10 @@ class Device { static constexpr const int kHighlightDown = -3; static constexpr const int kInvokeItem = -4; + // ENTER vs REBOOT: The latter will trigger a reboot that goes through bootloader, which allows + // using a new bootloader / recovery image if applicable. For example, REBOOT_RESCUE goes from + // rescue -> bootloader -> rescue, whereas ENTER_RESCUE switches from recovery -> rescue + // directly. enum BuiltinAction { NO_ACTION = 0, REBOOT = 1, @@ -50,11 +54,10 @@ class Device { KEY_INTERRUPTED = 13, ENTER_FASTBOOT = 14, ENTER_RECOVERY = 15, - // ENTER vs REBOOT: The latter will trigger a reboot that uses `rescue` as the reboot target. - // So it goes from rescue -> bootloader -> rescue, whereas ENTER_RESCUE switches from recovery - // -> rescue directly. ENTER_RESCUE = 16, - REBOOT_RESCUE = 17, + REBOOT_FASTBOOT = 17, + REBOOT_RECOVERY = 18, + REBOOT_RESCUE = 19, }; explicit Device(RecoveryUI* ui); -- cgit v1.2.3 From 5a1916b9bedce04fb79f64b61def1bc53fffb11d Mon Sep 17 00:00:00 2001 From: xunchang Date: Mon, 22 Apr 2019 12:18:14 -0700 Subject: Support wipe command in rescue mode Bug: 131037235 Test: unit tests pass, run `adb rescue wipe` Change-Id: I22668f2c98fe2d9195d2561f961c28a7c08e712c (cherry picked from commit fedeef6f6d1f7b8f1e5a8b9e77f8dc21ef6b3c95) --- install/adb_install.cpp | 23 ++++++++++++------ install/include/install/adb_install.h | 3 +-- minadbd/minadbd_services.cpp | 46 ++++++++++++++++++++++++++++++----- minadbd/minadbd_services_test.cpp | 2 +- minadbd/minadbd_types.h | 4 ++- recovery.cpp | 8 +++--- 6 files changed, 64 insertions(+), 22 deletions(-) diff --git a/install/adb_install.cpp b/install/adb_install.cpp index 9dfe0407f..4dd1f1b09 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -43,6 +43,7 @@ #include "fuse_sideload.h" #include "install/install.h" +#include "install/wipe_data.h" #include "minadbd_types.h" #include "otautil/sysutil.h" #include "recovery_ui/device.h" @@ -330,7 +331,7 @@ static void CreateMinadbdServiceAndExecuteCommands( signal(SIGPIPE, SIG_DFL); } -int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action) { +int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) { // Save the usb state to restore after the sideload operation. std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); // Clean up state and stop adbd. @@ -339,13 +340,7 @@ int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot return INSTALL_ERROR; } - if (!rescue_mode) { - ui->Print( - "\n\nNow send the package you want to apply\n" - "to the device with \"adb sideload \"...\n"); - } else { - ui->Print("\n\nWaiting for rescue commands...\n"); - } + RecoveryUI* ui = device->GetUI(); int install_result = INSTALL_ERROR; std::map command_map{ @@ -363,6 +358,18 @@ int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) }, }; + if (!rescue_mode) { + ui->Print( + "\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload \"...\n"); + } else { + ui->Print("\n\nWaiting for rescue commands...\n"); + command_map.emplace(MinadbdCommand::kWipeData, [&device]() { + bool result = WipeData(device, false); + return std::make_pair(result, true); + }); + } + CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode); // Clean up before switching to the older state, for example setting the state diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h index 49b32b54f..3a0a81747 100644 --- a/install/include/install/adb_install.h +++ b/install/include/install/adb_install.h @@ -17,9 +17,8 @@ #pragma once #include -#include // Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum // InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with // the reboot target set in reboot_action. -int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action); +int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action); diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 9b1999d90..1c4c0f494 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -104,7 +104,7 @@ static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, if (pieces.size() != 2 || !android::base::ParseInt(pieces[0], &file_size) || file_size <= 0 || !android::base::ParseInt(pieces[1], &block_size) || block_size <= 0) { LOG(ERROR) << "bad sideload-host arguments: " << args; - return kMinadbdPackageSizeError; + return kMinadbdHostCommandArgumentError; } LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size; @@ -124,17 +124,17 @@ static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, return kMinadbdMessageFormatError; } - // Signal host-side adb to stop. For sideload mode, we always send kSideloadServiceExitSuccess + // Signal host-side adb to stop. For sideload mode, we always send kMinadbdServicesExitSuccess // (i.e. "DONEDONE") regardless of the install result. For rescue mode, we send failure message on // install error. if (!rescue_mode || *status == MinadbdCommandStatus::kSuccess) { - if (!android::base::WriteFully(sfd, kSideloadServiceExitSuccess, - strlen(kSideloadServiceExitSuccess))) { + if (!android::base::WriteFully(sfd, kMinadbdServicesExitSuccess, + strlen(kMinadbdServicesExitSuccess))) { return kMinadbdHostSocketIOError; } } else { - if (!android::base::WriteFully(sfd, kSideloadServiceExitFailure, - strlen(kSideloadServiceExitFailure))) { + if (!android::base::WriteFully(sfd, kMinadbdServicesExitFailure, + strlen(kMinadbdServicesExitFailure))) { return kMinadbdHostSocketIOError; } } @@ -200,6 +200,34 @@ static void RebootHostService(unique_fd /* sfd */, const std::string& target) { } } +static void WipeDeviceService(unique_fd fd, const std::string& args) { + auto pieces = android::base::Split(args, ":"); + if (pieces.size() != 2 || pieces[0] != "userdata") { + LOG(ERROR) << "Failed to parse wipe device command arguments " << args; + exit(kMinadbdHostCommandArgumentError); + } + + size_t message_size; + if (!android::base::ParseUint(pieces[1], &message_size) || + message_size < strlen(kMinadbdServicesExitSuccess)) { + LOG(ERROR) << "Failed to parse wipe device message size in " << args; + exit(kMinadbdHostCommandArgumentError); + } + + WriteCommandToFd(MinadbdCommand::kWipeData, minadbd_socket); + MinadbdCommandStatus status; + if (!WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } + + std::string response = (status == MinadbdCommandStatus::kSuccess) ? kMinadbdServicesExitSuccess + : kMinadbdServicesExitFailure; + response += std::string(message_size - response.size(), '\0'); + if (!android::base::WriteFully(fd, response.c_str(), response.size())) { + exit(kMinadbdHostSocketIOError); + } +} + unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) { // Common services that are supported both in sideload and rescue modes. if (ConsumePrefix(&name, "reboot:")) { @@ -225,7 +253,13 @@ unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport * std::string args(name); return create_service_thread( "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args)); + } else if (ConsumePrefix(&name, "rescue-wipe:")) { + // rescue-wipe:target: + std::string args(name); + return create_service_thread("rescue-wipe", + std::bind(WipeDeviceService, std::placeholders::_1, args)); } + return unique_fd{}; } diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp index 593180bb3..f87873792 100644 --- a/minadbd/minadbd_services_test.cpp +++ b/minadbd/minadbd_services_test.cpp @@ -122,7 +122,7 @@ class MinadbdServicesTest : public ::testing::Test { TEST_F(MinadbdServicesTest, SideloadHostService_wrong_size_argument) { ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:abc:4096"), - ::testing::ExitedWithCode(kMinadbdPackageSizeError), ""); + ::testing::ExitedWithCode(kMinadbdHostCommandArgumentError), ""); } TEST_F(MinadbdServicesTest, SideloadHostService_wrong_block_size) { diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h index b370b7952..99fd45e83 100644 --- a/minadbd/minadbd_types.h +++ b/minadbd/minadbd_types.h @@ -30,7 +30,7 @@ enum MinadbdErrorCode : int { kMinadbdSocketIOError = 2, kMinadbdMessageFormatError = 3, kMinadbdAdbVersionError = 4, - kMinadbdPackageSizeError = 5, + kMinadbdHostCommandArgumentError = 5, kMinadbdFuseStartError = 6, kMinadbdUnsupportedCommandError = 7, kMinadbdCommandExecutionError = 8, @@ -51,6 +51,8 @@ enum class MinadbdCommand : uint32_t { kRebootFastboot = 4, kRebootRecovery = 5, kRebootRescue = 6, + kWipeCache = 7, + kWipeData = 8, // Last but invalid command. kError, diff --git a/recovery.cpp b/recovery.cpp index f9b3bfc0b..5fc673ec2 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -551,9 +551,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { if (chosen_action == Device::ENTER_RESCUE) { // Switch to graphics screen. ui->ShowText(false); - status = ApplyFromAdb(ui, true /* rescue_mode */, &reboot_action); + status = ApplyFromAdb(device, true /* rescue_mode */, &reboot_action); } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) { - status = ApplyFromAdb(ui, false /* rescue_mode */, &reboot_action); + status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action); } else { adb = false; status = ApplyFromSdcard(device, ui); @@ -946,7 +946,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorShowText(true); } - status = ApplyFromAdb(ui, false /* rescue_mode */, &next_action); + status = ApplyFromAdb(device, false /* rescue_mode */, &next_action); ui->Print("\nInstall from ADB complete (status: %d).\n", status); if (sideload_auto_reboot) { status = INSTALL_REBOOT; @@ -954,7 +954,7 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorPrint("\nInstall from ADB complete (status: %d).\n", status); } else if (fsck_unshare_blocks) { if (!do_fsck_unshare_blocks()) { -- cgit v1.2.3 From 9681eef5ff179980ae0d1c5437894f34ea7cfe87 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sat, 4 May 2019 18:39:11 -0700 Subject: Import translations. DO NOT MERGE Auto-generated-cl: translation import Bug: 64712476 Change-Id: I4933222253f1bde2daab4726be971c6d7008a0e4 --- tools/recovery_l10n/res/values-in/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml index 15a78ec48..43c9deb94 100644 --- a/tools/recovery_l10n/res/values-in/strings.xml +++ b/tools/recovery_l10n/res/values-in/strings.xml @@ -9,6 +9,6 @@ "Tidak dapat memuat sistem Android. Data Anda mungkin rusak. Jika terus mendapatkan pesan ini, Anda mungkin perlu melakukan reset ke setelan pabrik dan menghapus semua data pengguna yang disimpan di perangkat ini." "Coba lagi" "Reset ke setelan pabrik" - "Wipe semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!" + "Hapus total semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!" "Batal" -- cgit v1.2.3 From 35e0f6d290ea7c12164bcfaf1b857d965e943938 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 16 May 2019 14:42:42 -0700 Subject: Add misc_writer. bootloader_message.h currently divides /misc into four segments. The space between 2K and 16K is reserved for vendor use (e.g. bootloader persists flags). This CL adds a vendor tool "misc_writer", to allow writing data to the vendor space in /misc, before getting a dedicated HAL for accessing /misc partition (b/131775112). Targets need to explicitly include the module, then invoke the executable to write data. For example, the following command will write 3-byte data ("0xABCDEF") to offset 4 in vendor space (i.e. 2048 + 4 in /misc). $ /vendor/bin/misc_writer --vendor-space-offset 4 --hex-string 0xABCDEF Bug: 132906936 Test: Run recovery_unit_test on crosshatch. Test: Call the command via init.hardware.rc on crosshatch. Check that the call finishes successfully. Then check the contents written to /misc (`dd bs=1 skip=2048 if=/dev/block/sda2 count=32 | xxd`). Change-Id: I79548fc63fc79b705a0320868690569c3106949f Merged-In: I79548fc63fc79b705a0320868690569c3106949f (cherry picked from commit 7ae01698424cc3adf635c324961b1405594f5156) --- bootloader_message/Android.bp | 25 ++++- bootloader_message/bootloader_message.cpp | 42 ++++++++ .../bootloader_message/bootloader_message.h | 13 ++- misc_writer/Android.bp | 40 ++++++++ misc_writer/misc_writer.cpp | 106 +++++++++++++++++++++ tests/component/bootloader_message_test.cpp | 38 ++++++++ 6 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 misc_writer/Android.bp create mode 100644 misc_writer/misc_writer.cpp diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp index 5cd21323c..450dad08b 100644 --- a/bootloader_message/Android.bp +++ b/bootloader_message/Android.bp @@ -14,9 +14,8 @@ // limitations under the License. // -cc_library { - name: "libbootloader_message", - recovery_available: true, +cc_defaults { + name: "libbootloader_message_defaults", srcs: ["bootloader_message.cpp"], cflags: [ "-Wall", @@ -24,7 +23,25 @@ cc_library { ], shared_libs: [ "libbase", - "libfs_mgr", + ], + static_libs: [ + "libfstab", ], export_include_dirs: ["include"], } + +cc_library { + name: "libbootloader_message", + defaults: [ + "libbootloader_message_defaults", + ], + recovery_available: true, +} + +cc_library_static { + name: "libbootloader_message_vendor", + defaults: [ + "libbootloader_message_defaults", + ], + vendor: true, +} diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index 8c1d63bdd..c1ebeaa82 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -32,7 +33,17 @@ using android::fs_mgr::Fstab; using android::fs_mgr::ReadDefaultFstab; +static std::string g_misc_device_for_test; + +// Exposed for test purpose. +void SetMiscBlockDeviceForTest(std::string_view misc_device) { + g_misc_device_for_test = misc_device; +} + static std::string get_misc_blk_device(std::string* err) { + if (!g_misc_device_for_test.empty()) { + return g_misc_device_for_test; + } Fstab fstab; if (!ReadDefaultFstab(&fstab)) { *err = "failed to read default fstab"; @@ -228,6 +239,37 @@ bool write_wipe_package(const std::string& package_data, std::string* err) { WIPE_PACKAGE_OFFSET_IN_MISC, err); } +static bool OffsetAndSizeInVendorSpace(size_t offset, size_t size) { + auto total_size = WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC; + return size <= total_size && offset <= total_size - size; +} + +bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err) { + if (!OffsetAndSizeInVendorSpace(offset, size)) { + *err = android::base::StringPrintf("Out of bound read (offset %zu size %zu)", offset, size); + return false; + } + auto misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return read_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset, + err); +} + +bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err) { + if (!OffsetAndSizeInVendorSpace(offset, size)) { + *err = android::base::StringPrintf("Out of bound write (offset %zu size %zu)", offset, size); + return false; + } + auto misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset, + err); +} + extern "C" bool write_reboot_bootloader(void) { std::string err; return write_reboot_bootloader(&err); diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index 95c19ae54..95dd8f4c9 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -28,8 +28,9 @@ // 16K - 64K Used by uncrypt and recovery to store wipe_package for A/B devices // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they // are not configurable without changing all of them. -static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; -static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; +constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; +constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024; +constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; /* Bootloader Message (2-KiB) * @@ -228,6 +229,14 @@ bool read_wipe_package(std::string* package_data, size_t size, std::string* err) // Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC). bool write_wipe_package(const std::string& package_data, std::string* err); +// Reads data from the vendor space in /misc partition, with the given offset and size. Note that +// offset is in relative to the start of vendor space. +bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err); + +// Writes the given data to the vendor space in /misc partition, at the given offset. Note that +// offset is in relative to the start of the vendor space. +bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err); + #else #include diff --git a/misc_writer/Android.bp b/misc_writer/Android.bp new file mode 100644 index 000000000..567143c79 --- /dev/null +++ b/misc_writer/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2019 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. +// + +cc_binary { + name: "misc_writer", + vendor: true, + + srcs: [ + "misc_writer.cpp", + ], + + cpp_std: "experimental", + + cflags: [ + "-Wall", + "-Werror", + ], + + shared_libs: [ + "libbase", + ], + + static_libs: [ + "libbootloader_message_vendor", + "libfstab", + ], +} diff --git a/misc_writer/misc_writer.cpp b/misc_writer/misc_writer.cpp new file mode 100644 index 000000000..1d9702ebf --- /dev/null +++ b/misc_writer/misc_writer.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2019 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 +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace std::string_literals; + +static std::vector ParseHexString(std::string_view hex_string) { + auto length = hex_string.size(); + if (length % 2 != 0 || length == 0) { + return {}; + } + + std::vector result(length / 2); + for (size_t i = 0; i < length / 2; i++) { + auto sub = "0x" + std::string(hex_string.substr(i * 2, 2)); + if (!android::base::ParseUint(sub, &result[i])) { + return {}; + } + } + return result; +} + +static int Usage(std::string_view name) { + std::cerr << name << " usage:\n"; + std::cerr << name << " [--vendor-space-offset ] --hex-string 0xABCDEF\n"; + std::cerr << "Writes the given hex string to the specified offset in vendor space in /misc " + "partition. Offset defaults to 0 if unspecified.\n"; + return EXIT_FAILURE; +} + +// misc_writer is a vendor tool that writes data to the vendor space in /misc. +int main(int argc, char** argv) { + constexpr struct option OPTIONS[] = { + { "vendor-space-offset", required_argument, nullptr, 0 }, + { "hex-string", required_argument, nullptr, 0 }, + { nullptr, 0, nullptr, 0 }, + }; + + // Offset defaults to 0 if unspecified. + size_t offset = 0; + std::string_view hex_string; + + int arg; + int option_index; + while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { + if (arg != 0) { + LOG(ERROR) << "Invalid command argument"; + return Usage(argv[0]); + } + auto option_name = OPTIONS[option_index].name; + if (option_name == "vendor-space-offset"s) { + if (!android::base::ParseUint(optarg, &offset)) { + LOG(ERROR) << "Failed to parse the offset: " << optarg; + return Usage(argv[0]); + } + } else if (option_name == "hex-string"s) { + hex_string = optarg; + } + } + + if (hex_string.starts_with("0x") || hex_string.starts_with("0X")) { + hex_string = hex_string.substr(2); + } + if (hex_string.empty()) { + LOG(ERROR) << "Invalid input hex string: " << hex_string; + return Usage(argv[0]); + } + + auto data = ParseHexString(hex_string); + if (data.empty()) { + LOG(ERROR) << "Failed to parse the input hex string: " << hex_string; + return EXIT_FAILURE; + } + if (std::string err; !WriteMiscPartitionVendorSpace(data.data(), data.size(), offset, &err)) { + LOG(ERROR) << "Failed to write to misc partition: " << err; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp index b005d199c..95d875e69 100644 --- a/tests/component/bootloader_message_test.cpp +++ b/tests/component/bootloader_message_test.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include @@ -22,6 +23,10 @@ #include #include +using namespace std::string_literals; + +extern void SetMiscBlockDeviceForTest(std::string_view misc_device); + TEST(BootloaderMessageTest, read_and_write_bootloader_message) { TemporaryFile temp_misc; @@ -114,3 +119,36 @@ TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_long) { std::string(boot.reserved, sizeof(boot.reserved))); } +TEST(BootloaderMessageTest, WriteMiscPartitionVendorSpace) { + TemporaryFile temp_misc; + ASSERT_TRUE(android::base::WriteStringToFile(std::string(4096, '\x00'), temp_misc.path)); + SetMiscBlockDeviceForTest(temp_misc.path); + + constexpr std::string_view kTestMessage = "kTestMessage"; + std::string err; + ASSERT_TRUE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), 0, &err)); + + std::string message; + message.resize(kTestMessage.size()); + ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err)); + ASSERT_EQ(kTestMessage, message); + + // Write with an offset. + ASSERT_TRUE(WriteMiscPartitionVendorSpace("\x00\x00", 2, 5, &err)); + ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err)); + ASSERT_EQ("kTest\x00\x00ssage"s, message); + + // Write with the right size. + auto start_offset = + WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC - kTestMessage.size(); + ASSERT_TRUE( + WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), start_offset, &err)); + + // Out-of-bound write. + ASSERT_FALSE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), + start_offset + 1, &err)); + + // Message won't fit. + std::string long_message(WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC + 1, 'a'); + ASSERT_FALSE(WriteMiscPartitionVendorSpace(long_message.data(), long_message.size(), 0, &err)); +} -- cgit v1.2.3 From 46ec20b69298ff267fcc9f42a742015fb541b4a4 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 14 Jun 2019 09:43:24 -0700 Subject: libprocessgroup users use libcutils libprocessgroup symbols are being moved into libcutils in order to optimize linking/memory usage. libprocessgroup will no longer be required in the future (however removing references to it will come separately). Since libcutils is used statically here, the dependencies of libprocessgroup need to be explicitly listed. Bug: 135145426 Test: boot Change-Id: I91c082f0fa2f5f5c52751065cd5f50f5cb965b23 --- updater/Android.bp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/updater/Android.bp b/updater/Android.bp index b80cdb3a0..4e87db2dd 100644 --- a/updater/Android.bp +++ b/updater/Android.bp @@ -45,6 +45,8 @@ cc_defaults { "libcrypto", "libcrypto_utils", "libcutils", + "libcgrouprc", + "libcgrouprc_format", "libutils", "libtune2fs", -- cgit v1.2.3 From cdbd84de26bb213fee6d8560976d9b99eea75f77 Mon Sep 17 00:00:00 2001 From: Zhijun He Date: Wed, 19 Jun 2019 04:44:04 +0000 Subject: Revert "libprocessgroup users use libcutils" This reverts commit 46ec20b69298ff267fcc9f42a742015fb541b4a4. Reason for revert: breaks all camera use cases Bug: 135568875 Change-Id: I86747c0df5489f80d1966dd07669637597fb2b00 --- updater/Android.bp | 2 -- 1 file changed, 2 deletions(-) diff --git a/updater/Android.bp b/updater/Android.bp index 4e87db2dd..b80cdb3a0 100644 --- a/updater/Android.bp +++ b/updater/Android.bp @@ -45,8 +45,6 @@ cc_defaults { "libcrypto", "libcrypto_utils", "libcutils", - "libcgrouprc", - "libcgrouprc_format", "libutils", "libtune2fs", -- cgit v1.2.3 From 9c207203b2c8a06603b23b6525621d22feb9ef1d Mon Sep 17 00:00:00 2001 From: Zhijun He Date: Wed, 19 Jun 2019 04:44:04 +0000 Subject: Revert "libprocessgroup users use libcutils" This reverts commit 46ec20b69298ff267fcc9f42a742015fb541b4a4. Reason for revert: breaks all camera use cases Bug: 135568875 Change-Id: I86747c0df5489f80d1966dd07669637597fb2b00 (cherry picked from commit cdbd84de26bb213fee6d8560976d9b99eea75f77) --- updater/Android.bp | 2 -- 1 file changed, 2 deletions(-) diff --git a/updater/Android.bp b/updater/Android.bp index 4e87db2dd..b80cdb3a0 100644 --- a/updater/Android.bp +++ b/updater/Android.bp @@ -45,8 +45,6 @@ cc_defaults { "libcrypto", "libcrypto_utils", "libcutils", - "libcgrouprc", - "libcgrouprc_format", "libutils", "libtune2fs", -- cgit v1.2.3 From 793e8943eb1f1c968923de51b18403e717a05c79 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 8 Jul 2019 18:07:22 -0700 Subject: minadbd sends heartbeat to rescue service for getprop command. We start minadbd and rescue services in two processes. In particular, minadbd handles the requests from host, then communicates with rescue service to do install/wipe works. When resuce service doesn't see any request in a pre-defined timeout (currently 300s), rescue service will exit to avoid endless waiting. This CL changes minadbd to additionally send a no-op command to rescue service as a heartbeat signal, so that host side can finish time-consuming operations (e.g. downloading over network) while keeping rescue service alive. Bug: 136457446 Test: Enter resuce mode on blueline. Send `adb rescue getprop ro.build.fingerprint` and check that rescue service doesn't exit. Test: Stop sending the getprop command. Check that rescue service exits after 300s. Change-Id: Ib9d5ed710cfa94ecfe6cf393a71a0b67b2539531 Merged-In: Ib9d5ed710cfa94ecfe6cf393a71a0b67b2539531 (cherry picked from commit 2223e6a9f8bf24b023e8ae3103b50c37def3147e) (cherry picked from commit 0bbb2ed53eb4dc4ae8d447062482f9eda5ef9a91) (cherry picked from commit dd0158ac6016f2853d1af336e345980e06144abd) --- install/adb_install.cpp | 4 +++- minadbd/minadbd_services.cpp | 8 ++++++++ minadbd/minadbd_types.h | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/install/adb_install.cpp b/install/adb_install.cpp index 4dd1f1b09..9497df501 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -363,11 +363,13 @@ int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot "\n\nNow send the package you want to apply\n" "to the device with \"adb sideload \"...\n"); } else { - ui->Print("\n\nWaiting for rescue commands...\n"); command_map.emplace(MinadbdCommand::kWipeData, [&device]() { bool result = WipeData(device, false); return std::make_pair(result, true); }); + command_map.emplace(MinadbdCommand::kNoOp, []() { return std::make_pair(true, true); }); + + ui->Print("\n\nWaiting for rescue commands...\n"); } CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode); diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 1c4c0f494..6c10274dc 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -173,6 +173,14 @@ static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { if (!android::base::WriteFully(sfd, result.data(), result.size())) { exit(kMinadbdHostSocketIOError); } + + // Send heartbeat signal to keep the rescue service alive. + if (!WriteCommandToFd(MinadbdCommand::kNoOp, minadbd_socket)) { + exit(kMinadbdSocketIOError); + } + if (MinadbdCommandStatus status; !WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } } // Reboots into the given target. We don't reboot directly from minadbd, but going through recovery diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h index 99fd45e83..002523f1f 100644 --- a/minadbd/minadbd_types.h +++ b/minadbd/minadbd_types.h @@ -53,6 +53,7 @@ enum class MinadbdCommand : uint32_t { kRebootRescue = 6, kWipeCache = 7, kWipeData = 8, + kNoOp = 9, // Last but invalid command. kError, -- cgit v1.2.3