diff options
68 files changed, 1017 insertions, 338 deletions
@@ -16,58 +16,140 @@ #include "device.h" -static const char* MENU_ITEMS[] = { - "Reboot system now", -#ifdef DOWNLOAD_MODE - "Reboot to download mode", -#else - "Reboot to bootloader", -#endif - "Apply update", +#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A))) + +// *** Main menu *** +static const menu_type_t MAIN_MENU_TYPE = MT_GRID; +static const MenuItem MAIN_MENU_ITEMS[] = { + MenuItem("Reboot", "ic_reboot", "ic_reboot_sel"), + MenuItem("Apply update", "ic_system_update", "ic_system_update_sel"), + MenuItem("Factory reset", "ic_factory_reset", "ic_factory_reset_sel"), + MenuItem("Advanced", "ic_options_advanced", "ic_options_advanced_sel"), +}; +static const MenuItemVector main_menu_items_ = + MenuItemVector(MAIN_MENU_ITEMS, + MAIN_MENU_ITEMS + ARRAY_SIZE(MAIN_MENU_ITEMS)); +static const Device::BuiltinAction MAIN_MENU_ACTIONS[] = { + Device::REBOOT, + Device::APPLY_UPDATE, + Device::WIPE_MENU, + Device::ADVANCED_MENU, +}; +static const Device::MenuActionVector main_menu_actions_ = + Device::MenuActionVector(MAIN_MENU_ACTIONS, + MAIN_MENU_ACTIONS + ARRAY_SIZE(MAIN_MENU_ACTIONS)); +static_assert(ARRAY_SIZE(MAIN_MENU_ITEMS) == + ARRAY_SIZE(MAIN_MENU_ACTIONS), + "MAIN_MENU_ITEMS and MAIN_MENU_ACTIONS should have the same length."); + + +// *** Wipe menu *** +static const menu_type_t WIPE_MENU_TYPE = MT_LIST; +static const MenuItem WIPE_MENU_ITEMS[] = { #ifndef RELEASE_BUILD - "Wipe data (keep media)", + MenuItem("Wipe data (keep media)"), #endif - "Full factory reset", + MenuItem("Full factory reset"), #ifndef AB_OTA_UPDATER - "Wipe cache partition", -#endif // !AB_OTA_UPDATER - "Wipe system partition", - "Mount /system", - "View recovery logs", - "Run graphics test", - "Power off", - NULL, + MenuItem("Wipe cache"), +#endif + MenuItem("Wipe system"), }; - -static const Device::BuiltinAction MENU_ACTIONS[] = { - Device::REBOOT, - Device::REBOOT_BOOTLOADER, - Device::APPLY_UPDATE, +static const MenuItemVector wipe_menu_items_ = + MenuItemVector(WIPE_MENU_ITEMS, + WIPE_MENU_ITEMS + ARRAY_SIZE(WIPE_MENU_ITEMS)); +static const Device::BuiltinAction WIPE_MENU_ACTIONS[] = { #ifndef RELEASE_BUILD - Device::WIPE_DATA, + Device::WIPE_DATA, #endif - Device::WIPE_FULL, + Device::WIPE_FULL, #ifndef AB_OTA_UPDATER - Device::WIPE_CACHE, -#endif // !AB_OTA_UPDATER - Device::WIPE_SYSTEM, - Device::MOUNT_SYSTEM, - Device::VIEW_RECOVERY_LOGS, - Device::RUN_GRAPHICS_TEST, - Device::SHUTDOWN, + Device::WIPE_CACHE, +#endif + Device::WIPE_SYSTEM, }; +static const Device::MenuActionVector wipe_menu_actions_ = + Device::MenuActionVector(WIPE_MENU_ACTIONS, + WIPE_MENU_ACTIONS + ARRAY_SIZE(WIPE_MENU_ACTIONS)); +static_assert(ARRAY_SIZE(WIPE_MENU_ITEMS) == + ARRAY_SIZE(WIPE_MENU_ACTIONS), + "WIPE_MENU_ITEMS and WIPE_MENU_ACTIONS should have the same length."); + -static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) == - sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1, - "MENU_ITEMS and MENU_ACTIONS should have the same length, " - "except for the extra NULL entry in MENU_ITEMS."); +// *** Advanced menu +static const menu_type_t ADVANCED_MENU_TYPE = MT_LIST; -const char* const* Device::GetMenuItems() { - return MENU_ITEMS; +static const MenuItem ADVANCED_MENU_ITEMS[] = { +#ifdef DOWNLOAD_MODE + MenuItem("Reboot to download mode"), +#else + MenuItem("Reboot to bootloader"), +#endif + MenuItem("Mount system"), + MenuItem("View logs"), + MenuItem("Run graphics test"), + MenuItem("Power off"), +}; +static const MenuItemVector advanced_menu_items_ = + MenuItemVector(ADVANCED_MENU_ITEMS, + ADVANCED_MENU_ITEMS + ARRAY_SIZE(ADVANCED_MENU_ITEMS)); + +static const Device::BuiltinAction ADVANCED_MENU_ACTIONS[] = { + Device::REBOOT_BOOTLOADER, + Device::MOUNT_SYSTEM, + Device::VIEW_RECOVERY_LOGS, + Device::RUN_GRAPHICS_TEST, + Device::SHUTDOWN, +}; +static const Device::MenuActionVector advanced_menu_actions_ = + Device::MenuActionVector(ADVANCED_MENU_ACTIONS, + ADVANCED_MENU_ACTIONS + ARRAY_SIZE(ADVANCED_MENU_ACTIONS)); + +static_assert(ARRAY_SIZE(ADVANCED_MENU_ITEMS) == + ARRAY_SIZE(ADVANCED_MENU_ACTIONS), + "ADVANCED_MENU_ITEMS and ADVANCED_MENU_ACTIONS should have the same length."); + + +Device::Device(RecoveryUI* ui) : + ui_(ui) +{ + GoHome(); } Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { - return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position]; + if (menu_position < 0) { + if (menu_position == Device::kGoBack || + menu_position == Device::kGoHome) { + // Assume only two menu levels, so back is equivalent to home. + GoHome(); + } + return NO_ACTION; + } + BuiltinAction action = menu_actions_.at(menu_position); + switch (action) { + case WIPE_MENU: + menu_is_main_ = false; + menu_type_ = WIPE_MENU_TYPE; + menu_items_ = wipe_menu_items_; + menu_actions_ = wipe_menu_actions_; + break; + case ADVANCED_MENU: + menu_is_main_ = false; + menu_type_ = ADVANCED_MENU_TYPE; + menu_items_ = advanced_menu_items_; + menu_actions_ = advanced_menu_actions_; + break; + default: + break; // Fall through + } + return action; +} + +void Device::GoHome() { + menu_is_main_ = true; + menu_type_ = MAIN_MENU_TYPE; + menu_items_ = main_menu_items_; + menu_actions_ = main_menu_actions_; } int Device::HandleMenuKey(int key, bool visible) { @@ -21,7 +21,7 @@ class Device : public VoldWatcher { public: - explicit Device(RecoveryUI* ui) : ui_(ui) {} + explicit Device(RecoveryUI* ui); virtual ~Device() {} // Called to obtain the UI object that should be used to display the recovery user interface for @@ -55,24 +55,32 @@ class Device : public VoldWatcher { enum BuiltinAction { NO_ACTION = 0, + // Main menu REBOOT = 1, APPLY_UPDATE = 2, - // APPLY_CACHE was 3. - // APPLY_ADB_SIDELOAD was 4. - WIPE_DATA = 5, - WIPE_FULL = 6, - WIPE_CACHE = 7, - WIPE_SYSTEM = 8, - REBOOT_BOOTLOADER = 9, - SHUTDOWN = 10, - VIEW_RECOVERY_LOGS = 11, - MOUNT_SYSTEM = 12, - RUN_GRAPHICS_TEST = 13, + WIPE_MENU = 3, + ADVANCED_MENU = 4, + // Wipe menu + WIPE_DATA = 10, + WIPE_FULL = 11, + WIPE_CACHE = 12, + WIPE_SYSTEM = 13, + // Advanced menu + REBOOT_BOOTLOADER = 20, + MOUNT_SYSTEM = 21, + VIEW_RECOVERY_LOGS = 22, + RUN_GRAPHICS_TEST = 23, + SHUTDOWN = 24, }; - // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed - // to InvokeMenuItem will correspond to the indexes into this array. - virtual const char* const* GetMenuItems(); + typedef std::vector<MenuItem> MenuItemVector; + typedef std::vector<BuiltinAction> MenuActionVector; + + // Return the menu properties. The menu_position passed to InvokeMenuItem + // will correspond to the indexes in the associated vectors. + virtual bool IsMainMenu() const { return menu_is_main_; } + virtual menu_type_t GetMenuType() const { return menu_type_; } + virtual const MenuItemVector& GetMenuItems() const { return menu_items_; } // Perform a recovery action selected from the menu. 'menu_position' will be the item number of // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will @@ -82,6 +90,8 @@ class Device : public VoldWatcher { // here and return NO_ACTION. virtual BuiltinAction InvokeMenuItem(int menu_position); + virtual void GoHome(); + static const int kNoAction = -1; static const int kHighlightUp = -2; static const int kHighlightDown = -3; @@ -115,6 +125,11 @@ class Device : public VoldWatcher { private: RecoveryUI* ui_; + + bool menu_is_main_; + menu_type_t menu_type_; + MenuItemVector menu_items_; + MenuActionVector menu_actions_; }; // The device-specific library must define this function (or the default one will be used, if there diff --git a/minui/graphics.cpp b/minui/graphics.cpp index 3bfce11d..e9c2699d 100644 --- a/minui/graphics.cpp +++ b/minui/graphics.cpp @@ -29,6 +29,7 @@ #include "minui/minui.h" static GRFont* gr_font = NULL; +static GRFont* gr_font_menu = NULL; static MinuiBackend* gr_backend = nullptr; static int overscan_percent = OVERSCAN_PERCENT; @@ -52,6 +53,11 @@ const GRFont* gr_sys_font() return gr_font; } +const GRFont* gr_menu_font() +{ + return gr_font_menu; +} + int gr_measure(const GRFont* font, const char *s) { return font->char_width * strlen(s); @@ -284,6 +290,11 @@ static void gr_init_font(void) { int res = gr_init_font("font", &gr_font); if (res == 0) { + res = gr_init_font("font_menu", &gr_font_menu); + if (res != 0) { + printf("failed to read menu font\n"); + gr_font_menu = gr_font; + } return; } diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h index 4daccab2..9bcc394c 100644 --- a/minui/include/minui/minui.h +++ b/minui/include/minui/minui.h @@ -56,6 +56,7 @@ void gr_fill(int x1, int y1, int x2, int y2); void gr_texticon(int x, int y, GRSurface* icon); const GRFont* gr_sys_font(); +const GRFont* gr_menu_font(); int gr_init_font(const char* name, GRFont** dest); void gr_text(const GRFont* font, int x, int y, const char *s, bool bold); int gr_measure(const GRFont* font, const char *s); diff --git a/recovery.cpp b/recovery.cpp index abc1fe7c..9e42266e 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -666,18 +666,22 @@ static bool erase_volume(const char* volume, bool force = false) { // return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only // a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the // (non-negative) chosen item number, or -1 if timed out waiting for input. -int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only, +int get_menu_selection(bool menu_is_main, + menu_type_t menu_type, + const char* const* headers, + const MenuItemVector& menu_items, + bool menu_only, int initial_selection, Device* device) { // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. ui->FlushKeys(); - ui->StartMenu(headers, items, initial_selection); + ui->StartMenu(menu_is_main, menu_type, headers, menu_items, initial_selection); int selected = initial_selection; int chosen_item = -1; while (chosen_item < 0) { - int key = ui->WaitKey(); - if (key == -1) { // WaitKey() timed out. + RecoveryUI::InputEvent evt = ui->WaitInputEvent(); + if (evt.type() == RecoveryUI::EVENT_TYPE_NONE) { // WaitKey() timed out. if (ui->WasTextEverVisible()) { continue; } else { @@ -687,8 +691,21 @@ int get_menu_selection(const char* const* headers, const char* const* items, boo } } - bool visible = ui->IsTextVisible(); - int action = device->HandleMenuKey(key, visible); + int action = Device::kNoAction; + if (evt.type() == RecoveryUI::EVENT_TYPE_TOUCH) { + int touch_sel = ui->SelectMenu(evt.pos()); + if (touch_sel < 0) { + action = touch_sel; + } + else { + action = Device::kInvokeItem; + selected = touch_sel; + } + } + else { + bool visible = ui->IsTextVisible(); + action = device->HandleMenuKey(evt.key(), visible); + } if (action < 0) { switch (action) { @@ -724,6 +741,9 @@ int get_menu_selection(const char* const* headers, const char* const* items, boo } ui->EndMenu(); + if (chosen_item == Device::kGoHome) { + device->GoHome(); + } return chosen_item; } @@ -757,17 +777,17 @@ static std::string browse_directory(const std::string& path, Device* device) { // Append dirs to the zips list. zips.insert(zips.end(), dirs.begin(), dirs.end()); - const char* entries[zips.size() + 1]; - entries[zips.size()] = nullptr; + MenuItemVector items; for (size_t i = 0; i < zips.size(); i++) { - entries[i] = zips[i].c_str(); + items.push_back(MenuItem(zips[i])); } const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr }; int chosen_item = 0; while (true) { - chosen_item = get_menu_selection(headers, entries, true, chosen_item, device); + chosen_item = get_menu_selection(false, MT_LIST, headers, items, + true, chosen_item, device); if (chosen_item == Device::kGoHome) { return "@"; } @@ -798,9 +818,13 @@ static std::string browse_directory(const std::string& path, Device* device) { static bool yes_no(Device* device, const char* question1, const char* question2) { const char* headers[] = { question1, question2, NULL }; - const char* items[] = { " No", " Yes", NULL }; + const MenuItemVector items = { + MenuItem(" No"), + MenuItem(" Yes"), + }; - int chosen_item = get_menu_selection(headers, items, true, 0, device); + int chosen_item = get_menu_selection(false, MT_LIST, headers, items, + true, 0, device); return (chosen_item == 1); } @@ -831,13 +855,14 @@ static bool prompt_and_wipe_data(Device* device) { "stored on this device.", nullptr }; - const char* const items[] = { - "Try again", - "Factory data reset", - NULL + const MenuItemVector items = { + MenuItem("Try again"), + MenuItem("Factory data reset"), }; + for (;;) { - int chosen_item = get_menu_selection(headers, items, true, 0, device); + int chosen_item = get_menu_selection(false, MT_LIST, headers, items, + true, 0, device); if (chosen_item != 1) { return true; // Just reboot, no wipe; not a failure, user asked for it } @@ -1021,8 +1046,11 @@ static bool wipe_ab_device(size_t wipe_package_size) { return true; } -static void choose_recovery_file(Device* device) { +static int choose_recovery_file(Device* device) { std::vector<std::string> entries; + if (access(TEMPORARY_LOG_FILE, R_OK) != -1) { + entries.push_back(TEMPORARY_LOG_FILE); + } if (has_cache) { for (int i = 0; i < KEEP_LOG_COUNT; i++) { auto add_to_entries = [&](const char* filename) { @@ -1042,34 +1070,34 @@ static void choose_recovery_file(Device* device) { // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x add_to_entries(LAST_KMSG_FILE); } - } else { - // If cache partition is not found, view /tmp/recovery.log instead. - if (access(TEMPORARY_LOG_FILE, R_OK) == -1) { - return; - } else { - entries.push_back(TEMPORARY_LOG_FILE); - } + } + if (entries.empty()) { + // Should never happen + return Device::kNoAction; } - entries.push_back("Back"); - - std::vector<const char*> menu_entries(entries.size()); - std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(), - [](const std::string& entry) { return entry.c_str(); }); - menu_entries.push_back(nullptr); + MenuItemVector items(entries.size()); + std::transform(entries.cbegin(), entries.cend(), items.begin(), + [](const std::string& entry) { return MenuItem(entry.c_str()); }); const char* headers[] = { "Select file to view", nullptr }; int chosen_item = 0; while (true) { - chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device); + chosen_item = get_menu_selection(false, MT_LIST, headers, items, + true, chosen_item, device); if (chosen_item == Device::kGoHome || chosen_item == Device::kGoBack || chosen_item == 0) { break; } - ui->ShowFile(entries[chosen_item].c_str()); + int key = ui->ShowFile(entries[chosen_item].c_str()); + if (key == KEY_HOME || key == KEY_HOMEPAGE) { + chosen_item = Device::kGoHome; + break; + } } + return chosen_item; } static void run_graphics_test() { @@ -1156,43 +1184,35 @@ static int apply_from_storage(Device* device, const std::string& id, bool* wipe_ static int show_apply_update_menu(Device* device, bool* wipe_cache) { + MenuItemVector items; static const char* headers[] = { "Apply update", nullptr }; - char* menu_items[MAX_NUM_MANAGED_VOLUMES + 1 + 1]; std::vector<VolumeInfo> volumes = vdc->getVolumes(); - const int item_sideload = 0; - int n, i; - std::vector<VolumeInfo>::iterator vitr; - refresh: - menu_items[item_sideload] = strdup("Apply from ADB"); + items.push_back(MenuItem("Apply from ADB")); // Index 0 - n = item_sideload + 1; - for (vitr = volumes.begin(); vitr != volumes.end(); ++vitr) { - menu_items[n] = (char*)malloc(256); - sprintf(menu_items[n], "Choose from %s", vitr->mLabel.c_str()); - ++n; + for (auto& vol : volumes) { + items.push_back(MenuItem("Choose from " + vol.mLabel)); } - menu_items[n] = nullptr; int status = INSTALL_ERROR; - int chosen = get_menu_selection(headers, menu_items, 0, 0, device); - for (i = 0; i < n; ++i) { - free(menu_items[i]); - } + int chosen = get_menu_selection(false, MT_LIST, headers, items, + false, 0, device); if (chosen == Device::kRefresh) { goto refresh; } - if (chosen == Device::kGoBack) { + if (chosen == Device::kGoBack || + chosen == Device::kGoHome) { return INSTALL_NONE; } - if (chosen == item_sideload) { - static const char* headers[] = { "ADB Sideload", nullptr }; - static const char* list[] = { "Cancel sideload", nullptr }; + if (chosen == 0) { + static const char* s_headers[] = { "ADB Sideload", nullptr }; + static const MenuItemVector s_items = { MenuItem("Cancel sideload") }; start_sideload(ui, wipe_cache, TEMPORARY_INSTALL_FILE); - int item = get_menu_selection(headers, list, 0, 0, device); + int item = get_menu_selection(false, MT_LIST, s_headers, s_items, + false, 0, device); if (item != Device::kNoAction) { stop_sideload(); } @@ -1224,11 +1244,17 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { } ui->SetProgressType(RecoveryUI::EMPTY); - int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device); - // We are already in the main menu + int chosen_item = get_menu_selection(device->IsMainMenu(), + device->GetMenuType(), + nullptr, + device->GetMenuItems(), + false, 0, device); if (chosen_item == Device::kGoBack || - chosen_item == Device::kGoHome || - chosen_item == Device::kRefresh) { + chosen_item == Device::kGoHome) { + device->GoHome(); + continue; + } + if (chosen_item == Device::kRefresh) { continue; } @@ -1240,6 +1266,8 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { bool should_wipe_cache = false; switch (chosen_action) { case Device::NO_ACTION: + case Device::WIPE_MENU: + case Device::ADVANCED_MENU: break; case Device::REBOOT: @@ -1289,13 +1317,13 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { { status = show_apply_update_menu(device, &should_wipe_cache); - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; + if (status != INSTALL_NONE) { + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } } - } - if (status > 0 && status != INSTALL_NONE) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); @@ -1311,6 +1339,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case Device::VIEW_RECOVERY_LOGS: choose_recovery_file(device); + if (chosen_item == Device::kGoHome) { + device->GoHome(); + } break; case Device::RUN_GRAPHICS_TEST: diff --git a/res-hdpi/images/ic_back.png b/res-hdpi/images/ic_back.png Binary files differnew file mode 100644 index 00000000..ec1ea23e --- /dev/null +++ b/res-hdpi/images/ic_back.png diff --git a/res-hdpi/images/ic_back_sel.png b/res-hdpi/images/ic_back_sel.png Binary files differnew file mode 100644 index 00000000..2b154d13 --- /dev/null +++ b/res-hdpi/images/ic_back_sel.png diff --git a/res-hdpi/images/ic_factory_reset.png b/res-hdpi/images/ic_factory_reset.png Binary files differnew file mode 100644 index 00000000..f7b85671 --- /dev/null +++ b/res-hdpi/images/ic_factory_reset.png diff --git a/res-hdpi/images/ic_factory_reset_sel.png b/res-hdpi/images/ic_factory_reset_sel.png Binary files differnew file mode 100644 index 00000000..94222d0b --- /dev/null +++ b/res-hdpi/images/ic_factory_reset_sel.png diff --git a/res-hdpi/images/ic_options_advanced.png b/res-hdpi/images/ic_options_advanced.png Binary files differnew file mode 100644 index 00000000..940709ca --- /dev/null +++ b/res-hdpi/images/ic_options_advanced.png diff --git a/res-hdpi/images/ic_options_advanced_sel.png b/res-hdpi/images/ic_options_advanced_sel.png Binary files differnew file mode 100644 index 00000000..510731a4 --- /dev/null +++ b/res-hdpi/images/ic_options_advanced_sel.png diff --git a/res-hdpi/images/ic_reboot.png b/res-hdpi/images/ic_reboot.png Binary files differnew file mode 100644 index 00000000..f46f8342 --- /dev/null +++ b/res-hdpi/images/ic_reboot.png diff --git a/res-hdpi/images/ic_reboot_sel.png b/res-hdpi/images/ic_reboot_sel.png Binary files differnew file mode 100644 index 00000000..cbeeeff8 --- /dev/null +++ b/res-hdpi/images/ic_reboot_sel.png diff --git a/res-hdpi/images/ic_system_update.png b/res-hdpi/images/ic_system_update.png Binary files differnew file mode 100644 index 00000000..0f592577 --- /dev/null +++ b/res-hdpi/images/ic_system_update.png diff --git a/res-hdpi/images/ic_system_update_sel.png b/res-hdpi/images/ic_system_update_sel.png Binary files differnew file mode 100644 index 00000000..49c854e7 --- /dev/null +++ b/res-hdpi/images/ic_system_update_sel.png diff --git a/res-hdpi/images/logo_image.png b/res-hdpi/images/logo_image.png Binary files differnew file mode 100644 index 00000000..8bab73c1 --- /dev/null +++ b/res-hdpi/images/logo_image.png diff --git a/res-mdpi/images/ic_back.png b/res-mdpi/images/ic_back.png Binary files differnew file mode 100644 index 00000000..4af9ce63 --- /dev/null +++ b/res-mdpi/images/ic_back.png diff --git a/res-mdpi/images/ic_back_sel.png b/res-mdpi/images/ic_back_sel.png Binary files differnew file mode 100644 index 00000000..4e8152c7 --- /dev/null +++ b/res-mdpi/images/ic_back_sel.png diff --git a/res-mdpi/images/ic_factory_reset.png b/res-mdpi/images/ic_factory_reset.png Binary files differnew file mode 100644 index 00000000..114b1f96 --- /dev/null +++ b/res-mdpi/images/ic_factory_reset.png diff --git a/res-mdpi/images/ic_factory_reset_sel.png b/res-mdpi/images/ic_factory_reset_sel.png Binary files differnew file mode 100644 index 00000000..60471b30 --- /dev/null +++ b/res-mdpi/images/ic_factory_reset_sel.png diff --git a/res-mdpi/images/ic_options_advanced.png b/res-mdpi/images/ic_options_advanced.png Binary files differnew file mode 100644 index 00000000..1d509cab --- /dev/null +++ b/res-mdpi/images/ic_options_advanced.png diff --git a/res-mdpi/images/ic_options_advanced_sel.png b/res-mdpi/images/ic_options_advanced_sel.png Binary files differnew file mode 100644 index 00000000..6b6ba9c6 --- /dev/null +++ b/res-mdpi/images/ic_options_advanced_sel.png diff --git a/res-mdpi/images/ic_reboot.png b/res-mdpi/images/ic_reboot.png Binary files differnew file mode 100644 index 00000000..f6b73941 --- /dev/null +++ b/res-mdpi/images/ic_reboot.png diff --git a/res-mdpi/images/ic_reboot_sel.png b/res-mdpi/images/ic_reboot_sel.png Binary files differnew file mode 100644 index 00000000..007f5828 --- /dev/null +++ b/res-mdpi/images/ic_reboot_sel.png diff --git a/res-mdpi/images/ic_system_update.png b/res-mdpi/images/ic_system_update.png Binary files differnew file mode 100644 index 00000000..0395db24 --- /dev/null +++ b/res-mdpi/images/ic_system_update.png diff --git a/res-mdpi/images/ic_system_update_sel.png b/res-mdpi/images/ic_system_update_sel.png Binary files differnew file mode 100644 index 00000000..cd15c9e4 --- /dev/null +++ b/res-mdpi/images/ic_system_update_sel.png diff --git a/res-mdpi/images/logo_image.png b/res-mdpi/images/logo_image.png Binary files differnew file mode 100644 index 00000000..ebdb0e68 --- /dev/null +++ b/res-mdpi/images/logo_image.png diff --git a/res-xhdpi/images/ic_back.png b/res-xhdpi/images/ic_back.png Binary files differnew file mode 100644 index 00000000..6dbb7066 --- /dev/null +++ b/res-xhdpi/images/ic_back.png diff --git a/res-xhdpi/images/ic_back_sel.png b/res-xhdpi/images/ic_back_sel.png Binary files differnew file mode 100644 index 00000000..20e5451d --- /dev/null +++ b/res-xhdpi/images/ic_back_sel.png diff --git a/res-xhdpi/images/ic_factory_reset.png b/res-xhdpi/images/ic_factory_reset.png Binary files differnew file mode 100644 index 00000000..695fb54d --- /dev/null +++ b/res-xhdpi/images/ic_factory_reset.png diff --git a/res-xhdpi/images/ic_factory_reset_sel.png b/res-xhdpi/images/ic_factory_reset_sel.png Binary files differnew file mode 100644 index 00000000..31b2ca16 --- /dev/null +++ b/res-xhdpi/images/ic_factory_reset_sel.png diff --git a/res-xhdpi/images/ic_options_advanced.png b/res-xhdpi/images/ic_options_advanced.png Binary files differnew file mode 100644 index 00000000..8067ee31 --- /dev/null +++ b/res-xhdpi/images/ic_options_advanced.png diff --git a/res-xhdpi/images/ic_options_advanced_sel.png b/res-xhdpi/images/ic_options_advanced_sel.png Binary files differnew file mode 100644 index 00000000..a87325e6 --- /dev/null +++ b/res-xhdpi/images/ic_options_advanced_sel.png diff --git a/res-xhdpi/images/ic_reboot.png b/res-xhdpi/images/ic_reboot.png Binary files differnew file mode 100644 index 00000000..6f933d98 --- /dev/null +++ b/res-xhdpi/images/ic_reboot.png diff --git a/res-xhdpi/images/ic_reboot_sel.png b/res-xhdpi/images/ic_reboot_sel.png Binary files differnew file mode 100644 index 00000000..04f797d4 --- /dev/null +++ b/res-xhdpi/images/ic_reboot_sel.png diff --git a/res-xhdpi/images/ic_system_update.png b/res-xhdpi/images/ic_system_update.png Binary files differnew file mode 100644 index 00000000..22aca1eb --- /dev/null +++ b/res-xhdpi/images/ic_system_update.png diff --git a/res-xhdpi/images/ic_system_update_sel.png b/res-xhdpi/images/ic_system_update_sel.png Binary files differnew file mode 100644 index 00000000..11e6d7b9 --- /dev/null +++ b/res-xhdpi/images/ic_system_update_sel.png diff --git a/res-xhdpi/images/logo_image.png b/res-xhdpi/images/logo_image.png Binary files differnew file mode 100644 index 00000000..9c37f8f0 --- /dev/null +++ b/res-xhdpi/images/logo_image.png diff --git a/res-xxhdpi/images/font_menu.png b/res-xxhdpi/images/font_menu.png Binary files differnew file mode 100644 index 00000000..f3b54b38 --- /dev/null +++ b/res-xxhdpi/images/font_menu.png diff --git a/res-xxhdpi/images/ic_back.png b/res-xxhdpi/images/ic_back.png Binary files differnew file mode 100644 index 00000000..fef22710 --- /dev/null +++ b/res-xxhdpi/images/ic_back.png diff --git a/res-xxhdpi/images/ic_back_sel.png b/res-xxhdpi/images/ic_back_sel.png Binary files differnew file mode 100644 index 00000000..50dc5577 --- /dev/null +++ b/res-xxhdpi/images/ic_back_sel.png diff --git a/res-xxhdpi/images/ic_factory_reset.png b/res-xxhdpi/images/ic_factory_reset.png Binary files differnew file mode 100644 index 00000000..5792af95 --- /dev/null +++ b/res-xxhdpi/images/ic_factory_reset.png diff --git a/res-xxhdpi/images/ic_factory_reset_sel.png b/res-xxhdpi/images/ic_factory_reset_sel.png Binary files differnew file mode 100644 index 00000000..9a0b7608 --- /dev/null +++ b/res-xxhdpi/images/ic_factory_reset_sel.png diff --git a/res-xxhdpi/images/ic_options_advanced.png b/res-xxhdpi/images/ic_options_advanced.png Binary files differnew file mode 100644 index 00000000..6e56fc85 --- /dev/null +++ b/res-xxhdpi/images/ic_options_advanced.png diff --git a/res-xxhdpi/images/ic_options_advanced_sel.png b/res-xxhdpi/images/ic_options_advanced_sel.png Binary files differnew file mode 100644 index 00000000..30d38e26 --- /dev/null +++ b/res-xxhdpi/images/ic_options_advanced_sel.png diff --git a/res-xxhdpi/images/ic_reboot.png b/res-xxhdpi/images/ic_reboot.png Binary files differnew file mode 100644 index 00000000..1d3a5642 --- /dev/null +++ b/res-xxhdpi/images/ic_reboot.png diff --git a/res-xxhdpi/images/ic_reboot_sel.png b/res-xxhdpi/images/ic_reboot_sel.png Binary files differnew file mode 100644 index 00000000..6dfde37d --- /dev/null +++ b/res-xxhdpi/images/ic_reboot_sel.png diff --git a/res-xxhdpi/images/ic_system_update.png b/res-xxhdpi/images/ic_system_update.png Binary files differnew file mode 100644 index 00000000..78702005 --- /dev/null +++ b/res-xxhdpi/images/ic_system_update.png diff --git a/res-xxhdpi/images/ic_system_update_sel.png b/res-xxhdpi/images/ic_system_update_sel.png Binary files differnew file mode 100644 index 00000000..27297e08 --- /dev/null +++ b/res-xxhdpi/images/ic_system_update_sel.png diff --git a/res-xxhdpi/images/logo_image.png b/res-xxhdpi/images/logo_image.png Binary files differnew file mode 100644 index 00000000..258ae890 --- /dev/null +++ b/res-xxhdpi/images/logo_image.png diff --git a/res-xxxhdpi/images/ic_back.png b/res-xxxhdpi/images/ic_back.png Binary files differnew file mode 100644 index 00000000..b41f5572 --- /dev/null +++ b/res-xxxhdpi/images/ic_back.png diff --git a/res-xxxhdpi/images/ic_back_sel.png b/res-xxxhdpi/images/ic_back_sel.png Binary files differnew file mode 100644 index 00000000..b7f604cf --- /dev/null +++ b/res-xxxhdpi/images/ic_back_sel.png diff --git a/res-xxxhdpi/images/ic_factory_reset.png b/res-xxxhdpi/images/ic_factory_reset.png Binary files differnew file mode 100644 index 00000000..c14607c9 --- /dev/null +++ b/res-xxxhdpi/images/ic_factory_reset.png diff --git a/res-xxxhdpi/images/ic_factory_reset_sel.png b/res-xxxhdpi/images/ic_factory_reset_sel.png Binary files differnew file mode 100644 index 00000000..dd61b862 --- /dev/null +++ b/res-xxxhdpi/images/ic_factory_reset_sel.png diff --git a/res-xxxhdpi/images/ic_options_advanced.png b/res-xxxhdpi/images/ic_options_advanced.png Binary files differnew file mode 100644 index 00000000..322a7b59 --- /dev/null +++ b/res-xxxhdpi/images/ic_options_advanced.png diff --git a/res-xxxhdpi/images/ic_options_advanced_sel.png b/res-xxxhdpi/images/ic_options_advanced_sel.png Binary files differnew file mode 100644 index 00000000..64f631d6 --- /dev/null +++ b/res-xxxhdpi/images/ic_options_advanced_sel.png diff --git a/res-xxxhdpi/images/ic_reboot.png b/res-xxxhdpi/images/ic_reboot.png Binary files differnew file mode 100644 index 00000000..5d431632 --- /dev/null +++ b/res-xxxhdpi/images/ic_reboot.png diff --git a/res-xxxhdpi/images/ic_reboot_sel.png b/res-xxxhdpi/images/ic_reboot_sel.png Binary files differnew file mode 100644 index 00000000..0ccd6da7 --- /dev/null +++ b/res-xxxhdpi/images/ic_reboot_sel.png diff --git a/res-xxxhdpi/images/ic_system_update.png b/res-xxxhdpi/images/ic_system_update.png Binary files differnew file mode 100644 index 00000000..687c2823 --- /dev/null +++ b/res-xxxhdpi/images/ic_system_update.png diff --git a/res-xxxhdpi/images/ic_system_update_sel.png b/res-xxxhdpi/images/ic_system_update_sel.png Binary files differnew file mode 100644 index 00000000..929b6ac2 --- /dev/null +++ b/res-xxxhdpi/images/ic_system_update_sel.png diff --git a/res-xxxhdpi/images/logo_image.png b/res-xxxhdpi/images/logo_image.png Binary files differnew file mode 100644 index 00000000..0fa93e10 --- /dev/null +++ b/res-xxxhdpi/images/logo_image.png diff --git a/screen_ui.cpp b/screen_ui.cpp index 2f8b07b9..41d0594a 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -37,6 +37,8 @@ #include <android-base/strings.h> #include <android-base/stringprintf.h> +#include <healthd/BatteryMonitor.h> + #include "common.h" #include "device.h" #include "minui/minui.h" @@ -50,6 +52,69 @@ static double now() { return tv.tv_sec + tv.tv_usec / 1000000.0; } +static void get_battery_status(bool& charged, int& capacity) { + struct healthd_config healthd_config = { + .batteryStatusPath = android::String8(android::String8::kEmptyString), + .batteryHealthPath = android::String8(android::String8::kEmptyString), + .batteryPresentPath = android::String8(android::String8::kEmptyString), + .batteryCapacityPath = android::String8(android::String8::kEmptyString), + .batteryVoltagePath = android::String8(android::String8::kEmptyString), + .batteryTemperaturePath = android::String8(android::String8::kEmptyString), + .batteryTechnologyPath = android::String8(android::String8::kEmptyString), + .batteryCurrentNowPath = android::String8(android::String8::kEmptyString), + .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString), + .batteryChargeCounterPath = android::String8(android::String8::kEmptyString), + .batteryFullChargePath = android::String8(android::String8::kEmptyString), + .batteryCycleCountPath = android::String8(android::String8::kEmptyString), + .energyCounter = NULL, + .boot_min_cap = 0, + .screen_on = NULL + }; + healthd_board_init(&healthd_config); + + android::BatteryMonitor monitor; + monitor.init(&healthd_config); + + int charge_status = monitor.getChargeStatus(); + // Treat unknown status as charged. + charged = (charge_status != android::BATTERY_STATUS_DISCHARGING && + charge_status != android::BATTERY_STATUS_NOT_CHARGING); + android::BatteryProperty prop; + android::status_t status = monitor.getProperty(android::BATTERY_PROP_CAPACITY, &prop); + // If we can't read battery percentage, it may be a device without battery. In this + // situation, use 100 as a fake battery percentage. + if (status != 0) { + prop.valueInt64 = 100; + } + capacity = (int)prop.valueInt64; +} + +ScreenMenuItem::~ScreenMenuItem() { + if (icon_) { + res_free_surface(icon_); + } + if (icon_sel_) { + res_free_surface(icon_sel_); + } +} + +GRSurface* ScreenMenuItem::icon() { + if (!icon_) { + res_create_display_surface(icon_name_.c_str(), &icon_); + } + return icon_; +} + +GRSurface* ScreenMenuItem::icon_sel() { + if (icon_name_sel_.empty()) { + return icon(); + } + if (!icon_sel_) { + res_create_display_surface(icon_name_sel_.c_str(), &icon_sel_); + } + return icon_sel_; +} + ScreenRecoveryUI::ScreenRecoveryUI() : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH), kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT), @@ -69,9 +134,13 @@ ScreenRecoveryUI::ScreenRecoveryUI() text_top_(0), show_text(false), show_text_ever(false), - menu_(nullptr), + menu_is_main_(true), + menu_type_(MT_NONE), + menu_headers_(nullptr), + menu_start_y_(0), show_menu(false), - menu_items(0), + menu_show_start(0), + menu_show_count(0), menu_sel(0), file_viewer_text_(nullptr), intro_frames(0), @@ -180,7 +249,7 @@ void ScreenRecoveryUI::draw_background_locked() { // 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 (currentIcon != NONE) { + if (currentIcon != NONE && currentIcon != NO_COMMAND) { gr_color(0, 0, 0, 255); gr_clear(); GRSurface* frame = GetCurrentFrame(); @@ -228,8 +297,12 @@ void ScreenRecoveryUI::draw_foreground_locked() { } } +/* Lineage teal: #167c80 */ void ScreenRecoveryUI::SetColor(UIElement e) const { switch (e) { + case STATUSBAR: + gr_color(255, 255, 255, 255); + break; case INFO: gr_color(249, 194, 0, 255); break; @@ -238,13 +311,13 @@ void ScreenRecoveryUI::SetColor(UIElement e) const { break; case MENU: case MENU_SEL_BG: - gr_color(106, 103, 102, 255); + gr_color(0xd8, 0xd8, 0xd8, 255); break; case MENU_SEL_BG_ACTIVE: gr_color(138, 135, 134, 255); break; case MENU_SEL_FG: - gr_color(0, 177, 229, 255); + gr_color(0x16, 0x7c, 0x80, 255); break; case LOG: gr_color(196, 196, 196, 255); @@ -302,7 +375,8 @@ int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* line next_start += last_space + 1; } } - offset += DrawTextLine(x, y + offset, sub.c_str(), false); + gr_text(gr_menu_font(), x, y + offset, sub.c_str(), false); + offset += menu_char_height_ + 4; } } return offset; @@ -319,6 +393,215 @@ static const char* LONG_PRESS_HELP[] = { NULL }; +void ScreenRecoveryUI::draw_statusbar_locked() { + int y = kMarginHeight; + int x; + + int h_unit = gr_fb_width() / 9; + int v_unit = gr_fb_height() / 16; + + GRSurface* icon; + int icon_x, icon_y, icon_h, icon_w; + + // Local time + char localtm_str[] = "--:--"; + time_t now; + struct tm localtm; + time(&now); + if (now > TV_MIN) { + localtime_r(&now, &localtm); + snprintf(localtm_str, sizeof(localtm_str), "%02d:%02d", + localtm.tm_hour, localtm.tm_min); + } + + // Battery status + bool batt_charged; + int batt_capacity; + get_battery_status(batt_charged, batt_capacity); + char batt_capacity_str[3+1+1]; + snprintf(batt_capacity_str, sizeof(batt_capacity_str), "%d%%", batt_capacity); + + // Draw status bar from right to left + + // Time + SetColor(STATUSBAR); + x = gr_fb_width(); + x -= 5 * char_width_; + gr_text(gr_sys_font(), x, y, localtm_str, false); + + x -= char_width_; // Separator + + // Battery icon + x -= 1 * char_width_; + SetColor((batt_capacity < 20) ? HEADER : STATUSBAR); + + // Top + icon_x = x + char_width_ / 3; + icon_y = y; + icon_w = char_width_ / 3; + icon_h = char_height_ / 12; + gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h); + + // Main rect + icon_x = x; + icon_y = y + icon_h; + icon_w = char_width_; + icon_h = char_height_ - (char_height_ / 12); + gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h); + + // Capacity + icon_x = x + char_width_ / 6; + icon_y = y + char_height_ / 12; + icon_w = char_width_ - (2 * char_width_ / 6); + icon_h = char_height_ - (3 * char_height_ / 12); + int cap_h = icon_h * batt_capacity / 100; + gr_fill(icon_x, icon_y + icon_h - cap_h, icon_x + icon_w, icon_y + icon_h); + gr_color(0, 0, 0, 255); + gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h - cap_h); + SetColor(STATUSBAR); + + x -= char_width_; // Separator + + // Battery text + x -= strlen(batt_capacity_str) * char_width_; + gr_text(gr_sys_font(), x, y, batt_capacity_str, false); +} + +/* + * Header layout: + * * 1/32: Status bar + * * Header image + * * 1/32: Margin + */ +void ScreenRecoveryUI::draw_header_locked(int& y) { + int h_unit = gr_fb_width() / 9; + int v_unit = gr_fb_height() / 16; + + GRSurface* icon; + int icon_x, icon_y, icon_h, icon_w; + + y += v_unit / 2; // Margin + + // Draw back icon if not in main menu + if (!menu_is_main_) { + icon = (menu_sel == Device::kGoBack ? ic_back_sel : ic_back); + icon_w = gr_get_width(icon); + icon_h = gr_get_height(icon); + icon_x = (h_unit / 2) + ((h_unit * 1) - icon_w) / 2; + icon_y = y + ((v_unit * 1) - icon_h) / 2; + gr_blit(icon, 0, 0, icon_w, icon_h, icon_x, icon_y); + } + y += v_unit; + + // Draw logo + icon = logo_image; + icon_w = gr_get_width(icon); + icon_h = gr_get_height(icon); + icon_x = (gr_fb_width() - icon_w) / 2; + icon_y = y + ((v_unit * 4) - icon_h) / 2; + gr_blit(icon, 0, 0, icon_w, icon_h, icon_x, icon_y); + y += v_unit * 4; + + y += v_unit * 1; // Margin +} + +void ScreenRecoveryUI::draw_text_menu_locked(int& y) { + static constexpr int kMenuIndent = 4; + int x = kMarginWidth + kMenuIndent; + + draw_statusbar_locked(); + draw_header_locked(y); + + if (menu_headers_) { + SetColor(HEADER); + // Ignore kMenuIndent, which is not taken into account by text_cols_. + y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_); + + SetColor(MENU); + y += DrawHorizontalRule(y) + 4; + } + + menu_start_y_ = y; + int i; + for (i = menu_show_start; i < (int)menu_items_.size() && y < gr_fb_height(); ++i) { + const ScreenMenuItem& item = menu_items_.at(i); + if (i == menu_sel) { + SetColor(MENU_SEL_FG); + y += menu_char_height_; + gr_text(gr_menu_font(), x, y, item.text().c_str(), true); + y += menu_char_height_; + y += menu_char_height_; + } else { + SetColor(MENU); + y += menu_char_height_; + gr_text(gr_menu_font(), x, y, item.text().c_str(), false); + y += menu_char_height_; + y += menu_char_height_; + } + } + menu_show_count = i - menu_show_start; +} + +/* + * Grid layout. + * + * Grid item: + * Horizontal: + * * 3/9 of screen per item. + * * 1/9 of screen margin around/between items. + * Vertical: + * * 3/16 of screen per item. + * * No margin between items. + * + * Within a grid item: + * Asher's icons 1/5 of grid both dimensions. + * Current icons 2/5 of grid both dimensions. + * Horizontal: + * * All items centered. + * Vertical: + * * Icon lower aligned in top 2/3. + * * Text upper aligned in low 1/3 plus half line margin. + */ +void ScreenRecoveryUI::draw_grid_menu_locked(int& y) { + int h_unit = gr_fb_width() / 9; + int v_unit = gr_fb_height() / 16; + + int grid_w = h_unit * 3; + int grid_h = v_unit * 3; + + draw_statusbar_locked(); + draw_header_locked(y); + + menu_start_y_ = y; + int i; + for (i = menu_show_start; i < (int)menu_items_.size() && y + grid_h < gr_fb_height(); ++i) { + ScreenMenuItem& item = menu_items_.at(i); + int grid_x = (i % 2) ? h_unit * 5 : h_unit * 1; + int grid_y = y; + if (item.icon()) { + GRSurface* icon = (i == menu_sel) ? item.icon_sel() : item.icon(); + int icon_w = gr_get_width(icon); + int icon_h = gr_get_height(icon); + int icon_x = grid_x + (grid_w - icon_w) / 2; + int icon_y = grid_y + ((grid_h * 2 / 3) - icon_h) / 2; + gr_blit(icon, 0, 0, icon_w, icon_h, icon_x, icon_y); + } + if (!item.text().empty()) { + int text_w = item.text().size() * char_width_; + int text_h = char_height_; + int text_x = grid_x + (grid_w - text_w) / 2; + int text_y = grid_y + (grid_h * 2 / 3) + (char_height_ / 2); + SetColor(i == menu_sel ? MENU_SEL_FG : MENU); + gr_text(gr_sys_font(), text_x, text_y, item.text().c_str(), false); + } + if (i % 2) { + y += grid_h; + grid_y = y; + } + } + menu_show_count = i - menu_show_start; +} + // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex // locked. void ScreenRecoveryUI::draw_screen_locked() { @@ -333,50 +616,40 @@ void ScreenRecoveryUI::draw_screen_locked() { int y = kMarginHeight; if (show_menu) { - static constexpr int kMenuIndent = 4; - int x = kMarginWidth + kMenuIndent; - - SetColor(INFO); - y += DrawTextLine(x, y, "Android Recovery", true); - std::string recovery_fingerprint = - android::base::GetProperty("ro.bootimage.build.fingerprint", ""); - for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - y += DrawTextLine(x, y, chunk.c_str(), false); + switch (menu_type_) { + case MT_LIST: + draw_text_menu_locked(y); + break; + case MT_GRID: + draw_grid_menu_locked(y); + break; + default: + break; } - y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); - - SetColor(HEADER); - // Ignore kMenuIndent, which is not taken into account by text_cols_. - y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_); - SetColor(MENU); - y += DrawHorizontalRule(y) + 4; - for (int i = 0; i < menu_items; ++i) { - if (i == menu_sel) { - // Draw the highlight bar. - SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); - DrawHighlightBar(0, y - 2, gr_fb_width(), char_height_ + 4); - // Bold white text for the selected item. - SetColor(MENU_SEL_FG); - y += DrawTextLine(x, y, menu_[i], true); - SetColor(MENU); - } else { - y += DrawTextLine(x, y, menu_[i], false); - } + // Draw version info + if (menu_is_main_) { + int text_x, text_y; + text_x = (gr_fb_width() - (android_version_.size() * char_width_)) / 2; + text_y = gr_fb_height() - 2 * (char_height_ + 4); + DrawTextLine(text_x, text_y, android_version_.c_str(), false); + text_x = (gr_fb_width() - (lineage_version_.size() * char_width_)) / 2; + text_y = gr_fb_height() - 1 * (char_height_ + 4); + DrawTextLine(text_x, text_y, lineage_version_.c_str(), false); } - y += DrawHorizontalRule(y); } - - // 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(LOG); - int row = (text_top_ + text_rows_ - 1) % text_rows_; - size_t count = 0; - for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_; - ty -= char_height_, ++count) { - DrawTextLine(kMarginWidth, ty, text_[row], false); - --row; - if (row < 0) row = text_rows_ - 1; + else { + // 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(LOG); + int row = (text_top_ + text_rows_ - 1) % text_rows_; + size_t count = 0; + for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_; + ty -= char_height_, ++count) { + DrawTextLine(kMarginWidth, ty, text_[row], false); + --row; + if (row < 0) row = text_rows_ - 1; + } } } @@ -459,6 +732,10 @@ void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { } } +void ScreenRecoveryUI::FreeBitmap(GRSurface* surface) { + res_free_surface(surface); +} + void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface); if (result < 0) { @@ -491,6 +768,7 @@ bool ScreenRecoveryUI::InitTextParams() { } gr_font_size(gr_sys_font(), &char_width_, &char_height_); + gr_font_size(gr_menu_font(), &menu_char_width_, &menu_char_height_); text_rows_ = (gr_fb_height() - kMarginHeight * 2) / char_height_; text_cols_ = (gr_fb_width() - kMarginWidth * 2) / char_width_; return true; @@ -509,11 +787,14 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { text_ = Alloc2d(text_rows_, text_cols_ + 1); file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); - menu_ = Alloc2d(text_rows_, text_cols_ + 1); text_col_ = text_row_ = 0; text_top_ = 1; + LoadBitmap("logo_image", &logo_image); + LoadBitmap("ic_back", &ic_back); + LoadBitmap("ic_back_sel", &ic_back_sel); + LoadBitmap("icon_error", &error_icon); LoadBitmap("progress_empty", &progressBarEmpty); @@ -580,8 +861,10 @@ void ScreenRecoveryUI::LoadAnimation() { void ScreenRecoveryUI::SetBackground(Icon icon) { pthread_mutex_lock(&updateMutex); - currentIcon = icon; - update_screen_locked(); + if (icon != currentIcon) { + currentIcon = icon; + update_screen_locked(); + } pthread_mutex_unlock(&updateMutex); } @@ -698,7 +981,7 @@ void ScreenRecoveryUI::ClearText() { pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::ShowFile(FILE* fp) { +int ScreenRecoveryUI::ShowFile(FILE* fp) { std::vector<off_t> offsets; offsets.push_back(ftello(fp)); ClearText(); @@ -715,10 +998,16 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { Redraw(); while (show_prompt) { show_prompt = false; - int key = WaitKey(); - if (key == KEY_POWER || key == KEY_ENTER) { - return; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + RecoveryUI::InputEvent evt = WaitInputEvent(); + if (evt.type() != RecoveryUI::EVENT_TYPE_KEY) { + show_prompt = true; + continue; + } + if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER || + evt.key() == KEY_BACKSPACE || evt.key() == KEY_BACK || + evt.key() == KEY_HOME || evt.key() == KEY_HOMEPAGE) { + return evt.key(); + } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) { if (offsets.size() <= 1) { show_prompt = true; } else { @@ -727,7 +1016,7 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { } } else { if (feof(fp)) { - return; + return -1; } offsets.push_back(ftello(fp)); } @@ -746,13 +1035,14 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { } } } + return -1; } -void ScreenRecoveryUI::ShowFile(const char* filename) { +int ScreenRecoveryUI::ShowFile(const char* filename) { FILE* fp = fopen_path(filename, "re"); if (fp == nullptr) { Print(" Unable to open %s: %s\n", filename, strerror(errno)); - return; + return -1; } char** old_text = text_; @@ -764,30 +1054,38 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { text_ = file_viewer_text_; ClearText(); - ShowFile(fp); + int key = ShowFile(fp); fclose(fp); text_ = old_text; text_col_ = old_text_col; text_row_ = old_text_row; text_top_ = old_text_top; + return key; } -void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items, +void ScreenRecoveryUI::StartMenu(bool is_main, + menu_type_t type, + const char* const* headers, + const MenuItemVector& items, int initial_selection) { pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - menu_headers_ = headers; - size_t i = 0; - for (; i < text_rows_ && items[i] != nullptr; ++i) { - strncpy(menu_[i], items[i], text_cols_ - 1); - menu_[i][text_cols_ - 1] = '\0'; - } - menu_items = i; - show_menu = true; - menu_sel = initial_selection; - update_screen_locked(); + menu_is_main_ = is_main; + menu_type_ = type; + menu_headers_ = headers; + for (auto& item : items) { + menu_items_.push_back(ScreenMenuItem(item)); + } + show_menu = true; + menu_sel = initial_selection; + draw_screen_locked(); + if (menu_sel < menu_show_start) { + menu_show_start = menu_sel; + } + if (menu_sel >= menu_show_start + menu_show_count) { + menu_show_start = menu_sel - (menu_show_count - 1); } + update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -798,8 +1096,16 @@ int ScreenRecoveryUI::SelectMenu(int 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_sel < 0) menu_sel = (int)menu_items_.size() - 1; + if (menu_sel >= (int)menu_items_.size()) menu_sel = 0; + + // Scroll + if (menu_sel < menu_show_start) { + menu_show_start = menu_sel; + } + if (menu_sel >= menu_show_start + menu_show_count) { + menu_show_start = menu_sel - (menu_show_count - 1); + } sel = menu_sel; if (menu_sel != old_sel) update_screen_locked(); @@ -808,11 +1114,57 @@ int ScreenRecoveryUI::SelectMenu(int sel) { return sel; } +int ScreenRecoveryUI::SelectMenu(const Point& point) { + int sel = Device::kNoAction; + int h_unit = gr_fb_width() / 9; + int v_unit = gr_fb_height() / 16; + pthread_mutex_lock(&updateMutex); + if (show_menu) { + if (point.y() < menu_start_y_) { + if (!menu_is_main_ && + point.x() >= h_unit / 2 && point.x() < h_unit * 3 / 2 && + point.y() >= v_unit * 1 / 2 && point.y() < v_unit * 3 / 2) { + sel = Device::kGoBack; + } + } + else { + int row = -1, col = -1; + switch (menu_type_) { + case MT_LIST: + sel = (point.y() - menu_start_y_) / (menu_char_height_ * 3); + break; + case MT_GRID: + row = (point.y() - menu_start_y_) / (gr_fb_height() * 3 / 16); + col = (point.x()) / (gr_fb_width() / 9); + if ((col % 4) != 0) { + sel = row * 2 + ((col - 1) / 4); + } + break; + default: + break; + } + if (sel >= (int)menu_items_.size()) { + sel = Device::kNoAction; + } + } + if (sel != -1 && sel != menu_sel) { + menu_sel = sel; + update_screen_locked(); + usleep(100*1000); + } + } + pthread_mutex_unlock(&updateMutex); + return sel; +} + void ScreenRecoveryUI::EndMenu() { pthread_mutex_lock(&updateMutex); if (show_menu && text_rows_ > 0 && text_cols_ > 0) { show_menu = false; } + menu_type_ = MT_NONE; + menu_headers_ = nullptr; + menu_items_.clear(); pthread_mutex_unlock(&updateMutex); } diff --git a/screen_ui.h b/screen_ui.h index 726a9957..389d8c42 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -24,9 +24,35 @@ #include "ui.h" +#define MAX_MENU_ITEMS 32 + // From minui/minui.h. struct GRSurface; +class ScreenMenuItem { + public: + ScreenMenuItem() : icon_(nullptr), icon_sel_(nullptr) {} + explicit ScreenMenuItem(const MenuItem& mi) : + text_(mi.text()), + icon_name_(mi.icon_name()), + icon_(nullptr), + icon_name_sel_(mi.icon_name_sel()), + icon_sel_(nullptr) {} + ~ScreenMenuItem(); + + const std::string& text() const { return text_; } + GRSurface* icon(); + GRSurface* icon_sel(); + + private: + std::string text_; + std::string icon_name_; + GRSurface* icon_; + std::string icon_name_sel_; + GRSurface* icon_sel_; +}; +typedef std::vector<ScreenMenuItem> ScreenMenuItemVector; + // Implementation of RecoveryUI appropriate for devices with a screen // (shows an icon + a progress bar, text logging, menu, etc.) class ScreenRecoveryUI : public RecoveryUI { @@ -55,19 +81,29 @@ class ScreenRecoveryUI : public RecoveryUI { // printing messages void Print(const char* fmt, ...) override __printflike(2, 3); void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); - void ShowFile(const char* filename) override; + int ShowFile(const char* filename) override; // menu display - void StartMenu(const char* const* headers, const char* const* items, + void StartMenu(bool is_main, + menu_type_t type, + const char* const* headers, + const MenuItemVector& items, int initial_selection) override; int SelectMenu(int sel) override; + int SelectMenu(const Point& point) override; void EndMenu() override; + bool MenuShowing() const { return show_menu; } + bool MenuScrollable() const { return (menu_type_ == MT_LIST); } + int MenuItemStart() const { return menu_start_y_; } + int MenuItemHeight() const { return (3 * menu_char_height_); } + void KeyLongPress(int) override; void Redraw(); enum UIElement { + STATUSBAR, HEADER, MENU, MENU_SEL_BG, @@ -96,6 +132,10 @@ class ScreenRecoveryUI : public RecoveryUI { // The layout to use. int layout_; + GRSurface* logo_image; + GRSurface* ic_back; + GRSurface* ic_back_sel; + GRSurface* error_icon; GRSurface* erasing_text; @@ -128,10 +168,15 @@ class ScreenRecoveryUI : public RecoveryUI { bool show_text; bool show_text_ever; // has show_text ever been true? - char** menu_; + bool menu_is_main_; + menu_type_t menu_type_; const char* const* menu_headers_; + ScreenMenuItemVector menu_items_; + int menu_start_y_; bool show_menu; - int menu_items, menu_sel; + int menu_show_start; + int menu_show_count; + int menu_sel; // An alternate text screen, swapped with 'text_' when we're viewing a log file. char** file_viewer_text_; @@ -150,12 +195,19 @@ class ScreenRecoveryUI : public RecoveryUI { int char_width_; int char_height_; + int menu_char_width_; + int menu_char_height_; + pthread_mutex_t updateMutex; virtual bool InitTextParams(); virtual void draw_background_locked(); virtual void draw_foreground_locked(); + virtual void draw_statusbar_locked(); + virtual void draw_header_locked(int& y); + virtual void draw_text_menu_locked(int& y); + virtual void draw_grid_menu_locked(int& y); virtual void draw_screen_locked(); virtual void update_screen_locked(); virtual void update_progress_locked(); @@ -166,13 +218,14 @@ class ScreenRecoveryUI : public RecoveryUI { static void* ProgressThreadStartRoutine(void* data); void ProgressThreadLoop(); - virtual void ShowFile(FILE*); + virtual int ShowFile(FILE*); virtual void PrintV(const char*, bool, va_list); void PutChar(char); void ClearText(); void LoadAnimation(); void LoadBitmap(const char* filename, GRSurface** surface); + void FreeBitmap(GRSurface* surface); void LoadLocalizedBitmap(const char* filename, GRSurface** surface); int PixelsFromDp(int dp) const; @@ -51,15 +51,26 @@ class StubRecoveryUI : public RecoveryUI { va_end(ap); } void PrintOnScreenOnly(const char* fmt, ...) override {} - void ShowFile(const char* filename) override {} + int ShowFile(const char* filename) override { return -1; } // menu display - void StartMenu(const char* const* headers, const char* const* items, + void StartMenu(bool is_main, + menu_type_t type, + const char* const* headers, + const MenuItemVector& items, int initial_selection) override {} int SelectMenu(int sel) override { return sel; } + int SelectMenu(const Point& point) override { + return 0; + } void EndMenu() override {} + + bool MenuShowing() const { return true; } + bool MenuScrollable() const { return true; } + int MenuItemStart() const { return 0; } + int MenuItemHeight() const { return 1; } }; #endif // RECOVERY_STUB_UI_H @@ -39,6 +39,7 @@ #include <android-base/properties.h> #include <android-base/strings.h> #include <cutils/android_reboot.h> +#include <cutils/properties.h> #include <minui/minui.h> #include "common.h" @@ -58,7 +59,7 @@ RecoveryUI::RecoveryUI() volumes_changed_(false), kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD), kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD), - key_queue_len(0), + event_queue_len(0), key_last_down(-1), key_long_press(false), key_down_count(0), @@ -70,10 +71,20 @@ RecoveryUI::RecoveryUI() has_down_key(false), has_touch_screen(false), touch_slot_(0), + touch_finger_down_(false), + touch_saw_x_(false), + touch_saw_y_(false), + touch_reported_(false), is_bootreason_recovery_ui_(false), screensaver_state_(ScreensaverState::DISABLED) { - pthread_mutex_init(&key_queue_mutex, nullptr); - pthread_cond_init(&key_queue_cond, nullptr); + char propval[PROPERTY_VALUE_MAX]; + property_get("ro.build.version.release", propval, "(unknown)"); + android_version_ = std::string("Android ") + propval; + property_get("ro.lineage.version", propval, "(unknown)"); + lineage_version_ = std::string("LineageOS ") + propval; + + pthread_mutex_init(&event_queue_mutex, nullptr); + pthread_cond_init(&event_queue_cond, nullptr); memset(key_pressed, 0, sizeof(key_pressed)); } @@ -216,59 +227,59 @@ void RecoveryUI::Stop() { !android::base::WriteStringToFile("0", BRIGHTNESS_FILE); } -void RecoveryUI::OnTouchEvent() { - Point delta = touch_pos_ - touch_start_; - enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction; +void RecoveryUI::OnTouchPress() { + touch_start_ = touch_track_ = touch_pos_; +} - // We only consider a valid swipe if: - // - the delta along one axis is below kTouchLowThreshold; - // - and the delta along the other axis is beyond kTouchHighThreshold. - if (abs(delta.y()) < kTouchLowThreshold && abs(delta.x()) > kTouchHighThreshold) { - direction = delta.x() < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT; - } else if (abs(delta.x()) < kTouchLowThreshold && abs(delta.y()) > kTouchHighThreshold) { - direction = delta.y() < 0 ? SwipeDirection::UP : SwipeDirection::DOWN; - } else { - for (const auto& vk : virtual_keys_) { - if (touch_start_.x() >= vk.min_.x() && touch_start_.x() < vk.max_.x() && - touch_start_.y() >= vk.min_.y() && touch_start_.y() < vk.max_.y()) { - ProcessKey(vk.keycode, 1); // press key - ProcessKey(vk.keycode, 0); // and release it - return; +void RecoveryUI::OnTouchTrack() { + if (touch_pos_.y() <= gr_fb_height()) { + if (MenuShowing() && MenuScrollable()) { + while (abs(touch_pos_.y() - touch_track_.y()) >= MenuItemHeight()) { + int dy = touch_pos_.y() - touch_track_.y(); + int key = (dy < 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; + ProcessKey(key, 1); // press key + ProcessKey(key, 0); // and release it + int sgn = (dy > 0) - (dy < 0); + touch_track_.y(touch_track_.y() + sgn * MenuItemHeight()); } } - LOG(DEBUG) << "Ignored " << delta.x() << " " << delta.y() - << " (low: " << kTouchLowThreshold - << ", high: " << kTouchHighThreshold << ")"; - return; } +} +void RecoveryUI::OnTouchRelease() { // 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; + // Check vkeys. Only report if touch both starts and ends in the vkey. + if (touch_start_.y() > gr_fb_height() && touch_pos_.y() > gr_fb_height()) { + for (const auto& vk : virtual_keys_) { + if (vk.inside(touch_start_) && vk.inside(touch_pos_)) { + ProcessKey(vk.keycode, 1); // press key + ProcessKey(vk.keycode, 0); // and release it + } + } + return; + } - case SwipeDirection::DOWN: - ProcessKey(KEY_DOWN, 1); // press down key - ProcessKey(KEY_DOWN, 0); // and release it - break; + // If we tracked a vertical swipe, ignore the release + if (touch_track_ != touch_start_) { + return; + } - case SwipeDirection::LEFT: - ProcessKey(KEY_BACK, 1); // press power key - ProcessKey(KEY_BACK, 0); // and release it - break; - case SwipeDirection::RIGHT: - ProcessKey(KEY_POWER, 1); // press power key - ProcessKey(KEY_POWER, 0); // and release it - break; - }; + // Check for horizontal swipe + Point delta = touch_pos_ - touch_start_; + if (abs(delta.y()) < kTouchLowThreshold && abs(delta.x()) > kTouchHighThreshold) { + int key = (delta.x() < 0) ? KEY_BACK : KEY_POWER; + ProcessKey(key, 1); // press key + ProcessKey(key, 0); // and release it + return; + } + + // Simple touch + EnqueueTouch(touch_pos_); } int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { @@ -279,10 +290,6 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { // 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 // @@ -299,14 +306,17 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { 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_ = touch_pos_; - touch_swiping_ = true; - } else if (!touch_finger_down_ && touch_swiping_) { - touch_swiping_ = false; - OnTouchEvent(); + // There might be multiple SYN_REPORT events. Only report press/release once. + if (!touch_reported_ && touch_finger_down_) { + if (touch_saw_x_ && touch_saw_y_) { + OnTouchPress(); + touch_reported_ = true; + touch_saw_x_ = touch_saw_y_ = false; + } + } else if (touch_reported_ && !touch_finger_down_) { + OnTouchRelease(); + touch_reported_ = false; + touch_saw_x_ = touch_saw_y_ = false; } } return 0; @@ -342,13 +352,23 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { switch (ev.code) { case ABS_MT_POSITION_X: - touch_pos_.x(ev.value); touch_finger_down_ = true; + touch_saw_x_ = true; + touch_pos_.x(ev.value); + if (touch_reported_ && touch_saw_y_) { + OnTouchTrack(); + touch_saw_x_ = touch_saw_y_ = false; + } break; case ABS_MT_POSITION_Y: - touch_pos_.y(ev.value); touch_finger_down_ = true; + touch_saw_y_ = true; + touch_pos_.y(ev.value); + if (touch_reported_ && touch_saw_x_) { + OnTouchTrack(); + touch_saw_x_ = touch_saw_y_ = false; + } break; case ABS_MT_TRACKING_ID: @@ -398,7 +418,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { bool long_press = false; bool reboot_enabled; - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); key_pressed[key_code] = updown; if (updown) { ++key_down_count; @@ -419,7 +439,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { key_last_down = -1; } reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); if (register_key) { switch (CheckKey(key_code, long_press)) { @@ -459,27 +479,38 @@ void* RecoveryUI::time_key_helper(void* cookie) { void RecoveryUI::time_key(int key_code, int count) { usleep(750000); // 750 ms == "long" bool long_press = false; - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); if (key_last_down == key_code && key_down_count == count) { long_press = key_long_press = true; } - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); if (long_press) KeyLongPress(key_code); } void RecoveryUI::EnqueueKey(int key_code) { - pthread_mutex_lock(&key_queue_mutex); - const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); - if (key_queue_len < queue_max) { - key_queue[key_queue_len++] = key_code; - pthread_cond_signal(&key_queue_cond); + pthread_mutex_lock(&event_queue_mutex); + const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]); + if (event_queue_len < queue_max) { + InputEvent event(key_code); + event_queue[event_queue_len++] = event; + pthread_cond_signal(&event_queue_cond); + } + pthread_mutex_unlock(&event_queue_mutex); +} + +void RecoveryUI::EnqueueTouch(const Point& pos) { + pthread_mutex_lock(&event_queue_mutex); + const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]); + if (event_queue_len < queue_max) { + InputEvent event(pos); + event_queue[event_queue_len++] = event; + pthread_cond_signal(&event_queue_cond); } - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); } -int RecoveryUI::WaitKey() { - pthread_mutex_lock(&key_queue_mutex); - int timeouts = UI_WAIT_KEY_TIMEOUT_SEC; +RecoveryUI::InputEvent RecoveryUI::WaitInputEvent() { + pthread_mutex_lock(&event_queue_mutex); // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is // plugged in. @@ -492,16 +523,18 @@ int RecoveryUI::WaitKey() { timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; int rc = 0; - while (key_queue_len == 0 && rc != ETIMEDOUT) { + while (event_queue_len == 0 && rc != ETIMEDOUT) { + Print(""); // Force screen update struct timespec key_timeout; gettimeofday(&now, nullptr); key_timeout.tv_sec = now.tv_sec + 1; key_timeout.tv_nsec = now.tv_usec * 1000; - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &key_timeout); + rc = pthread_cond_timedwait(&event_queue_cond, &event_queue_mutex, &key_timeout); if (rc == ETIMEDOUT) { if (VolumesChanged()) { - pthread_mutex_unlock(&key_queue_mutex); - return KEY_REFRESH; + pthread_mutex_unlock(&event_queue_mutex); + InputEvent event(KEY_REFRESH); + return event; } if (key_timeout.tv_sec <= timeout.tv_sec) { rc = 0; @@ -528,8 +561,8 @@ int RecoveryUI::WaitKey() { } 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); + if (event_queue_len > 0) { + memcpy(&event_queue[0], &event_queue[1], sizeof(int) * --event_queue_len); } } @@ -542,23 +575,24 @@ int RecoveryUI::WaitKey() { } } } - } while (IsUsbConnected() && key_queue_len == 0); + } while (IsUsbConnected() && event_queue_len == 0); - int key = -1; - if (key_queue_len > 0) { - key = key_queue[0]; - memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + InputEvent event; + if (event_queue_len > 0) { + event = event_queue[0]; + memcpy(&event_queue[0], &event_queue[1], sizeof(event_queue[0]) * --event_queue_len); } - pthread_mutex_unlock(&key_queue_mutex); - return key; + pthread_mutex_unlock(&event_queue_mutex); + return event; } void RecoveryUI::CancelWaitKey() { - pthread_mutex_lock(&key_queue_mutex); - key_queue[key_queue_len++] = KEY_REFRESH; - pthread_cond_signal(&key_queue_cond); - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); + InputEvent event(KEY_REFRESH); + event_queue[event_queue_len++] = event; + pthread_cond_signal(&event_queue_cond); + pthread_mutex_unlock(&event_queue_mutex); } bool RecoveryUI::IsUsbConnected() { @@ -578,16 +612,16 @@ bool RecoveryUI::IsUsbConnected() { } bool RecoveryUI::IsKeyPressed(int key) { - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); int pressed = key_pressed[key]; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); return pressed; } bool RecoveryUI::IsLongPress() { - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); bool result = key_long_press; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); return result; } @@ -604,15 +638,15 @@ bool RecoveryUI::HasTouchScreen() const { } void RecoveryUI::FlushKeys() { - pthread_mutex_lock(&key_queue_mutex); - key_queue_len = 0; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); + event_queue_len = 0; + pthread_mutex_unlock(&event_queue_mutex); } RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); key_long_press = false; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); // 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_)) { @@ -635,9 +669,9 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { // Press power seven times in a row to reboot. if (key == KEY_POWER) { - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); bool reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); if (reboot_enabled) { ++consecutive_power_keys; @@ -657,9 +691,9 @@ void RecoveryUI::KeyLongPress(int) { } void RecoveryUI::SetEnableReboot(bool enabled) { - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&event_queue_mutex); enable_reboot = enabled; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_unlock(&event_queue_mutex); } void RecoveryUI::SetLocale(const std::string& new_locale) { @@ -22,9 +22,33 @@ #include <time.h> #include <string> +#include <vector> #include "voldclient.h" +enum menu_type_t { MT_NONE, MT_LIST, MT_GRID }; + +class MenuItem { + public: + MenuItem() {} + explicit MenuItem(const std::string& text, + const std::string& icon_name = "", + const std::string& icon_name_sel = "") : + text_(text), + icon_name_(icon_name), + icon_name_sel_(icon_name_sel) {} + + const std::string& text() const { return text_; } + const std::string& icon_name() const { return icon_name_; } + const std::string& icon_name_sel() const { return icon_name_sel_; } + + private: + std::string text_; + std::string icon_name_; + std::string icon_name_sel_; +}; +typedef std::vector<MenuItem> MenuItemVector; + /* * Simple representation of a (x,y) coordinate with convenience operators */ @@ -109,12 +133,37 @@ class RecoveryUI { virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; - virtual void ShowFile(const char* filename) = 0; - - // --- key handling --- + virtual int ShowFile(const char* filename) = 0; + + // --- event handling --- + + enum event_type_t { EVENT_TYPE_NONE, EVENT_TYPE_KEY, EVENT_TYPE_TOUCH }; + class InputEvent { + public: + InputEvent() : + type_(EVENT_TYPE_NONE), + evt_({0}) {} + explicit InputEvent(int key) : + type_(EVENT_TYPE_KEY), + evt_({key}) {} + explicit InputEvent(const Point& pos) : + type_(EVENT_TYPE_TOUCH), + evt_({0}) { evt_.pos = pos; } + + event_type_t type() const { return type_; } + int key() const { return evt_.key; } + const Point& pos() const { return evt_.pos; } + + private: + event_type_t type_; + union { + int key; + Point pos; + } evt_; + }; // Waits for a key and return it. May return -1 after timeout. - virtual int WaitKey(); + virtual InputEvent WaitInputEvent(); // Cancel a WaitKey() virtual void CancelWaitKey(); @@ -156,12 +205,16 @@ class RecoveryUI { // Display some header text followed by a menu of items, which appears at the top of the screen // (in place of any scrolling ui_print() output, if necessary). - virtual void StartMenu(const char* const* headers, const char* const* items, + virtual void StartMenu(bool is_main, + menu_type_t type, + const char* const* headers, + const MenuItemVector& items, int initial_selection) = 0; // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item // selected. virtual int SelectMenu(int sel) = 0; + virtual int SelectMenu(const Point& point) = 0; // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed. virtual void EndMenu() = 0; @@ -169,8 +222,17 @@ class RecoveryUI { // Notify of volume state change void onVolumeChanged() { volumes_changed_ = 1; } + virtual bool MenuShowing() const = 0; + virtual bool MenuScrollable() const = 0; + virtual int MenuItemStart() const = 0; + virtual int MenuItemHeight() const = 0; + protected: void EnqueueKey(int key_code); + void EnqueueTouch(const Point& pos); + + std::string android_version_; + std::string lineage_version_; // The locale that's used to show the rendered texts. std::string locale_; @@ -192,14 +254,15 @@ class RecoveryUI { const int kTouchHighThreshold; // Key event input queue - pthread_mutex_t key_queue_mutex; - pthread_cond_t key_queue_cond; - int key_queue[256], key_queue_len; - char key_pressed[KEY_MAX + 1]; // under key_queue_mutex - int key_last_down; // under key_queue_mutex - bool key_long_press; // under key_queue_mutex - int key_down_count; // under key_queue_mutex - bool enable_reboot; // under key_queue_mutex + pthread_mutex_t event_queue_mutex; + pthread_cond_t event_queue_cond; + InputEvent event_queue[256]; + int event_queue_len; + char key_pressed[KEY_MAX + 1]; // under event_queue_mutex + int key_last_down; // under event_queue_mutex + bool key_long_press; // under event_queue_mutex + int key_down_count; // under event_queue_mutex + bool enable_reboot; // under event_queue_mutex int rel_sum; int consecutive_power_keys; @@ -211,6 +274,11 @@ class RecoveryUI { bool has_touch_screen; struct vkey_t { + bool inside (const Point& p) const { + return (p.x() >= min_.x() && p.x() < max_.x() && + p.y() >= min_.y() && p.y() < max_.y()); + } + int keycode; Point min_; Point max_; @@ -218,10 +286,13 @@ class RecoveryUI { // Touch event related variables. See the comments in RecoveryUI::OnInputEvent(). int touch_slot_; + bool touch_finger_down_; + bool touch_saw_x_; + bool touch_saw_y_; + bool touch_reported_; Point touch_pos_; Point touch_start_; - bool touch_finger_down_; - bool touch_swiping_; + Point touch_track_; std::vector<vkey_t> virtual_keys_; bool is_bootreason_recovery_ui_; @@ -235,7 +306,9 @@ class RecoveryUI { void OnTouchDeviceDetected(int fd); void OnKeyDetected(int key_code); - void OnTouchEvent(); + void OnTouchPress(); + void OnTouchTrack(); + void OnTouchRelease(); int OnInputEvent(int fd, uint32_t epevents); void ProcessKey(int key_code, int updown); diff --git a/wear_ui.cpp b/wear_ui.cpp index 169ef20e..bd2ad44f 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -137,8 +137,8 @@ void WearRecoveryUI::draw_screen_locked() { // 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); + if ((int)menu_items_.size() > menu_end - menu_start) { + sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, (int)menu_items_.size()); gr_text(gr_sys_font(), x + 4, y, cur_selection_str, 1); y += char_height_ + 4; } @@ -147,18 +147,19 @@ void WearRecoveryUI::draw_screen_locked() { SetColor(MENU); for (int i = menu_start; i < menu_end; ++i) { + const ScreenMenuItem& item = menu_items_.at(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], 1); + if (!item.text().empty()) { + gr_text(gr_sys_font(), x + 4, y, item.text().c_str(), 1); } SetColor(MENU); - } else if (menu_[i][0]) { - gr_text(gr_sys_font(), x + 4, y, menu_[i], 0); + } else if (!item.text().empty()) { + gr_text(gr_sys_font(), x + 4, y, item.text().c_str(), 0); } y += char_height_ + 4; } @@ -250,7 +251,10 @@ void WearRecoveryUI::Print(const char* fmt, ...) { pthread_mutex_unlock(&updateMutex); } -void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items, +void WearRecoveryUI::StartMenu(bool is_main, + menu_type_t type, + const char* const* headers, + const MenuItemVector& items, int initial_selection) { pthread_mutex_lock(&updateMutex); if (text_rows_ > 0 && text_cols_ > 0) { @@ -261,16 +265,14 @@ void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* it // Because WearRecoveryUI supports scrollable menu, it's fine to have // more entries than text_rows_. The menu may be truncated otherwise. // Bug: 23752519 - for (; items[i] != nullptr; i++) { - strncpy(menu_[i], items[i], text_cols_ - 1); - menu_[i][text_cols_ - 1] = '\0'; + for (auto& item : items) { + menu_items_.push_back(ScreenMenuItem(item)); } - menu_items = i; show_menu = true; menu_sel = initial_selection; menu_start = 0; menu_end = visible_text_rows - 1 - kMenuUnusableRows; - if (menu_items <= menu_end) menu_end = menu_items; + if ((int)menu_items_.size() <= menu_end) menu_end = (int)menu_items_.size(); update_screen_locked(); } pthread_mutex_unlock(&updateMutex); @@ -283,11 +285,11 @@ int WearRecoveryUI::SelectMenu(int sel) { 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 >= (int)menu_items_.size()) menu_sel = (int)menu_items_.size() - 1; if (menu_sel < menu_start) { menu_start--; menu_end--; - } else if (menu_sel >= menu_end && menu_sel < menu_items) { + } else if (menu_sel >= menu_end && menu_sel < (int)menu_items_.size()) { menu_end++; menu_start++; } @@ -298,7 +300,11 @@ int WearRecoveryUI::SelectMenu(int sel) { return sel; } -void WearRecoveryUI::ShowFile(FILE* fp) { +int WearRecoveryUI::SelectMenu(const Point& point) { + return -1; +} + +int WearRecoveryUI::ShowFile(FILE* fp) { std::vector<off_t> offsets; offsets.push_back(ftello(fp)); ClearText(); @@ -315,10 +321,14 @@ void WearRecoveryUI::ShowFile(FILE* fp) { Redraw(); while (show_prompt) { show_prompt = false; - int key = WaitKey(); - if (key == KEY_POWER || key == KEY_ENTER) { - return; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + RecoveryUI::InputEvent evt = WaitInputEvent(); + if (evt.type() != RecoveryUI::EVENT_TYPE_KEY) { + show_prompt = true; + continue; + } + if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER) { + return evt.key(); + } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) { if (offsets.size() <= 1) { show_prompt = true; } else { @@ -327,7 +337,7 @@ void WearRecoveryUI::ShowFile(FILE* fp) { } } else { if (feof(fp)) { - return; + return -1; } offsets.push_back(ftello(fp)); } @@ -347,6 +357,7 @@ void WearRecoveryUI::ShowFile(FILE* fp) { } } } + return -1; } void WearRecoveryUI::PutChar(char ch) { @@ -359,14 +370,15 @@ void WearRecoveryUI::PutChar(char ch) { pthread_mutex_unlock(&updateMutex); } -void WearRecoveryUI::ShowFile(const char* filename) { +int WearRecoveryUI::ShowFile(const char* filename) { FILE* fp = fopen_path(filename, "re"); if (fp == nullptr) { Print(" Unable to open %s: %s\n", filename, strerror(errno)); - return; + return -1; } - ShowFile(fp); + int key = ShowFile(fp); fclose(fp); + return key; } void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { @@ -32,13 +32,17 @@ class WearRecoveryUI : public ScreenRecoveryUI { // printing messages void Print(const char* fmt, ...) override; void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); - void ShowFile(const char* filename) override; - void ShowFile(FILE* fp) override; + int ShowFile(const char* filename) override; + int ShowFile(FILE* fp) override; // menu display - void StartMenu(const char* const* headers, const char* const* items, + void StartMenu(bool is_main, + menu_type_t type, + const char* const* headers, + const MenuItemVector& items, int initial_selection) override; int SelectMenu(int sel) override; + int SelectMenu(const Point& point) override; protected: // progress bar vertical position, it's centered horizontally |