summaryrefslogtreecommitdiffstats
path: root/screen_ui.cpp
diff options
context:
space:
mode:
authorTianjie Xu <xunchang@google.com>2018-08-20 22:40:47 +0200
committerxunchang <xunchang@google.com>2019-03-21 08:45:37 +0100
commitb5108c372c8b92671ea5ebb4eeff00757fcee187 (patch)
tree6e3ec0b1c17e068568c089037b85c09692ad1608 /screen_ui.cpp
parentMerge "Move apply_from_sdcard to fuse_sdcard_install" am: 3c61cd02cf am: 3c21700fcd (diff)
downloadandroid_bootable_recovery-b5108c372c8b92671ea5ebb4eeff00757fcee187.tar
android_bootable_recovery-b5108c372c8b92671ea5ebb4eeff00757fcee187.tar.gz
android_bootable_recovery-b5108c372c8b92671ea5ebb4eeff00757fcee187.tar.bz2
android_bootable_recovery-b5108c372c8b92671ea5ebb4eeff00757fcee187.tar.lz
android_bootable_recovery-b5108c372c8b92671ea5ebb4eeff00757fcee187.tar.xz
android_bootable_recovery-b5108c372c8b92671ea5ebb4eeff00757fcee187.tar.zst
android_bootable_recovery-b5108c372c8b92671ea5ebb4eeff00757fcee187.zip
Diffstat (limited to 'screen_ui.cpp')
-rw-r--r--screen_ui.cpp1334
1 files changed, 0 insertions, 1334 deletions
diff --git a/screen_ui.cpp b/screen_ui.cpp
deleted file mode 100644
index 6f2b68b41..000000000
--- a/screen_ui.cpp
+++ /dev/null
@@ -1,1334 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "screen_ui.h"
-
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <chrono>
-#include <memory>
-#include <string>
-#include <thread>
-#include <unordered_map>
-#include <vector>
-
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.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).
-static double now() {
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- return tv.tv_sec + tv.tv_usec / 1000000.0;
-}
-
-Menu::Menu(size_t initial_selection, const DrawInterface& draw_func)
- : selection_(initial_selection), draw_funcs_(draw_func) {}
-
-size_t Menu::selection() const {
- return selection_;
-}
-
-TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length,
- const std::vector<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),
- progress(0),
- pagesIdentical(false),
- text_cols_(0),
- text_rows_(0),
- text_(nullptr),
- text_col_(0),
- text_row_(0),
- show_text(false),
- show_text_ever(false),
- scrollable_menu_(scrollable_menu),
- file_viewer_text_(nullptr),
- stage(-1),
- max_stage(-1),
- locale_(""),
- rtl_locale_(false) {}
-
-ScreenRecoveryUI::~ScreenRecoveryUI() {
- progress_thread_stopped_ = true;
- if (progress_thread_.joinable()) {
- progress_thread_.join();
- }
- // No-op if gr_init() (via Init()) was not called or had failed.
- gr_exit();
-}
-
-const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
- if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) {
- return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get();
- }
- return error_icon_.get();
-}
-
-const GRSurface* ScreenRecoveryUI::GetCurrentText() const {
- switch (current_icon_) {
- case ERASING:
- return erasing_text_.get();
- case ERROR:
- return error_text_.get();
- case INSTALLING_UPDATE:
- return installing_text_.get();
- case NO_COMMAND:
- return no_command_text_.get();
- case NONE:
- abort();
- }
-}
-
-int ScreenRecoveryUI::PixelsFromDp(int dp) const {
- return dp * density_;
-}
-
-// Here's the intended layout:
-
-// | portrait large landscape large
-// ---------+-------------------------------------------------
-// gap |
-// icon | (200dp)
-// gap | 68dp 68dp 56dp 112dp
-// text | (14sp)
-// gap | 32dp 32dp 26dp 52dp
-// progress | (2dp)
-// gap |
-
-// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines
-// work), so that's the more useful measurement for calling code. We use even top and bottom gaps.
-
-enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX };
-enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX };
-static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = {
- { 32, 68, }, // PORTRAIT
- { 32, 68, }, // PORTRAIT_LARGE
- { 26, 56, }, // LANDSCAPE
- { 52, 112, }, // LANDSCAPE_LARGE
-};
-
-int ScreenRecoveryUI::GetAnimationBaseline() const {
- return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
- gr_get_height(loop_frames_[0].get());
-}
-
-int ScreenRecoveryUI::GetTextBaseline() const {
- return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
- gr_get_height(installing_text_.get());
-}
-
-int ScreenRecoveryUI::GetProgressBaseline() const {
- int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) +
- gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) +
- gr_get_height(progress_bar_fill_.get());
- int bottom_gap = (ScreenHeight() - elements_sum) / 2;
- return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get());
-}
-
-// Clear the screen and draw the currently selected background icon (if any).
-// Should only be called with updateMutex locked.
-void ScreenRecoveryUI::draw_background_locked() {
- pagesIdentical = false;
- gr_color(0, 0, 0, 255);
- gr_clear();
- if (current_icon_ != NONE) {
- if (max_stage != -1) {
- int stage_height = gr_get_height(stage_marker_empty_.get());
- int stage_width = gr_get_width(stage_marker_empty_.get());
- int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2;
- int y = ScreenHeight() - stage_height - margin_height_;
- for (int i = 0; i < max_stage; ++i) {
- const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_;
- DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y);
- x += stage_width;
- }
- }
-
- const auto& text_surface = GetCurrentText();
- int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
- int text_y = GetTextBaseline();
- gr_color(255, 255, 255, 255);
- DrawTextIcon(text_x, text_y, text_surface);
- }
-}
-
-// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
-// called with updateMutex locked.
-void ScreenRecoveryUI::draw_foreground_locked() {
- if (current_icon_ != NONE) {
- const auto& frame = GetCurrentFrame();
- int frame_width = gr_get_width(frame);
- int frame_height = gr_get_height(frame);
- int frame_x = (ScreenWidth() - frame_width) / 2;
- int frame_y = GetAnimationBaseline();
- DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
- }
-
- if (progressBarType != EMPTY) {
- int width = gr_get_width(progress_bar_empty_.get());
- int height = gr_get_height(progress_bar_empty_.get());
-
- int progress_x = (ScreenWidth() - width) / 2;
- int progress_y = GetProgressBaseline();
-
- // Erase behind the progress bar (in case this was a progress-only update)
- gr_color(0, 0, 0, 255);
- DrawFill(progress_x, progress_y, width, height);
-
- if (progressBarType == DETERMINATE) {
- float p = progressScopeStart + progress * progressScopeSize;
- int pos = static_cast<int>(p * width);
-
- if (rtl_locale_) {
- // Fill the progress bar from right to left.
- if (pos > 0) {
- DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height,
- progress_x + width - pos, progress_y);
- }
- if (pos < width - 1) {
- DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y);
- }
- } else {
- // Fill the progress bar from left to right.
- if (pos > 0) {
- DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y);
- }
- if (pos < width - 1) {
- DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos,
- progress_y);
- }
- }
- }
- }
-}
-
-void ScreenRecoveryUI::SetColor(UIElement e) const {
- switch (e) {
- case UIElement::INFO:
- gr_color(249, 194, 0, 255);
- break;
- case UIElement::HEADER:
- gr_color(247, 0, 6, 255);
- break;
- case UIElement::MENU:
- case UIElement::MENU_SEL_BG:
- gr_color(0, 106, 157, 255);
- break;
- case UIElement::MENU_SEL_BG_ACTIVE:
- gr_color(0, 156, 100, 255);
- break;
- case UIElement::MENU_SEL_FG:
- gr_color(255, 255, 255, 255);
- break;
- case UIElement::LOG:
- gr_color(196, 196, 196, 255);
- break;
- case UIElement::TEXT_FILL:
- gr_color(0, 0, 0, 160);
- break;
- default:
- gr_color(255, 255, 255, 255);
- break;
- }
-}
-
-void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries,
- size_t sel) {
- 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>> surfaces;
- for (const auto& name : text_name) {
- auto text_image = LoadLocalizedBitmap(name);
- if (!text_image) {
- Print("Failed to load %s\n", name.c_str());
- return;
- }
- surfaces.emplace(name, std::move(text_image));
- }
-
- std::lock_guard<std::mutex> lg(updateMutex);
- gr_color(0, 0, 0, 255);
- gr_clear();
-
- int text_y = margin_height_;
- int text_x = margin_width_;
- int line_spacing = gr_sys_font()->char_height; // Put some extra space between images.
- // Write the header and descriptive texts.
- SetColor(UIElement::INFO);
- std::string header = "Show background text image";
- text_y += DrawTextLine(text_x, text_y, header, true);
- std::string locale_selection = android::base::StringPrintf(
- "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size());
- // clang-format off
- std::vector<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(UIElement::LOG);
- text_y += DrawTextLine(text_x, text_y, p.first, false);
- gr_color(255, 255, 255, 255);
- gr_texticon(text_x, text_y, p.second.get());
- text_y += gr_get_height(p.second.get());
- }
- // Update the whole screen.
- gr_flip();
-}
-
-void ScreenRecoveryUI::CheckBackgroundTextImages() {
- // Load a list of locales embedded in one of the resource files.
- std::vector<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) {
- selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
- SelectAndShowBackgroundText(locales_entries, selected);
- } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) {
- selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
- SelectAndShowBackgroundText(locales_entries, selected);
- }
- }
-
- SetLocale(saved_locale);
-}
-
-int ScreenRecoveryUI::ScreenWidth() const {
- return gr_fb_width();
-}
-
-int ScreenRecoveryUI::ScreenHeight() const {
- return gr_fb_height();
-}
-
-void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
- int dy) const {
- gr_blit(surface, sx, sy, w, h, dx, dy);
-}
-
-int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
- gr_fill(0, y + 4, ScreenWidth(), y + 6);
- return 8;
-}
-
-void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
- gr_fill(x, y, x + width, y + height);
-}
-
-void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
- gr_fill(x, y, w, h);
-}
-
-void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const {
- gr_texticon(x, y, surface);
-}
-
-int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
- gr_text(gr_sys_font(), x, y, line.c_str(), bold);
- return char_height_ + 4;
-}
-
-int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
- int offset = 0;
- for (const auto& line : lines) {
- offset += DrawTextLine(x, y + offset, line, false);
- }
- return offset;
-}
-
-int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y,
- const std::vector<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 (const auto& line : lines) {
- size_t next_start = 0;
- while (next_start < line.size()) {
- std::string sub = line.substr(next_start, text_cols + 1);
- if (sub.size() <= text_cols) {
- next_start += sub.size();
- } else {
- // Line too long and must be wrapped to text_cols columns.
- size_t last_space = sub.find_last_of(" \t\n");
- if (last_space == std::string::npos) {
- // No space found, just draw as much as we can.
- sub.resize(text_cols);
- next_start += text_cols;
- } else {
- sub.resize(last_space);
- next_start += last_space + 1;
- }
- }
- offset += DrawTextLine(x, y + offset, sub, false);
- }
- }
- return offset;
-}
-
-void ScreenRecoveryUI::SetTitle(const std::vector<std::string>& lines) {
- title_lines_ = lines;
-}
-
-// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
-// locked.
-void ScreenRecoveryUI::draw_screen_locked() {
- if (!show_text) {
- draw_background_locked();
- draw_foreground_locked();
- return;
- }
-
- gr_color(0, 0, 0, 255);
- gr_clear();
-
- // clang-format off
- static std::vector<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 (fastbootd_logo_ && fastbootd_logo_enabled_) {
- // Try to get this centered on screen.
- auto width = gr_get_width(fastbootd_logo_.get());
- auto height = gr_get_height(fastbootd_logo_.get());
- auto centered_x = ScreenWidth() / 2 - width / 2;
- DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y);
- y += height;
- }
-
- if (menu_) {
- int x = margin_width_ + kMenuIndent;
-
- SetColor(UIElement::INFO);
-
- for (size_t i = 0; i < title_lines_.size(); i++) {
- y += DrawTextLine(x, y, title_lines_[i], i == 0);
- }
-
- y += DrawTextLines(x, y, help_message);
-
- y += menu_->DrawHeader(x, y);
- y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress());
- }
-
- // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
- // we've displayed the entire text buffer.
- SetColor(UIElement::LOG);
- int row = text_row_;
- size_t count = 0;
- for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_;
- ty -= char_height_, ++count) {
- DrawTextLine(margin_width_, ty, text_[row], false);
- --row;
- if (row < 0) row = text_rows_ - 1;
- }
-}
-
-// Redraw everything on the screen and flip the screen (make it visible).
-// Should only be called with updateMutex locked.
-void ScreenRecoveryUI::update_screen_locked() {
- draw_screen_locked();
- gr_flip();
-}
-
-// Updates only the progress bar, if possible, otherwise redraws the screen.
-// Should only be called with updateMutex locked.
-void ScreenRecoveryUI::update_progress_locked() {
- if (show_text || !pagesIdentical) {
- draw_screen_locked(); // Must redraw the whole screen
- pagesIdentical = true;
- } else {
- draw_foreground_locked(); // Draw only the progress bar and overlays
- }
- gr_flip();
-}
-
-void ScreenRecoveryUI::ProgressThreadLoop() {
- double interval = 1.0 / animation_fps_;
- while (!progress_thread_stopped_) {
- double start = now();
- bool redraw = false;
- {
- std::lock_guard<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_ + 1) % loop_frames_.size();
- }
-
- redraw = true;
- }
-
- // move the progress bar forward on timed intervals, if configured
- int duration = progressScopeDuration;
- if (progressBarType == DETERMINATE && duration > 0) {
- double elapsed = now() - progressScopeTime;
- float p = 1.0 * elapsed / duration;
- if (p > 1.0) p = 1.0;
- if (p > progress) {
- progress = p;
- redraw = true;
- }
- }
-
- if (redraw) update_progress_locked();
- }
-
- double end = now();
- // minimum of 20ms delay between frames
- double delay = interval - (end - start);
- if (delay < 0.02) delay = 0.02;
- usleep(static_cast<useconds_t>(delay * 1000000));
- }
-}
-
-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);
-}
-
-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) {
- char** result = new char*[rows];
- for (size_t i = 0; i < rows; ++i) {
- result[i] = new char[cols];
- memset(result[i], 0, cols);
- }
- return result;
-}
-
-// Choose the right background string to display during update.
-void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
- if (security_update) {
- installing_text_ = LoadLocalizedBitmap("installing_security_text");
- } else {
- installing_text_ = LoadLocalizedBitmap("installing_text");
- }
- Redraw();
-}
-
-bool ScreenRecoveryUI::InitTextParams() {
- // gr_init() would return successfully on font initialization failure.
- if (gr_sys_font() == nullptr) {
- return false;
- }
- gr_font_size(gr_sys_font(), &char_width_, &char_height_);
- text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_;
- text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_;
- return true;
-}
-
-bool ScreenRecoveryUI::LoadWipeDataMenuText() {
- // Ignores the errors since the member variables will stay as nullptr.
- cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text");
- factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text");
- try_again_text_ = LoadLocalizedBitmap("try_again_text");
- wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text");
- wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text");
- return true;
-}
-
-bool ScreenRecoveryUI::Init(const std::string& locale) {
- RecoveryUI::Init(locale);
-
- if (gr_init() == -1) {
- return false;
- }
-
- if (!InitTextParams()) {
- return false;
- }
-
- // Are we portrait or landscape?
- layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
- // Are we the large variant of our base layout?
- if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
-
- text_ = Alloc2d(text_rows_, text_cols_ + 1);
- file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
-
- text_col_ = text_row_ = 0;
-
- // Set up the locale info.
- SetLocale(locale);
-
- error_icon_ = LoadBitmap("icon_error");
-
- progress_bar_empty_ = LoadBitmap("progress_empty");
- progress_bar_fill_ = LoadBitmap("progress_fill");
- stage_marker_empty_ = LoadBitmap("stage_empty");
- stage_marker_fill_ = LoadBitmap("stage_fill");
-
- erasing_text_ = LoadLocalizedBitmap("erasing_text");
- no_command_text_ = LoadLocalizedBitmap("no_command_text");
- error_text_ = LoadLocalizedBitmap("error_text");
-
- if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
- fastbootd_logo_ = LoadBitmap("fastbootd");
- }
-
- // Background text for "installing_update" could be "installing update" or
- // "installing security update". It will be set after Init() according to the commands in BCB.
- installing_text_.reset();
-
- LoadWipeDataMenuText();
-
- LoadAnimation();
-
- // Keep the progress bar updated, even when the process is otherwise busy.
- progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this);
-
- return true;
-}
-
-std::string ScreenRecoveryUI::GetLocale() const {
- return locale_;
-}
-
-void ScreenRecoveryUI::LoadAnimation() {
- std::unique_ptr<DIR, 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;
-
- while ((de = readdir(dir.get())) != nullptr) {
- int value, num_chars;
- if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
- intro_frame_names.emplace_back(de->d_name, num_chars);
- } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
- loop_frame_names.emplace_back(de->d_name, num_chars);
- }
- }
-
- size_t intro_frames = intro_frame_names.size();
- size_t loop_frames = loop_frame_names.size();
-
- // It's okay to not have an intro.
- if (intro_frames == 0) intro_done_ = true;
- // But you must have an animation.
- if (loop_frames == 0) abort();
-
- std::sort(intro_frame_names.begin(), intro_frame_names.end());
- std::sort(loop_frame_names.begin(), loop_frame_names.end());
-
- intro_frames_.clear();
- intro_frames_.reserve(intro_frames);
- for (const auto& frame_name : intro_frame_names) {
- intro_frames_.emplace_back(LoadBitmap(frame_name));
- }
-
- loop_frames_.clear();
- loop_frames_.reserve(loop_frames);
- for (const auto& frame_name : loop_frame_names) {
- loop_frames_.emplace_back(LoadBitmap(frame_name));
- }
-}
-
-void ScreenRecoveryUI::SetBackground(Icon icon) {
- std::lock_guard<std::mutex> lg(updateMutex);
-
- current_icon_ = icon;
- update_screen_locked();
-}
-
-void ScreenRecoveryUI::SetProgressType(ProgressType type) {
- std::lock_guard<std::mutex> lg(updateMutex);
- if (progressBarType != type) {
- progressBarType = type;
- }
- progressScopeStart = 0;
- progressScopeSize = 0;
- progress = 0;
- update_progress_locked();
-}
-
-void ScreenRecoveryUI::ShowProgress(float portion, float seconds) {
- std::lock_guard<std::mutex> lg(updateMutex);
- progressBarType = DETERMINATE;
- progressScopeStart += progressScopeSize;
- progressScopeSize = portion;
- progressScopeTime = now();
- progressScopeDuration = seconds;
- progress = 0;
- update_progress_locked();
-}
-
-void ScreenRecoveryUI::SetProgress(float fraction) {
- std::lock_guard<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(progress_bar_empty_.get());
- float scale = width * progressScopeSize;
- if ((int)(progress * scale) != (int)(fraction * scale)) {
- progress = fraction;
- update_progress_locked();
- }
- }
-}
-
-void ScreenRecoveryUI::SetStage(int current, int max) {
- std::lock_guard<std::mutex> lg(updateMutex);
- stage = current;
- max_stage = max;
-}
-
-void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
- std::string str;
- android::base::StringAppendV(&str, fmt, ap);
-
- if (copy_to_stdout) {
- fputs(str.c_str(), stdout);
- }
-
- std::lock_guard<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_) {
- text_[text_row_][text_col_] = '\0';
- text_col_ = 0;
- text_row_ = (text_row_ + 1) % text_rows_;
- }
- if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
- }
- text_[text_row_][text_col_] = '\0';
- update_screen_locked();
- }
-}
-
-void ScreenRecoveryUI::Print(const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- PrintV(fmt, true, ap);
- va_end(ap);
-}
-
-void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- PrintV(fmt, false, ap);
- va_end(ap);
-}
-
-void ScreenRecoveryUI::PutChar(char ch) {
- std::lock_guard<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_;
- }
-}
-
-void ScreenRecoveryUI::ClearText() {
- 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);
- }
-}
-
-void ScreenRecoveryUI::ShowFile(FILE* fp) {
- std::vector<off_t> offsets;
- offsets.push_back(ftello(fp));
- ClearText();
-
- struct stat sb;
- fstat(fileno(fp), &sb);
-
- bool show_prompt = false;
- while (true) {
- if (show_prompt) {
- PrintOnScreenOnly("--(%d%% of %d bytes)--",
- static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
- static_cast<int>(sb.st_size));
- Redraw();
- 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) {
- if (offsets.size() <= 1) {
- show_prompt = true;
- } else {
- offsets.pop_back();
- fseek(fp, offsets.back(), SEEK_SET);
- }
- } else {
- if (feof(fp)) {
- return;
- }
- offsets.push_back(ftello(fp));
- }
- }
- ClearText();
- }
-
- int ch = getc(fp);
- if (ch == EOF) {
- while (text_row_ < text_rows_ - 1) PutChar('\n');
- show_prompt = true;
- } else {
- PutChar(ch);
- if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
- show_prompt = true;
- }
- }
- }
-}
-
-void ScreenRecoveryUI::ShowFile(const std::string& filename) {
- std::unique_ptr<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;
- }
-
- char** old_text = text_;
- size_t old_text_col = text_col_;
- size_t old_text_row = text_row_;
-
- // Swap in the alternate screen and clear it.
- text_ = file_viewer_text_;
- ClearText();
-
- ShowFile(fp.get());
-
- text_ = old_text;
- text_col_ = old_text_col;
- text_row_ = old_text_row;
-}
-
-std::unique_ptr<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);
- }
-
- fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n");
-
- return CreateMenu(text_headers, text_items, initial_selection);
-}
-
-std::unique_ptr<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);
- }
-
- 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);
-
- if (sel != old_sel) {
- update_screen_locked();
- }
- }
- return sel;
-}
-
-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;
- }
- }
-
- 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() {
- std::lock_guard<std::mutex> lg(updateMutex);
- int visible = show_text;
- return visible;
-}
-
-bool ScreenRecoveryUI::WasTextEverVisible() {
- std::lock_guard<std::mutex> lg(updateMutex);
- int ever_visible = show_text_ever;
- return ever_visible;
-}
-
-void ScreenRecoveryUI::ShowText(bool visible) {
- std::lock_guard<std::mutex> lg(updateMutex);
- show_text = visible;
- if (show_text) show_text_ever = true;
- update_screen_locked();
-}
-
-void ScreenRecoveryUI::Redraw() {
- std::lock_guard<std::mutex> lg(updateMutex);
- update_screen_locked();
-}
-
-void ScreenRecoveryUI::KeyLongPress(int) {
- // Redraw so that if we're in the menu, the highlight
- // will change color to indicate a successful long press.
- Redraw();
-}
-
-void ScreenRecoveryUI::SetLocale(const std::string& new_locale) {
- locale_ = new_locale;
- rtl_locale_ = false;
-
- if (!new_locale.empty()) {
- size_t separator = new_locale.find('-');
- // lang has the language prefix prior to the separator, or full string if none exists.
- std::string lang = new_locale.substr(0, separator);
-
- // A bit cheesy: keep an explicit list of supported RTL languages.
- if (lang == "ar" || // Arabic
- lang == "fa" || // Persian (Farsi)
- lang == "he" || // Hebrew (new language code)
- lang == "iw" || // Hebrew (old language code)
- lang == "ur") { // Urdu
- rtl_locale_ = true;
- }
- }
-}