diff options
Diffstat (limited to 'screen_ui.cpp')
-rw-r--r-- | screen_ui.cpp | 1334 |
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; - } - } -} |