1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "recovery_ui/screen_ui.h" 18 19 #include <dirent.h> 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <stdarg.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <sys/stat.h> 27 #include <sys/time.h> 28 #include <sys/types.h> 29 #include <time.h> 30 #include <unistd.h> 31 32 #include <algorithm> 33 #include <chrono> 34 #include <memory> 35 #include <string> 36 #include <thread> 37 #include <unordered_map> 38 #include <vector> 39 40 #include <android-base/logging.h> 41 #include <android-base/properties.h> 42 #include <android-base/stringprintf.h> 43 #include <android-base/strings.h> 44 45 #include "minui/minui.h" 46 #include "otautil/paths.h" 47 #include "recovery_ui/device.h" 48 #include "recovery_ui/ui.h" 49 50 // Return the current time as a double (including fractions of a second). 51 static double now() { 52 struct timeval tv; 53 gettimeofday(&tv, nullptr); 54 return tv.tv_sec + tv.tv_usec / 1000000.0; 55 } 56 57 Menu::Menu(size_t initial_selection, const DrawInterface& draw_func) 58 : selection_(initial_selection), draw_funcs_(draw_func) {} 59 60 size_t Menu::selection() const { 61 return selection_; 62 } 63 64 TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length, 65 const std::vector<std::string>& headers, const std::vector<std::string>& items, 66 size_t initial_selection, int char_height, const DrawInterface& draw_funcs) 67 : Menu(initial_selection, draw_funcs), 68 scrollable_(scrollable), 69 max_display_items_(max_items), 70 max_item_length_(max_length), 71 text_headers_(headers), 72 menu_start_(0), 73 char_height_(char_height) { 74 CHECK_LE(max_items, static_cast<size_t>(std::numeric_limits<int>::max())); 75 76 // It's fine to have more entries than text_rows_ if scrollable menu is supported. 77 size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_); 78 for (size_t i = 0; i < items_count; ++i) { 79 text_items_.emplace_back(items[i].substr(0, max_item_length_)); 80 } 81 82 CHECK(!text_items_.empty()); 83 } 84 85 const std::vector<std::string>& TextMenu::text_headers() const { 86 return text_headers_; 87 } 88 89 std::string TextMenu::TextItem(size_t index) const { 90 CHECK_LT(index, text_items_.size()); 91 92 return text_items_[index]; 93 } 94 95 size_t TextMenu::MenuStart() const { 96 return menu_start_; 97 } 98 99 size_t TextMenu::MenuEnd() const { 100 return std::min(ItemsCount(), menu_start_ + max_display_items_); 101 } 102 103 size_t TextMenu::ItemsCount() const { 104 return text_items_.size(); 105 } 106 107 bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const { 108 if (!scrollable_ || ItemsCount() <= max_display_items_) { 109 return false; 110 } 111 112 *cur_selection_str = 113 android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount()); 114 return true; 115 } 116 117 // TODO(xunchang) modify the function parameters to button up & down. 118 int TextMenu::Select(int sel) { 119 CHECK_LE(ItemsCount(), static_cast<size_t>(std::numeric_limits<int>::max())); 120 int count = ItemsCount(); 121 122 // Wraps the selection at boundary if the menu is not scrollable. 123 if (!scrollable_) { 124 if (sel < 0) { 125 selection_ = count - 1; 126 } else if (sel >= count) { 127 selection_ = 0; 128 } else { 129 selection_ = sel; 130 } 131 132 return selection_; 133 } 134 135 if (sel < 0) { 136 selection_ = 0; 137 } else if (sel >= count) { 138 selection_ = count - 1; 139 } else { 140 if (static_cast<size_t>(sel) < menu_start_) { 141 menu_start_--; 142 } else if (static_cast<size_t>(sel) >= MenuEnd()) { 143 menu_start_++; 144 } 145 selection_ = sel; 146 } 147 148 return selection_; 149 } 150 151 int TextMenu::DrawHeader(int x, int y) const { 152 int offset = 0; 153 154 draw_funcs_.SetColor(UIElement::HEADER); 155 if (!scrollable()) { 156 offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers()); 157 } else { 158 offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers()); 159 // Show the current menu item number in relation to total number if items don't fit on the 160 // screen. 161 std::string cur_selection_str; 162 if (ItemsOverflow(&cur_selection_str)) { 163 offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true); 164 } 165 } 166 167 return offset; 168 } 169 170 int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { 171 int offset = 0; 172 173 draw_funcs_.SetColor(UIElement::MENU); 174 // Do not draw the horizontal rule for wear devices. 175 if (!scrollable()) { 176 offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; 177 } 178 for (size_t i = MenuStart(); i < MenuEnd(); ++i) { 179 bool bold = false; 180 if (i == selection()) { 181 // Draw the highlight bar. 182 draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); 183 184 int bar_height = char_height_ + 4; 185 draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); 186 187 // Bold white text for the selected item. 188 draw_funcs_.SetColor(UIElement::MENU_SEL_FG); 189 bold = true; 190 } 191 offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), bold); 192 193 draw_funcs_.SetColor(UIElement::MENU); 194 } 195 offset += draw_funcs_.DrawHorizontalRule(y + offset); 196 197 return offset; 198 } 199 200 GraphicMenu::GraphicMenu(const GRSurface* graphic_headers, 201 const std::vector<const GRSurface*>& graphic_items, 202 size_t initial_selection, const DrawInterface& draw_funcs) 203 : Menu(initial_selection, draw_funcs) { 204 graphic_headers_ = graphic_headers->Clone(); 205 graphic_items_.reserve(graphic_items.size()); 206 for (const auto& item : graphic_items) { 207 graphic_items_.emplace_back(item->Clone()); 208 } 209 } 210 211 int GraphicMenu::Select(int sel) { 212 CHECK_LE(graphic_items_.size(), static_cast<size_t>(std::numeric_limits<int>::max())); 213 int count = graphic_items_.size(); 214 215 // Wraps the selection at boundary if the menu is not scrollable. 216 if (sel < 0) { 217 selection_ = count - 1; 218 } else if (sel >= count) { 219 selection_ = 0; 220 } else { 221 selection_ = sel; 222 } 223 224 return selection_; 225 } 226 227 int GraphicMenu::DrawHeader(int x, int y) const { 228 draw_funcs_.SetColor(UIElement::HEADER); 229 draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get()); 230 return graphic_headers_->height; 231 } 232 233 int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { 234 int offset = 0; 235 236 draw_funcs_.SetColor(UIElement::MENU); 237 offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4; 238 239 for (size_t i = 0; i < graphic_items_.size(); i++) { 240 auto& item = graphic_items_[i]; 241 if (i == selection_) { 242 draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG); 243 244 int bar_height = item->height + 4; 245 draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height); 246 247 // Bold white text for the selected item. 248 draw_funcs_.SetColor(UIElement::MENU_SEL_FG); 249 } 250 draw_funcs_.DrawTextIcon(x, y + offset, item.get()); 251 offset += item->height; 252 253 draw_funcs_.SetColor(UIElement::MENU); 254 } 255 offset += draw_funcs_.DrawHorizontalRule(y + offset); 256 257 return offset; 258 } 259 260 bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, 261 const std::vector<const GRSurface*>& graphic_items) { 262 int offset = 0; 263 if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) { 264 return false; 265 } 266 offset += graphic_headers->height; 267 268 for (const auto& item : graphic_items) { 269 if (!ValidateGraphicSurface(max_width, max_height, offset, item)) { 270 return false; 271 } 272 offset += item->height; 273 } 274 275 return true; 276 } 277 278 bool GraphicMenu::ValidateGraphicSurface(size_t max_width, size_t max_height, int y, 279 const GRSurface* surface) { 280 if (!surface) { 281 fprintf(stderr, "Graphic surface can not be null\n"); 282 return false; 283 } 284 285 if (surface->pixel_bytes != 1 || surface->width != surface->row_bytes) { 286 fprintf(stderr, "Invalid graphic surface, pixel bytes: %zu, width: %zu row_bytes: %zu\n", 287 surface->pixel_bytes, surface->width, surface->row_bytes); 288 return false; 289 } 290 291 if (surface->width > max_width || surface->height > max_height - y) { 292 fprintf(stderr, 293 "Graphic surface doesn't fit into the screen. width: %zu, height: %zu, max_width: %zu," 294 " max_height: %zu, vertical offset: %d\n", 295 surface->width, surface->height, max_width, max_height, y); 296 return false; 297 } 298 299 return true; 300 } 301 302 ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {} 303 304 constexpr int kDefaultMarginHeight = 0; 305 constexpr int kDefaultMarginWidth = 0; 306 constexpr int kDefaultAnimationFps = 30; 307 308 ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) 309 : margin_width_( 310 android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)), 311 margin_height_( 312 android::base::GetIntProperty("ro.recovery.ui.margin_height", kDefaultMarginHeight)), 313 animation_fps_( 314 android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)), 315 density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), 316 current_icon_(NONE), 317 current_frame_(0), 318 intro_done_(false), 319 progressBarType(EMPTY), 320 progressScopeStart(0), 321 progressScopeSize(0), 322 progress(0), 323 pagesIdentical(false), 324 text_cols_(0), 325 text_rows_(0), 326 text_(nullptr), 327 text_col_(0), 328 text_row_(0), 329 show_text(false), 330 show_text_ever(false), 331 scrollable_menu_(scrollable_menu), 332 file_viewer_text_(nullptr), 333 stage(-1), 334 max_stage(-1), 335 locale_(""), 336 rtl_locale_(false) {} 337 338 ScreenRecoveryUI::~ScreenRecoveryUI() { 339 progress_thread_stopped_ = true; 340 if (progress_thread_.joinable()) { 341 progress_thread_.join(); 342 } 343 // No-op if gr_init() (via Init()) was not called or had failed. 344 gr_exit(); 345 } 346 347 const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { 348 if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) { 349 return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get(); 350 } 351 return error_icon_.get(); 352 } 353 354 const GRSurface* ScreenRecoveryUI::GetCurrentText() const { 355 switch (current_icon_) { 356 case ERASING: 357 return erasing_text_.get(); 358 case ERROR: 359 return error_text_.get(); 360 case INSTALLING_UPDATE: 361 return installing_text_.get(); 362 case NO_COMMAND: 363 return no_command_text_.get(); 364 case NONE: 365 abort(); 366 } 367 } 368 369 int ScreenRecoveryUI::PixelsFromDp(int dp) const { 370 return dp * density_; 371 } 372 373 // Here's the intended layout: 374 375 // | portrait large landscape large 376 // ---------+------------------------------------------------- 377 // gap | 378 // icon | (200dp) 379 // gap | 68dp 68dp 56dp 112dp 380 // text | (14sp) 381 // gap | 32dp 32dp 26dp 52dp 382 // progress | (2dp) 383 // gap | 384 385 // Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines 386 // work), so that's the more useful measurement for calling code. We use even top and bottom gaps. 387 388 enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX }; 389 enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX }; 390 static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { 391 { 32, 68 }, // PORTRAIT 392 { 32, 68 }, // PORTRAIT_LARGE 393 { 26, 56 }, // LANDSCAPE 394 { 52, 112 }, // LANDSCAPE_LARGE 395 }; 396 397 int ScreenRecoveryUI::GetAnimationBaseline() const { 398 return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - 399 gr_get_height(loop_frames_[0].get()); 400 } 401 402 int ScreenRecoveryUI::GetTextBaseline() const { 403 return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - 404 gr_get_height(installing_text_.get()); 405 } 406 407 int ScreenRecoveryUI::GetProgressBaseline() const { 408 int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) + 409 gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) + 410 gr_get_height(progress_bar_fill_.get()); 411 int bottom_gap = (ScreenHeight() - elements_sum) / 2; 412 return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get()); 413 } 414 415 // Clear the screen and draw the currently selected background icon (if any). 416 // Should only be called with updateMutex locked. 417 void ScreenRecoveryUI::draw_background_locked() { 418 pagesIdentical = false; 419 gr_color(0, 0, 0, 255); 420 gr_clear(); 421 if (current_icon_ != NONE) { 422 if (max_stage != -1) { 423 int stage_height = gr_get_height(stage_marker_empty_.get()); 424 int stage_width = gr_get_width(stage_marker_empty_.get()); 425 int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2; 426 int y = ScreenHeight() - stage_height - margin_height_; 427 for (int i = 0; i < max_stage; ++i) { 428 const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_; 429 DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y); 430 x += stage_width; 431 } 432 } 433 434 const auto& text_surface = GetCurrentText(); 435 int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2; 436 int text_y = GetTextBaseline(); 437 gr_color(255, 255, 255, 255); 438 DrawTextIcon(text_x, text_y, text_surface); 439 } 440 } 441 442 // Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be 443 // called with updateMutex locked. 444 void ScreenRecoveryUI::draw_foreground_locked() { 445 if (current_icon_ != NONE) { 446 const auto& frame = GetCurrentFrame(); 447 int frame_width = gr_get_width(frame); 448 int frame_height = gr_get_height(frame); 449 int frame_x = (ScreenWidth() - frame_width) / 2; 450 int frame_y = GetAnimationBaseline(); 451 DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); 452 } 453 454 if (progressBarType != EMPTY) { 455 int width = gr_get_width(progress_bar_empty_.get()); 456 int height = gr_get_height(progress_bar_empty_.get()); 457 458 int progress_x = (ScreenWidth() - width) / 2; 459 int progress_y = GetProgressBaseline(); 460 461 // Erase behind the progress bar (in case this was a progress-only update) 462 gr_color(0, 0, 0, 255); 463 DrawFill(progress_x, progress_y, width, height); 464 465 if (progressBarType == DETERMINATE) { 466 float p = progressScopeStart + progress * progressScopeSize; 467 int pos = static_cast<int>(p * width); 468 469 if (rtl_locale_) { 470 // Fill the progress bar from right to left. 471 if (pos > 0) { 472 DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height, 473 progress_x + width - pos, progress_y); 474 } 475 if (pos < width - 1) { 476 DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y); 477 } 478 } else { 479 // Fill the progress bar from left to right. 480 if (pos > 0) { 481 DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y); 482 } 483 if (pos < width - 1) { 484 DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos, 485 progress_y); 486 } 487 } 488 } 489 } 490 } 491 492 void ScreenRecoveryUI::SetColor(UIElement e) const { 493 switch (e) { 494 case UIElement::INFO: 495 gr_color(249, 194, 0, 255); 496 break; 497 case UIElement::HEADER: 498 gr_color(247, 0, 6, 255); 499 break; 500 case UIElement::MENU: 501 case UIElement::MENU_SEL_BG: 502 gr_color(0, 106, 157, 255); 503 break; 504 case UIElement::MENU_SEL_BG_ACTIVE: 505 gr_color(0, 156, 100, 255); 506 break; 507 case UIElement::MENU_SEL_FG: 508 gr_color(255, 255, 255, 255); 509 break; 510 case UIElement::LOG: 511 gr_color(196, 196, 196, 255); 512 break; 513 case UIElement::TEXT_FILL: 514 gr_color(0, 0, 0, 160); 515 break; 516 default: 517 gr_color(255, 255, 255, 255); 518 break; 519 } 520 } 521 522 void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries, 523 size_t sel) { 524 SetLocale(locales_entries[sel]); 525 std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text", 526 "installing_security_text", "no_command_text" }; 527 std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces; 528 for (const auto& name : text_name) { 529 auto text_image = LoadLocalizedBitmap(name); 530 if (!text_image) { 531 Print("Failed to load %s\n", name.c_str()); 532 return; 533 } 534 surfaces.emplace(name, std::move(text_image)); 535 } 536 537 std::lock_guard<std::mutex> lg(updateMutex); 538 gr_color(0, 0, 0, 255); 539 gr_clear(); 540 541 int text_y = margin_height_; 542 int text_x = margin_width_; 543 int line_spacing = gr_sys_font()->char_height; // Put some extra space between images. 544 // Write the header and descriptive texts. 545 SetColor(UIElement::INFO); 546 std::string header = "Show background text image"; 547 text_y += DrawTextLine(text_x, text_y, header, true); 548 std::string locale_selection = android::base::StringPrintf( 549 "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel + 1, locales_entries.size()); 550 // clang-format off 551 std::vector<std::string> instruction = { 552 locale_selection, 553 "Use volume up/down to switch locales and power to exit." 554 }; 555 // clang-format on 556 text_y += DrawWrappedTextLines(text_x, text_y, instruction); 557 558 // Iterate through the text images and display them in order for the current locale. 559 for (const auto& p : surfaces) { 560 text_y += line_spacing; 561 SetColor(UIElement::LOG); 562 text_y += DrawTextLine(text_x, text_y, p.first, false); 563 gr_color(255, 255, 255, 255); 564 gr_texticon(text_x, text_y, p.second.get()); 565 text_y += gr_get_height(p.second.get()); 566 } 567 // Update the whole screen. 568 gr_flip(); 569 } 570 571 void ScreenRecoveryUI::CheckBackgroundTextImages() { 572 // Load a list of locales embedded in one of the resource files. 573 std::vector<std::string> locales_entries = get_locales_in_png("installing_text"); 574 if (locales_entries.empty()) { 575 Print("Failed to load locales from the resource files\n"); 576 return; 577 } 578 std::string saved_locale = locale_; 579 size_t selected = 0; 580 SelectAndShowBackgroundText(locales_entries, selected); 581 582 FlushKeys(); 583 while (true) { 584 int key = WaitKey(); 585 if (key == static_cast<int>(KeyError::INTERRUPTED)) break; 586 if (key == KEY_POWER || key == KEY_ENTER) { 587 break; 588 } else if (key == KEY_UP || key == KEY_VOLUMEUP) { 589 selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1; 590 SelectAndShowBackgroundText(locales_entries, selected); 591 } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) { 592 selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1; 593 SelectAndShowBackgroundText(locales_entries, selected); 594 } 595 } 596 597 SetLocale(saved_locale); 598 } 599 600 int ScreenRecoveryUI::ScreenWidth() const { 601 return gr_fb_width(); 602 } 603 604 int ScreenRecoveryUI::ScreenHeight() const { 605 return gr_fb_height(); 606 } 607 608 void ScreenRecoveryUI::DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx, 609 int dy) const { 610 gr_blit(surface, sx, sy, w, h, dx, dy); 611 } 612 613 int ScreenRecoveryUI::DrawHorizontalRule(int y) const { 614 gr_fill(0, y + 4, ScreenWidth(), y + 6); 615 return 8; 616 } 617 618 void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const { 619 gr_fill(x, y, x + width, y + height); 620 } 621 622 void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const { 623 gr_fill(x, y, w, h); 624 } 625 626 void ScreenRecoveryUI::DrawTextIcon(int x, int y, const GRSurface* surface) const { 627 gr_texticon(x, y, surface); 628 } 629 630 int ScreenRecoveryUI::DrawTextLine(int x, int y, const std::string& line, bool bold) const { 631 gr_text(gr_sys_font(), x, y, line.c_str(), bold); 632 return char_height_ + 4; 633 } 634 635 int ScreenRecoveryUI::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const { 636 int offset = 0; 637 for (const auto& line : lines) { 638 offset += DrawTextLine(x, y + offset, line, false); 639 } 640 return offset; 641 } 642 643 int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, 644 const std::vector<std::string>& lines) const { 645 // Keep symmetrical margins based on the given offset (i.e. x). 646 size_t text_cols = (ScreenWidth() - x * 2) / char_width_; 647 int offset = 0; 648 for (const auto& line : lines) { 649 size_t next_start = 0; 650 while (next_start < line.size()) { 651 std::string sub = line.substr(next_start, text_cols + 1); 652 if (sub.size() <= text_cols) { 653 next_start += sub.size(); 654 } else { 655 // Line too long and must be wrapped to text_cols columns. 656 size_t last_space = sub.find_last_of(" \t\n"); 657 if (last_space == std::string::npos) { 658 // No space found, just draw as much as we can. 659 sub.resize(text_cols); 660 next_start += text_cols; 661 } else { 662 sub.resize(last_space); 663 next_start += last_space + 1; 664 } 665 } 666 offset += DrawTextLine(x, y + offset, sub, false); 667 } 668 } 669 return offset; 670 } 671 672 void ScreenRecoveryUI::SetTitle(const std::vector<std::string>& lines) { 673 title_lines_ = lines; 674 } 675 676 // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex 677 // locked. 678 void ScreenRecoveryUI::draw_screen_locked() { 679 if (!show_text) { 680 draw_background_locked(); 681 draw_foreground_locked(); 682 return; 683 } 684 685 gr_color(0, 0, 0, 255); 686 gr_clear(); 687 688 // clang-format off 689 static std::vector<std::string> REGULAR_HELP{ 690 "Use volume up/down and power.", 691 }; 692 static std::vector<std::string> LONG_PRESS_HELP{ 693 "Any button cycles highlight.", 694 "Long-press activates.", 695 }; 696 // clang-format on 697 draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); 698 } 699 700 // Draws the menu and text buffer on the screen. Should only be called with updateMutex locked. 701 void ScreenRecoveryUI::draw_menu_and_text_buffer_locked( 702 const std::vector<std::string>& help_message) { 703 int y = margin_height_; 704 705 if (fastbootd_logo_ && fastbootd_logo_enabled_) { 706 // Try to get this centered on screen. 707 auto width = gr_get_width(fastbootd_logo_.get()); 708 auto height = gr_get_height(fastbootd_logo_.get()); 709 auto centered_x = ScreenWidth() / 2 - width / 2; 710 DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y); 711 y += height; 712 } 713 714 if (menu_) { 715 int x = margin_width_ + kMenuIndent; 716 717 SetColor(UIElement::INFO); 718 719 for (size_t i = 0; i < title_lines_.size(); i++) { 720 y += DrawTextLine(x, y, title_lines_[i], i == 0); 721 } 722 723 y += DrawTextLines(x, y, help_message); 724 725 y += menu_->DrawHeader(x, y); 726 y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress()); 727 } 728 729 // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or 730 // we've displayed the entire text buffer. 731 SetColor(UIElement::LOG); 732 int row = text_row_; 733 size_t count = 0; 734 for (int ty = ScreenHeight() - margin_height_ - char_height_; ty >= y && count < text_rows_; 735 ty -= char_height_, ++count) { 736 DrawTextLine(margin_width_, ty, text_[row], false); 737 --row; 738 if (row < 0) row = text_rows_ - 1; 739 } 740 } 741 742 // Redraw everything on the screen and flip the screen (make it visible). 743 // Should only be called with updateMutex locked. 744 void ScreenRecoveryUI::update_screen_locked() { 745 draw_screen_locked(); 746 gr_flip(); 747 } 748 749 // Updates only the progress bar, if possible, otherwise redraws the screen. 750 // Should only be called with updateMutex locked. 751 void ScreenRecoveryUI::update_progress_locked() { 752 if (show_text || !pagesIdentical) { 753 draw_screen_locked(); // Must redraw the whole screen 754 pagesIdentical = true; 755 } else { 756 draw_foreground_locked(); // Draw only the progress bar and overlays 757 } 758 gr_flip(); 759 } 760 761 void ScreenRecoveryUI::ProgressThreadLoop() { 762 double interval = 1.0 / animation_fps_; 763 while (!progress_thread_stopped_) { 764 double start = now(); 765 bool redraw = false; 766 { 767 std::lock_guard<std::mutex> lg(updateMutex); 768 769 // update the installation animation, if active 770 // skip this if we have a text overlay (too expensive to update) 771 if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) { 772 if (!intro_done_) { 773 if (current_frame_ == intro_frames_.size() - 1) { 774 intro_done_ = true; 775 current_frame_ = 0; 776 } else { 777 ++current_frame_; 778 } 779 } else { 780 current_frame_ = (current_frame_ + 1) % loop_frames_.size(); 781 } 782 783 redraw = true; 784 } 785 786 // move the progress bar forward on timed intervals, if configured 787 int duration = progressScopeDuration; 788 if (progressBarType == DETERMINATE && duration > 0) { 789 double elapsed = now() - progressScopeTime; 790 float p = 1.0 * elapsed / duration; 791 if (p > 1.0) p = 1.0; 792 if (p > progress) { 793 progress = p; 794 redraw = true; 795 } 796 } 797 798 if (redraw) update_progress_locked(); 799 } 800 801 double end = now(); 802 // minimum of 20ms delay between frames 803 double delay = interval - (end - start); 804 if (delay < 0.02) delay = 0.02; 805 usleep(static_cast<useconds_t>(delay * 1000000)); 806 } 807 } 808 809 std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filename) { 810 GRSurface* surface; 811 if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) { 812 LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; 813 return nullptr; 814 } 815 return std::unique_ptr<GRSurface>(surface); 816 } 817 818 std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) { 819 GRSurface* surface; 820 if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); 821 result < 0) { 822 LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; 823 return nullptr; 824 } 825 return std::unique_ptr<GRSurface>(surface); 826 } 827 828 static char** Alloc2d(size_t rows, size_t cols) { 829 char** result = new char*[rows]; 830 for (size_t i = 0; i < rows; ++i) { 831 result[i] = new char[cols]; 832 memset(result[i], 0, cols); 833 } 834 return result; 835 } 836 837 // Choose the right background string to display during update. 838 void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { 839 if (security_update) { 840 installing_text_ = LoadLocalizedBitmap("installing_security_text"); 841 } else { 842 installing_text_ = LoadLocalizedBitmap("installing_text"); 843 } 844 Redraw(); 845 } 846 847 bool ScreenRecoveryUI::InitTextParams() { 848 // gr_init() would return successfully on font initialization failure. 849 if (gr_sys_font() == nullptr) { 850 return false; 851 } 852 gr_font_size(gr_sys_font(), &char_width_, &char_height_); 853 text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_; 854 text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_; 855 return true; 856 } 857 858 bool ScreenRecoveryUI::LoadWipeDataMenuText() { 859 // Ignores the errors since the member variables will stay as nullptr. 860 cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text"); 861 factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text"); 862 try_again_text_ = LoadLocalizedBitmap("try_again_text"); 863 wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text"); 864 wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text"); 865 return true; 866 } 867 868 bool ScreenRecoveryUI::Init(const std::string& locale) { 869 RecoveryUI::Init(locale); 870 871 if (gr_init() == -1) { 872 return false; 873 } 874 875 if (!InitTextParams()) { 876 return false; 877 } 878 879 // Are we portrait or landscape? 880 layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; 881 // Are we the large variant of our base layout? 882 if (gr_fb_height() > PixelsFromDp(800)) ++layout_; 883 884 text_ = Alloc2d(text_rows_, text_cols_ + 1); 885 file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); 886 887 text_col_ = text_row_ = 0; 888 889 // Set up the locale info. 890 SetLocale(locale); 891 892 error_icon_ = LoadBitmap("icon_error"); 893 894 progress_bar_empty_ = LoadBitmap("progress_empty"); 895 progress_bar_fill_ = LoadBitmap("progress_fill"); 896 stage_marker_empty_ = LoadBitmap("stage_empty"); 897 stage_marker_fill_ = LoadBitmap("stage_fill"); 898 899 erasing_text_ = LoadLocalizedBitmap("erasing_text"); 900 no_command_text_ = LoadLocalizedBitmap("no_command_text"); 901 error_text_ = LoadLocalizedBitmap("error_text"); 902 903 if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { 904 fastbootd_logo_ = LoadBitmap("fastbootd"); 905 } 906 907 // Background text for "installing_update" could be "installing update" or 908 // "installing security update". It will be set after Init() according to the commands in BCB. 909 installing_text_.reset(); 910 911 LoadWipeDataMenuText(); 912 913 LoadAnimation(); 914 915 // Keep the progress bar updated, even when the process is otherwise busy. 916 progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this); 917 918 return true; 919 } 920 921 std::string ScreenRecoveryUI::GetLocale() const { 922 return locale_; 923 } 924 925 void ScreenRecoveryUI::LoadAnimation() { 926 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(Paths::Get().resource_dir().c_str()), 927 closedir); 928 dirent* de; 929 std::vector<std::string> intro_frame_names; 930 std::vector<std::string> loop_frame_names; 931 932 while ((de = readdir(dir.get())) != nullptr) { 933 int value, num_chars; 934 if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { 935 intro_frame_names.emplace_back(de->d_name, num_chars); 936 } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { 937 loop_frame_names.emplace_back(de->d_name, num_chars); 938 } 939 } 940 941 size_t intro_frames = intro_frame_names.size(); 942 size_t loop_frames = loop_frame_names.size(); 943 944 // It's okay to not have an intro. 945 if (intro_frames == 0) intro_done_ = true; 946 // But you must have an animation. 947 if (loop_frames == 0) abort(); 948 949 std::sort(intro_frame_names.begin(), intro_frame_names.end()); 950 std::sort(loop_frame_names.begin(), loop_frame_names.end()); 951 952 intro_frames_.clear(); 953 intro_frames_.reserve(intro_frames); 954 for (const auto& frame_name : intro_frame_names) { 955 intro_frames_.emplace_back(LoadBitmap(frame_name)); 956 } 957 958 loop_frames_.clear(); 959 loop_frames_.reserve(loop_frames); 960 for (const auto& frame_name : loop_frame_names) { 961 loop_frames_.emplace_back(LoadBitmap(frame_name)); 962 } 963 } 964 965 void ScreenRecoveryUI::SetBackground(Icon icon) { 966 std::lock_guard<std::mutex> lg(updateMutex); 967 968 current_icon_ = icon; 969 update_screen_locked(); 970 } 971 972 void ScreenRecoveryUI::SetProgressType(ProgressType type) { 973 std::lock_guard<std::mutex> lg(updateMutex); 974 if (progressBarType != type) { 975 progressBarType = type; 976 } 977 progressScopeStart = 0; 978 progressScopeSize = 0; 979 progress = 0; 980 update_progress_locked(); 981 } 982 983 void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { 984 std::lock_guard<std::mutex> lg(updateMutex); 985 progressBarType = DETERMINATE; 986 progressScopeStart += progressScopeSize; 987 progressScopeSize = portion; 988 progressScopeTime = now(); 989 progressScopeDuration = seconds; 990 progress = 0; 991 update_progress_locked(); 992 } 993 994 void ScreenRecoveryUI::SetProgress(float fraction) { 995 std::lock_guard<std::mutex> lg(updateMutex); 996 if (fraction < 0.0) fraction = 0.0; 997 if (fraction > 1.0) fraction = 1.0; 998 if (progressBarType == DETERMINATE && fraction > progress) { 999 // Skip updates that aren't visibly different. 1000 int width = gr_get_width(progress_bar_empty_.get()); 1001 float scale = width * progressScopeSize; 1002 if ((int)(progress * scale) != (int)(fraction * scale)) { 1003 progress = fraction; 1004 update_progress_locked(); 1005 } 1006 } 1007 } 1008 1009 void ScreenRecoveryUI::SetStage(int current, int max) { 1010 std::lock_guard<std::mutex> lg(updateMutex); 1011 stage = current; 1012 max_stage = max; 1013 } 1014 1015 void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { 1016 std::string str; 1017 android::base::StringAppendV(&str, fmt, ap); 1018 1019 if (copy_to_stdout) { 1020 fputs(str.c_str(), stdout); 1021 } 1022 1023 std::lock_guard<std::mutex> lg(updateMutex); 1024 if (text_rows_ > 0 && text_cols_ > 0) { 1025 for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { 1026 if (*ptr == '\n' || text_col_ >= text_cols_) { 1027 text_[text_row_][text_col_] = '\0'; 1028 text_col_ = 0; 1029 text_row_ = (text_row_ + 1) % text_rows_; 1030 } 1031 if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; 1032 } 1033 text_[text_row_][text_col_] = '\0'; 1034 update_screen_locked(); 1035 } 1036 } 1037 1038 void ScreenRecoveryUI::Print(const char* fmt, ...) { 1039 va_list ap; 1040 va_start(ap, fmt); 1041 PrintV(fmt, true, ap); 1042 va_end(ap); 1043 } 1044 1045 void ScreenRecoveryUI::PrintOnScreenOnly(const char* fmt, ...) { 1046 va_list ap; 1047 va_start(ap, fmt); 1048 PrintV(fmt, false, ap); 1049 va_end(ap); 1050 } 1051 1052 void ScreenRecoveryUI::PutChar(char ch) { 1053 std::lock_guard<std::mutex> lg(updateMutex); 1054 if (ch != '\n') text_[text_row_][text_col_++] = ch; 1055 if (ch == '\n' || text_col_ >= text_cols_) { 1056 text_col_ = 0; 1057 ++text_row_; 1058 } 1059 } 1060 1061 void ScreenRecoveryUI::ClearText() { 1062 std::lock_guard<std::mutex> lg(updateMutex); 1063 text_col_ = 0; 1064 text_row_ = 0; 1065 for (size_t i = 0; i < text_rows_; ++i) { 1066 memset(text_[i], 0, text_cols_ + 1); 1067 } 1068 } 1069 1070 void ScreenRecoveryUI::ShowFile(FILE* fp) { 1071 std::vector<off_t> offsets; 1072 offsets.push_back(ftello(fp)); 1073 ClearText(); 1074 1075 struct stat sb; 1076 fstat(fileno(fp), &sb); 1077 1078 bool show_prompt = false; 1079 while (true) { 1080 if (show_prompt) { 1081 PrintOnScreenOnly("--(%d%% of %d bytes)--", 1082 static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), 1083 static_cast<int>(sb.st_size)); 1084 Redraw(); 1085 while (show_prompt) { 1086 show_prompt = false; 1087 int key = WaitKey(); 1088 if (key == static_cast<int>(KeyError::INTERRUPTED)) return; 1089 if (key == KEY_POWER || key == KEY_ENTER) { 1090 return; 1091 } else if (key == KEY_UP || key == KEY_VOLUMEUP) { 1092 if (offsets.size() <= 1) { 1093 show_prompt = true; 1094 } else { 1095 offsets.pop_back(); 1096 fseek(fp, offsets.back(), SEEK_SET); 1097 } 1098 } else { 1099 if (feof(fp)) { 1100 return; 1101 } 1102 offsets.push_back(ftello(fp)); 1103 } 1104 } 1105 ClearText(); 1106 } 1107 1108 int ch = getc(fp); 1109 if (ch == EOF) { 1110 while (text_row_ < text_rows_ - 1) PutChar('\n'); 1111 show_prompt = true; 1112 } else { 1113 PutChar(ch); 1114 if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { 1115 show_prompt = true; 1116 } 1117 } 1118 } 1119 } 1120 1121 void ScreenRecoveryUI::ShowFile(const std::string& filename) { 1122 std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "re"), fclose); 1123 if (!fp) { 1124 Print(" Unable to open %s: %s\n", filename.c_str(), strerror(errno)); 1125 return; 1126 } 1127 1128 char** old_text = text_; 1129 size_t old_text_col = text_col_; 1130 size_t old_text_row = text_row_; 1131 1132 // Swap in the alternate screen and clear it. 1133 text_ = file_viewer_text_; 1134 ClearText(); 1135 1136 ShowFile(fp.get()); 1137 1138 text_ = old_text; 1139 text_col_ = old_text_col; 1140 text_row_ = old_text_row; 1141 } 1142 1143 std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu( 1144 const GRSurface* graphic_header, const std::vector<const GRSurface*>& graphic_items, 1145 const std::vector<std::string>& text_headers, const std::vector<std::string>& text_items, 1146 size_t initial_selection) const { 1147 // horizontal unusable area: margin width + menu indent 1148 size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent; 1149 // vertical unusable area: margin height + title lines + helper message + high light bar. 1150 // It is safe to reserve more space. 1151 size_t max_height = ScreenHeight() - margin_height_ - char_height_ * (title_lines_.size() + 3); 1152 if (GraphicMenu::Validate(max_width, max_height, graphic_header, graphic_items)) { 1153 return std::make_unique<GraphicMenu>(graphic_header, graphic_items, initial_selection, *this); 1154 } 1155 1156 fprintf(stderr, "Failed to initialize graphic menu, falling back to use the text menu.\n"); 1157 1158 return CreateMenu(text_headers, text_items, initial_selection); 1159 } 1160 1161 std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(const std::vector<std::string>& text_headers, 1162 const std::vector<std::string>& text_items, 1163 size_t initial_selection) const { 1164 if (text_rows_ > 0 && text_cols_ > 1) { 1165 return std::make_unique<TextMenu>(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers, 1166 text_items, initial_selection, char_height_, *this); 1167 } 1168 1169 fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_, 1170 text_cols_); 1171 return nullptr; 1172 } 1173 1174 int ScreenRecoveryUI::SelectMenu(int sel) { 1175 std::lock_guard<std::mutex> lg(updateMutex); 1176 if (menu_) { 1177 int old_sel = menu_->selection(); 1178 sel = menu_->Select(sel); 1179 1180 if (sel != old_sel) { 1181 update_screen_locked(); 1182 } 1183 } 1184 return sel; 1185 } 1186 1187 size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only, 1188 const std::function<int(int, bool)>& key_handler) { 1189 // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. 1190 FlushKeys(); 1191 1192 // If there is a key interrupt in progress, return KeyError::INTERRUPTED without starting the 1193 // menu. 1194 if (IsKeyInterrupted()) return static_cast<size_t>(KeyError::INTERRUPTED); 1195 1196 CHECK(menu != nullptr); 1197 1198 // Starts and displays the menu 1199 menu_ = std::move(menu); 1200 Redraw(); 1201 1202 int selected = menu_->selection(); 1203 int chosen_item = -1; 1204 while (chosen_item < 0) { 1205 int key = WaitKey(); 1206 if (key == static_cast<int>(KeyError::INTERRUPTED)) { // WaitKey() was interrupted. 1207 return static_cast<size_t>(KeyError::INTERRUPTED); 1208 } 1209 if (key == static_cast<int>(KeyError::TIMED_OUT)) { // WaitKey() timed out. 1210 if (WasTextEverVisible()) { 1211 continue; 1212 } else { 1213 LOG(INFO) << "Timed out waiting for key input; rebooting."; 1214 menu_.reset(); 1215 Redraw(); 1216 return static_cast<size_t>(KeyError::TIMED_OUT); 1217 } 1218 } 1219 1220 bool visible = IsTextVisible(); 1221 int action = key_handler(key, visible); 1222 if (action < 0) { 1223 switch (action) { 1224 case Device::kHighlightUp: 1225 selected = SelectMenu(--selected); 1226 break; 1227 case Device::kHighlightDown: 1228 selected = SelectMenu(++selected); 1229 break; 1230 case Device::kInvokeItem: 1231 chosen_item = selected; 1232 break; 1233 case Device::kNoAction: 1234 break; 1235 } 1236 } else if (!menu_only) { 1237 chosen_item = action; 1238 } 1239 } 1240 1241 menu_.reset(); 1242 Redraw(); 1243 1244 return chosen_item; 1245 } 1246 1247 size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers, 1248 const std::vector<std::string>& items, size_t initial_selection, 1249 bool menu_only, 1250 const std::function<int(int, bool)>& key_handler) { 1251 auto menu = CreateMenu(headers, items, initial_selection); 1252 if (menu == nullptr) { 1253 return initial_selection; 1254 } 1255 1256 return ShowMenu(CreateMenu(headers, items, initial_selection), menu_only, key_handler); 1257 } 1258 1259 size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers, 1260 const std::vector<std::string>& backup_items, 1261 const std::function<int(int, bool)>& key_handler) { 1262 auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(), 1263 { try_again_text_.get(), factory_data_reset_text_.get() }, 1264 backup_headers, backup_items, 0); 1265 if (wipe_data_menu == nullptr) { 1266 return 0; 1267 } 1268 1269 return ShowMenu(std::move(wipe_data_menu), true, key_handler); 1270 } 1271 1272 size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu( 1273 const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items, 1274 const std::function<int(int, bool)>& key_handler) { 1275 auto confirmation_menu = 1276 CreateMenu(wipe_data_confirmation_text_.get(), 1277 { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers, 1278 backup_items, 0); 1279 if (confirmation_menu == nullptr) { 1280 return 0; 1281 } 1282 1283 return ShowMenu(std::move(confirmation_menu), true, key_handler); 1284 } 1285 1286 bool ScreenRecoveryUI::IsTextVisible() { 1287 std::lock_guard<std::mutex> lg(updateMutex); 1288 int visible = show_text; 1289 return visible; 1290 } 1291 1292 bool ScreenRecoveryUI::WasTextEverVisible() { 1293 std::lock_guard<std::mutex> lg(updateMutex); 1294 int ever_visible = show_text_ever; 1295 return ever_visible; 1296 } 1297 1298 void ScreenRecoveryUI::ShowText(bool visible) { 1299 std::lock_guard<std::mutex> lg(updateMutex); 1300 show_text = visible; 1301 if (show_text) show_text_ever = true; 1302 update_screen_locked(); 1303 } 1304 1305 void ScreenRecoveryUI::Redraw() { 1306 std::lock_guard<std::mutex> lg(updateMutex); 1307 update_screen_locked(); 1308 } 1309 1310 void ScreenRecoveryUI::KeyLongPress(int) { 1311 // Redraw so that if we're in the menu, the highlight 1312 // will change color to indicate a successful long press. 1313 Redraw(); 1314 } 1315 1316 void ScreenRecoveryUI::SetLocale(const std::string& new_locale) { 1317 locale_ = new_locale; 1318 rtl_locale_ = false; 1319 1320 if (!new_locale.empty()) { 1321 size_t separator = new_locale.find('-'); 1322 // lang has the language prefix prior to the separator, or full string if none exists. 1323 std::string lang = new_locale.substr(0, separator); 1324 1325 // A bit cheesy: keep an explicit list of supported RTL languages. 1326 if (lang == "ar" || // Arabic 1327 lang == "fa" || // Persian (Farsi) 1328 lang == "he" || // Hebrew (new language code) 1329 lang == "iw" || // Hebrew (old language code) 1330 lang == "ur") { // Urdu 1331 rtl_locale_ = true; 1332 } 1333 } 1334 } 1335