diff options
Diffstat (limited to 'screen_ui.cpp')
-rw-r--r-- | screen_ui.cpp | 927 |
1 files changed, 644 insertions, 283 deletions
diff --git a/screen_ui.cpp b/screen_ui.cpp index c8fb5aa75..6c00a2235 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -19,8 +19,6 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> -#include <linux/input.h> -#include <pthread.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -31,8 +29,11 @@ #include <time.h> #include <unistd.h> +#include <algorithm> +#include <chrono> #include <memory> #include <string> +#include <thread> #include <unordered_map> #include <vector> @@ -40,10 +41,10 @@ #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include <minui/minui.h> -#include "common.h" #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). @@ -53,12 +54,268 @@ static double now() { return tv.tv_sec + tv.tv_usec / 1000000.0; } -ScreenRecoveryUI::ScreenRecoveryUI() - : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH), - kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT), - kAnimationFps(RECOVERY_UI_ANIMATION_FPS), - kDensity(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), - currentIcon(NONE), +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<std::string>& headers, const std::vector<std::string>& 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<size_t>(std::numeric_limits<int>::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<std::string>& 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<size_t>(std::numeric_limits<int>::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<size_t>(sel) < menu_start_) { + menu_start_--; + } else if (static_cast<size_t>(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<const GRSurface*>& 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<size_t>(std::numeric_limits<int>::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<const GRSurface*>& 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<float>(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), @@ -71,45 +328,46 @@ 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), - current_frame(0), - intro_done(false), stage(-1), max_stage(-1), locale_(""), - rtl_locale_(false), - updateMutex(PTHREAD_MUTEX_INITIALIZER) {} + 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(); +} -GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { - if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; +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; + return error_icon_.get(); } -GRSurface* ScreenRecoveryUI::GetCurrentText() const { - switch (currentIcon) { +const GRSurface* ScreenRecoveryUI::GetCurrentText() const { + switch (current_icon_) { case ERASING: - return erasing_text; + return erasing_text_.get(); case ERROR: - return error_text; + return error_text_.get(); case INSTALLING_UPDATE: - return installing_text; + return installing_text_.get(); case NO_COMMAND: - return no_command_text; + return no_command_text_.get(); case NONE: abort(); } } int ScreenRecoveryUI::PixelsFromDp(int dp) const { - return dp * kDensity; + return dp * density_; } // Here's the intended layout: @@ -137,20 +395,21 @@ static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { }; int ScreenRecoveryUI::GetAnimationBaseline() const { - return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]); + 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); + gr_get_height(installing_text_.get()); } int ScreenRecoveryUI::GetProgressBaseline() const { - int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) + - gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) + - gr_get_height(progressBarFill); + 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(progressBarFill); + return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get()); } // Clear the screen and draw the currently selected background icon (if any). @@ -159,20 +418,20 @@ void ScreenRecoveryUI::draw_background_locked() { pagesIdentical = false; gr_color(0, 0, 0, 255); gr_clear(); - if (currentIcon != NONE) { + if (current_icon_ != NONE) { if (max_stage != -1) { - int stage_height = gr_get_height(stageMarkerEmpty); - int stage_width = gr_get_width(stageMarkerEmpty); - int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; - int y = ScreenHeight() - stage_height - kMarginHeight; + 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) { - GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; - DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y); + 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; } } - GRSurface* text_surface = GetCurrentText(); + 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); @@ -183,8 +442,8 @@ 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) { - GRSurface* frame = GetCurrentFrame(); + 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; @@ -193,8 +452,8 @@ void ScreenRecoveryUI::draw_foreground_locked() { } if (progressBarType != EMPTY) { - int width = gr_get_width(progressBarEmpty); - int height = gr_get_height(progressBarEmpty); + 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(); @@ -210,19 +469,20 @@ void ScreenRecoveryUI::draw_foreground_locked() { if (rtl_locale_) { // Fill the progress bar from right to left. if (pos > 0) { - DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos, - progress_y); + DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height, + progress_x + width - pos, progress_y); } if (pos < width - 1) { - DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y); + 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(progressBarFill, 0, 0, pos, height, progress_x, progress_y); + DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y); } if (pos < width - 1) { - DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y); + DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos, + progress_y); } } } @@ -231,26 +491,26 @@ void ScreenRecoveryUI::draw_foreground_locked() { void ScreenRecoveryUI::SetColor(UIElement e) const { switch (e) { - case INFO: + case UIElement::INFO: gr_color(249, 194, 0, 255); break; - case HEADER: + case UIElement::HEADER: gr_color(247, 0, 6, 255); break; - case MENU: - case MENU_SEL_BG: + case UIElement::MENU: + case UIElement::MENU_SEL_BG: gr_color(0, 106, 157, 255); break; - case MENU_SEL_BG_ACTIVE: + case UIElement::MENU_SEL_BG_ACTIVE: gr_color(0, 156, 100, 255); break; - case MENU_SEL_FG: + case UIElement::MENU_SEL_FG: gr_color(255, 255, 255, 255); break; - case LOG: + case UIElement::LOG: gr_color(196, 196, 196, 255); break; - case TEXT_FILL: + case UIElement::TEXT_FILL: gr_color(0, 0, 0, 160); break; default: @@ -264,62 +524,65 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string SetLocale(locales_entries[sel]); std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text", "installing_security_text", "no_command_text" }; - std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces; + std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces; for (const auto& name : text_name) { - GRSurface* text_image = nullptr; - LoadLocalizedBitmap(name.c_str(), &text_image); + auto text_image = LoadLocalizedBitmap(name); if (!text_image) { Print("Failed to load %s\n", name.c_str()); return; } - surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free)); + surfaces.emplace(name, std::move(text_image)); } - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); gr_color(0, 0, 0, 255); gr_clear(); - int text_y = kMarginHeight; - int text_x = kMarginWidth; + 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(INFO); + SetColor(UIElement::INFO); std::string header = "Show background text image"; - text_y += DrawTextLine(text_x, text_y, header.c_str(), true); + 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, locales_entries.size()); - const char* instruction[] = { locale_selection.c_str(), - "Use volume up/down to switch locales and power to exit.", - nullptr }; + "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size()); + // clang-format off + std::vector<std::string> 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(LOG); - text_y += DrawTextLine(text_x, text_y, p.first.c_str(), false); + 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(); - pthread_mutex_unlock(&updateMutex); } -void ScreenRecoveryUI::CheckBackgroundTextImages(const std::string& saved_locale) { +void ScreenRecoveryUI::CheckBackgroundTextImages() { // Load a list of locales embedded in one of the resource files. std::vector<std::string> 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<int>(KeyError::INTERRUPTED)) break; if (key == KEY_POWER || key == KEY_ENTER) { break; } else if (key == KEY_UP || key == KEY_VOLUMEUP) { @@ -342,7 +605,7 @@ int ScreenRecoveryUI::ScreenHeight() const { return gr_fb_height(); } -void ScreenRecoveryUI::DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, +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); } @@ -360,61 +623,55 @@ 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, GRSurface* surface) const { +void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { gr_texticon(x, y, surface); } -int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { - gr_text(gr_sys_font(), x, y, line, bold); +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 char* const* lines) const { +int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const { int offset = 0; - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - offset += DrawTextLine(x, y + offset, lines[i], false); + for (const auto& line : lines) { + offset += DrawTextLine(x, y + offset, line, false); } return offset; } -int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const { +int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, + const std::vector<std::string>& 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 (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - // The line will be wrapped if it exceeds text_cols_. - std::string line(lines[i]); + for (const auto& line : lines) { size_t next_start = 0; while (next_start < line.size()) { - std::string sub = line.substr(next_start, text_cols_ + 1); - if (sub.size() <= text_cols_) { + std::string sub = line.substr(next_start, text_cols + 1); + if (sub.size() <= text_cols) { next_start += sub.size(); } else { - // Line too long and must be wrapped to text_cols_ columns. + // Line too long and must be wrapped to text_cols columns. size_t last_space = sub.find_last_of(" \t\n"); if (last_space == std::string::npos) { - // No space found, just draw as much as we can - sub.resize(text_cols_); - next_start += text_cols_; + // 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.c_str(), false); + offset += DrawTextLine(x, y + offset, sub, false); } } return offset; } -static const char* REGULAR_HELP[] = { - "Use volume up/down and power.", - NULL -}; - -static const char* LONG_PRESS_HELP[] = { - "Any button cycles highlight.", - "Long-press activates.", - NULL -}; +void ScreenRecoveryUI::SetTitle(const std::vector<std::string>& lines) { + title_lines_ = lines; +} // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex // locked. @@ -428,50 +685,45 @@ void ScreenRecoveryUI::draw_screen_locked() { gr_color(0, 0, 0, 255); gr_clear(); - 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); - } - 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, ScreenWidth(), char_height_ + 4); - // 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); - } + // clang-format off + static std::vector<std::string> REGULAR_HELP{ + "Use volume up/down and power.", + }; + static std::vector<std::string> 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<std::string>& help_message) { + int y = margin_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 += DrawHorizontalRule(y); + + 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(LOG); + SetColor(UIElement::LOG); int row = text_row_; size_t count = 0; - for (int ty = ScreenHeight() - kMarginHeight - char_height_; ty >= y && count < text_rows_; + for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_; ty -= char_height_, ++count) { - DrawTextLine(kMarginWidth, ty, text_[row], false); + DrawTextLine(margin_width_, ty, text_[row], false); --row; if (row < 0) row = text_rows_ - 1; } @@ -496,52 +748,46 @@ void ScreenRecoveryUI::update_progress_locked() { gr_flip(); } -// Keeps the progress bar updated, even when the process is otherwise busy. -void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { - reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); - return nullptr; -} - void ScreenRecoveryUI::ProgressThreadLoop() { - double interval = 1.0 / kAnimationFps; - while (true) { + double interval = 1.0 / animation_fps_; + while (!progress_thread_stopped_) { double start = now(); - pthread_mutex_lock(&updateMutex); - bool redraw = false; - - // update the installation animation, if active - // skip this if we have a text overlay (too expensive to update) - if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { - if (!intro_done) { - if (current_frame == intro_frames - 1) { - intro_done = true; - current_frame = 0; + { + std::lock_guard<std::mutex> 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_ = (current_frame_ + 1) % loop_frames_.size(); } - } else { - current_frame = (current_frame + 1) % loop_frames; - } - redraw = true; - } - - // move the progress bar forward on timed intervals, if configured - int duration = progressScopeDuration; - if (progressBarType == DETERMINATE && duration > 0) { - double elapsed = now() - progressScopeTime; - float p = 1.0 * elapsed / duration; - if (p > 1.0) p = 1.0; - if (p > progress) { - progress = p; redraw = true; } - } - if (redraw) update_progress_locked(); + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = true; + } + } + + if (redraw) update_progress_locked(); + } - pthread_mutex_unlock(&updateMutex); double end = now(); // minimum of 20ms delay between frames double delay = interval - (end - start); @@ -550,18 +796,23 @@ void ScreenRecoveryUI::ProgressThreadLoop() { } } -void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { - int result = res_create_display_surface(filename, surface); - if (result < 0) { - LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; +std::unique_ptr<GRSurface> 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<GRSurface>(surface); } -void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { - int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface); - if (result < 0) { - LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; +std::unique_ptr<GRSurface> 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<GRSurface>(surface); } static char** Alloc2d(size_t rows, size_t cols) { @@ -576,27 +827,41 @@ static char** Alloc2d(size_t rows, size_t cols) { // Choose the right background string to display during update. void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { if (security_update) { - LoadLocalizedBitmap("installing_security_text", &installing_text); + installing_text_ = LoadLocalizedBitmap("installing_security_text"); } else { - LoadLocalizedBitmap("installing_text", &installing_text); + installing_text_ = LoadLocalizedBitmap("installing_text"); } Redraw(); } bool ScreenRecoveryUI::InitTextParams() { - if (gr_init() < 0) { + // 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() - kMarginHeight * 2) / char_height_; - text_cols_ = (ScreenWidth() - kMarginWidth * 2) / char_width_; + 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; } @@ -614,31 +879,38 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { // Set up the locale info. SetLocale(locale); - LoadBitmap("icon_error", &error_icon); + error_icon_ = LoadBitmap("icon_error"); - LoadBitmap("progress_empty", &progressBarEmpty); - LoadBitmap("progress_fill", &progressBarFill); + progress_bar_empty_ = LoadBitmap("progress_empty"); + progress_bar_fill_ = LoadBitmap("progress_fill"); + stage_marker_empty_ = LoadBitmap("stage_empty"); + stage_marker_fill_ = LoadBitmap("stage_fill"); - LoadBitmap("stage_empty", &stageMarkerEmpty); - LoadBitmap("stage_fill", &stageMarkerFill); + erasing_text_ = LoadLocalizedBitmap("erasing_text"); + no_command_text_ = LoadLocalizedBitmap("no_command_text"); + error_text_ = LoadLocalizedBitmap("error_text"); - // Background text for "installing_update" could be "installing update" - // or "installing security update". It will be set after UI init according - // to commands in BCB. - installing_text = nullptr; - LoadLocalizedBitmap("erasing_text", &erasing_text); - LoadLocalizedBitmap("no_command_text", &no_command_text); - LoadLocalizedBitmap("error_text", &error_text); + // 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(); - pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); + // Keep the progress bar updated, even when the process is otherwise busy. + progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this); return true; } +std::string ScreenRecoveryUI::GetLocale() const { + return locale_; +} + void ScreenRecoveryUI::LoadAnimation() { - std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir); + std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(Paths::Get().resource_dir().c_str()), + closedir); dirent* de; std::vector<std::string> intro_frame_names; std::vector<std::string> loop_frame_names; @@ -652,39 +924,39 @@ void ScreenRecoveryUI::LoadAnimation() { } } - intro_frames = intro_frame_names.size(); - loop_frames = loop_frame_names.size(); + 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; + 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()); - introFrames = new GRSurface*[intro_frames]; - for (size_t i = 0; i < intro_frames; i++) { - LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]); + intro_frames_.clear(); + intro_frames_.reserve(intro_frames); + for (const auto& frame_name : intro_frame_names) { + intro_frames_.emplace_back(LoadBitmap(frame_name)); } - loopFrames = new GRSurface*[loop_frames]; - for (size_t i = 0; i < loop_frames; i++) { - LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]); + 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) { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); - currentIcon = icon; + current_icon_ = icon; update_screen_locked(); - - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetProgressType(ProgressType type) { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); if (progressBarType != type) { progressBarType = type; } @@ -692,11 +964,10 @@ void ScreenRecoveryUI::SetProgressType(ProgressType type) { progressScopeSize = 0; progress = 0; update_progress_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); progressBarType = DETERMINATE; progressScopeStart += progressScopeSize; progressScopeSize = portion; @@ -704,30 +975,27 @@ void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { progressScopeDuration = seconds; progress = 0; update_progress_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetProgress(float fraction) { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> 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(progressBarEmpty); + 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(); } } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetStage(int current, int max) { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); stage = current; max_stage = max; - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { @@ -738,7 +1006,7 @@ void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) fputs(str.c_str(), stdout); } - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> 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_) { @@ -751,7 +1019,6 @@ void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) text_[text_row_][text_col_] = '\0'; update_screen_locked(); } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::Print(const char* fmt, ...) { @@ -769,23 +1036,21 @@ void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { } void ScreenRecoveryUI::PutChar(char ch) { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); if (ch != '\n') text_[text_row_][text_col_++] = ch; if (ch == '\n' || text_col_ >= text_cols_) { text_col_ = 0; ++text_row_; } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ClearText() { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); text_col_ = 0; text_row_ = 0; for (size_t i = 0; i < text_rows_; ++i) { memset(text_[i], 0, text_cols_ + 1); } - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ShowFile(FILE* fp) { @@ -806,6 +1071,7 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { while (show_prompt) { show_prompt = false; int key = WaitKey(); + if (key == static_cast<int>(KeyError::INTERRUPTED)) return; if (key == KEY_POWER || key == KEY_ENTER) { return; } else if (key == KEY_UP || key == KEY_VOLUMEUP) { @@ -838,10 +1104,10 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { } } -void ScreenRecoveryUI::ShowFile(const char* filename) { - FILE* fp = fopen_path(filename, "re"); - if (fp == nullptr) { - Print(" Unable to open %s: %s\n", filename, strerror(errno)); +void ScreenRecoveryUI::ShowFile(const std::string& filename) { + std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "re"), fclose); + if (!fp) { + Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno)); return; } @@ -853,83 +1119,178 @@ void ScreenRecoveryUI::ShowFile(const char* filename) { text_ = file_viewer_text_; ClearText(); - ShowFile(fp); - fclose(fp); + ShowFile(fp.get()); text_ = old_text; text_col_ = old_text_col; text_row_ = old_text_row; } -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<int>(menu_.size()); - show_menu = true; - menu_sel = initial_selection; - update_screen_locked(); +std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu( + const GRSurface* graphic_header, const std::vector<const GRSurface*>& graphic_items, + const std::vector<std::string>& text_headers, const std::vector<std::string>& 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<GraphicMenu>(graphic_header, graphic_items, initial_selection, *this); } - pthread_mutex_unlock(&updateMutex); + + fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n"); + + return CreateMenu(text_headers, text_items, initial_selection); } -int ScreenRecoveryUI::SelectMenu(int sel) { - pthread_mutex_lock(&updateMutex); - if (show_menu) { - int old_sel = menu_sel; - menu_sel = sel; +std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(const std::vector<std::string>& text_headers, + const std::vector<std::string>& text_items, + size_t initial_selection) const { + if (text_rows_ > 0 && text_cols_ > 1) { + return std::make_unique<TextMenu>(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers, + text_items, initial_selection, char_height_, *this); + } - // Wrap at top and bottom. - if (menu_sel < 0) menu_sel = menu_items - 1; - if (menu_sel >= menu_items) menu_sel = 0; + 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<std::mutex> lg(updateMutex); + 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; } -void ScreenRecoveryUI::EndMenu() { - pthread_mutex_lock(&updateMutex); - if (show_menu && text_rows_ > 0 && text_cols_ > 0) { - show_menu = false; - update_screen_locked(); +size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only, + const std::function<int(int, bool)>& 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<size_t>(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<int>(KeyError::INTERRUPTED)) { // WaitKey() was interrupted. + return static_cast<size_t>(KeyError::INTERRUPTED); + } + if (key == static_cast<int>(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<size_t>(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; + } } - pthread_mutex_unlock(&updateMutex); + + menu_.reset(); + Redraw(); + + return chosen_item; +} + +size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers, + const std::vector<std::string>& items, size_t initial_selection, + bool menu_only, + const std::function<int(int, bool)>& 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<std::string>& backup_headers, + const std::vector<std::string>& backup_items, + const std::function<int(int, bool)>& 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<std::string>& backup_headers, const std::vector<std::string>& backup_items, + const std::function<int(int, bool)>& 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() { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); int visible = show_text; - pthread_mutex_unlock(&updateMutex); return visible; } bool ScreenRecoveryUI::WasTextEverVisible() { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); int ever_visible = show_text_ever; - pthread_mutex_unlock(&updateMutex); return ever_visible; } void ScreenRecoveryUI::ShowText(bool visible) { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); show_text = visible; if (show_text) show_text_ever = true; update_screen_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::Redraw() { - pthread_mutex_lock(&updateMutex); + std::lock_guard<std::mutex> lg(updateMutex); update_screen_locked(); - pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::KeyLongPress(int) { @@ -943,9 +1304,9 @@ void ScreenRecoveryUI::SetLocale(const std::string& new_locale) { rtl_locale_ = false; if (!new_locale.empty()) { - size_t underscore = new_locale.find('_'); - // lang has the language prefix prior to '_', or full string if '_' doesn't exist. - std::string lang = new_locale.substr(0, underscore); + 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 |