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