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 "screen_ui.h" 18 19 #include <dirent.h> 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <linux/input.h> 23 #include <pthread.h> 24 #include <stdarg.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <sys/stat.h> 29 #include <sys/time.h> 30 #include <sys/types.h> 31 #include <time.h> 32 #include <unistd.h> 33 34 #include <memory> 35 #include <string> 36 #include <unordered_map> 37 #include <vector> 38 39 #include <android-base/logging.h> 40 #include <android-base/properties.h> 41 #include <android-base/stringprintf.h> 42 #include <android-base/strings.h> 43 #include <minui/minui.h> 44 45 #include "common.h" 46 #include "device.h" 47 #include "ui.h" 48 49 // Return the current time as a double (including fractions of a second). 50 static double now() { 51 struct timeval tv; 52 gettimeofday(&tv, nullptr); 53 return tv.tv_sec + tv.tv_usec / 1000000.0; 54 } 55 56 ScreenRecoveryUI::ScreenRecoveryUI() 57 : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH), 58 kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT), 59 kAnimationFps(RECOVERY_UI_ANIMATION_FPS), 60 kDensity(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), 61 currentIcon(NONE), 62 progressBarType(EMPTY), 63 progressScopeStart(0), 64 progressScopeSize(0), 65 progress(0), 66 pagesIdentical(false), 67 text_cols_(0), 68 text_rows_(0), 69 text_(nullptr), 70 text_col_(0), 71 text_row_(0), 72 show_text(false), 73 show_text_ever(false), 74 menu_headers_(nullptr), 75 show_menu(false), 76 menu_items(0), 77 menu_sel(0), 78 file_viewer_text_(nullptr), 79 intro_frames(0), 80 loop_frames(0), 81 current_frame(0), 82 intro_done(false), 83 stage(-1), 84 max_stage(-1), 85 locale_(""), 86 rtl_locale_(false), 87 updateMutex(PTHREAD_MUTEX_INITIALIZER) {} 88 89 GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { 90 if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { 91 return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; 92 } 93 return error_icon; 94 } 95 96 GRSurface* ScreenRecoveryUI::GetCurrentText() const { 97 switch (currentIcon) { 98 case ERASING: 99 return erasing_text; 100 case ERROR: 101 return error_text; 102 case INSTALLING_UPDATE: 103 return installing_text; 104 case NO_COMMAND: 105 return no_command_text; 106 case NONE: 107 abort(); 108 } 109 } 110 111 int ScreenRecoveryUI::PixelsFromDp(int dp) const { 112 return dp * kDensity; 113 } 114 115 // Here's the intended layout: 116 117 // | portrait large landscape large 118 // ---------+------------------------------------------------- 119 // gap | 120 // icon | (200dp) 121 // gap | 68dp 68dp 56dp 112dp 122 // text | (14sp) 123 // gap | 32dp 32dp 26dp 52dp 124 // progress | (2dp) 125 // gap | 126 127 // Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines 128 // work), so that's the more useful measurement for calling code. We use even top and bottom gaps. 129 130 enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX }; 131 enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX }; 132 static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { 133 { 32, 68, }, // PORTRAIT 134 { 32, 68, }, // PORTRAIT_LARGE 135 { 26, 56, }, // LANDSCAPE 136 { 52, 112, }, // LANDSCAPE_LARGE 137 }; 138 139 int ScreenRecoveryUI::GetAnimationBaseline() const { 140 return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]); 141 } 142 143 int ScreenRecoveryUI::GetTextBaseline() const { 144 return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - 145 gr_get_height(installing_text); 146 } 147 148 int ScreenRecoveryUI::GetProgressBaseline() const { 149 int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) + 150 gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) + 151 gr_get_height(progressBarFill); 152 int bottom_gap = (ScreenHeight() - elements_sum) / 2; 153 return ScreenHeight() - bottom_gap - gr_get_height(progressBarFill); 154 } 155 156 // Clear the screen and draw the currently selected background icon (if any). 157 // Should only be called with updateMutex locked. 158 void ScreenRecoveryUI::draw_background_locked() { 159 pagesIdentical = false; 160 gr_color(0, 0, 0, 255); 161 gr_clear(); 162 if (currentIcon != NONE) { 163 if (max_stage != -1) { 164 int stage_height = gr_get_height(stageMarkerEmpty); 165 int stage_width = gr_get_width(stageMarkerEmpty); 166 int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; 167 int y = ScreenHeight() - stage_height - kMarginHeight; 168 for (int i = 0; i < max_stage; ++i) { 169 GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; 170 DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y); 171 x += stage_width; 172 } 173 } 174 175 GRSurface* text_surface = GetCurrentText(); 176 int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2; 177 int text_y = GetTextBaseline(); 178 gr_color(255, 255, 255, 255); 179 DrawTextIcon(text_x, text_y, text_surface); 180 } 181 } 182 183 // Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be 184 // called with updateMutex locked. 185 void ScreenRecoveryUI::draw_foreground_locked() { 186 if (currentIcon != NONE) { 187 GRSurface* frame = GetCurrentFrame(); 188 int frame_width = gr_get_width(frame); 189 int frame_height = gr_get_height(frame); 190 int frame_x = (ScreenWidth() - frame_width) / 2; 191 int frame_y = GetAnimationBaseline(); 192 DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); 193 } 194 195 if (progressBarType != EMPTY) { 196 int width = gr_get_width(progressBarEmpty); 197 int height = gr_get_height(progressBarEmpty); 198 199 int progress_x = (ScreenWidth() - width) / 2; 200 int progress_y = GetProgressBaseline(); 201 202 // Erase behind the progress bar (in case this was a progress-only update) 203 gr_color(0, 0, 0, 255); 204 DrawFill(progress_x, progress_y, width, height); 205 206 if (progressBarType == DETERMINATE) { 207 float p = progressScopeStart + progress * progressScopeSize; 208 int pos = static_cast<int>(p * width); 209 210 if (rtl_locale_) { 211 // Fill the progress bar from right to left. 212 if (pos > 0) { 213 DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos, 214 progress_y); 215 } 216 if (pos < width - 1) { 217 DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y); 218 } 219 } else { 220 // Fill the progress bar from left to right. 221 if (pos > 0) { 222 DrawSurface(progressBarFill, 0, 0, pos, height, progress_x, progress_y); 223 } 224 if (pos < width - 1) { 225 DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y); 226 } 227 } 228 } 229 } 230 } 231 232 void ScreenRecoveryUI::SetColor(UIElement e) const { 233 switch (e) { 234 case INFO: 235 gr_color(249, 194, 0, 255); 236 break; 237 case HEADER: 238 gr_color(247, 0, 6, 255); 239 break; 240 case MENU: 241 case MENU_SEL_BG: 242 gr_color(0, 106, 157, 255); 243 break; 244 case MENU_SEL_BG_ACTIVE: 245 gr_color(0, 156, 100, 255); 246 break; 247 case MENU_SEL_FG: 248 gr_color(255, 255, 255, 255); 249 break; 250 case LOG: 251 gr_color(196, 196, 196, 255); 252 break; 253 case TEXT_FILL: 254 gr_color(0, 0, 0, 160); 255 break; 256 default: 257 gr_color(255, 255, 255, 255); 258 break; 259 } 260 } 261 262 void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries, 263 size_t sel) { 264 SetLocale(locales_entries[sel]); 265 std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text", 266 "installing_security_text", "no_command_text" }; 267 std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces; 268 for (const auto& name : text_name) { 269 GRSurface* text_image = nullptr; 270 LoadLocalizedBitmap(name.c_str(), &text_image); 271 if (!text_image) { 272 Print("Failed to load %s\n", name.c_str()); 273 return; 274 } 275 surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free)); 276 } 277 278 pthread_mutex_lock(&updateMutex); 279 gr_color(0, 0, 0, 255); 280 gr_clear(); 281 282 int text_y = kMarginHeight; 283 int text_x = kMarginWidth; 284 int line_spacing = gr_sys_font()->char_height; // Put some extra space between images. 285 // Write the header and descriptive texts. 286 SetColor(INFO); 287 std::string header = "Show background text image"; 288 text_y += DrawTextLine(text_x, text_y, header.c_str(), true); 289 std::string locale_selection = android::base::StringPrintf( 290 "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel, locales_entries.size()); 291 const char* instruction[] = { locale_selection.c_str(), 292 "Use volume up/down to switch locales and power to exit.", 293 nullptr }; 294 text_y += DrawWrappedTextLines(text_x, text_y, instruction); 295 296 // Iterate through the text images and display them in order for the current locale. 297 for (const auto& p : surfaces) { 298 text_y += line_spacing; 299 SetColor(LOG); 300 text_y += DrawTextLine(text_x, text_y, p.first.c_str(), false); 301 gr_color(255, 255, 255, 255); 302 gr_texticon(text_x, text_y, p.second.get()); 303 text_y += gr_get_height(p.second.get()); 304 } 305 // Update the whole screen. 306 gr_flip(); 307 pthread_mutex_unlock(&updateMutex); 308 } 309 310 void ScreenRecoveryUI::CheckBackgroundTextImages(const std::string& saved_locale) { 311 // Load a list of locales embedded in one of the resource files. 312 std::vector<std::string> locales_entries = get_locales_in_png("installing_text"); 313 if (locales_entries.empty()) { 314 Print("Failed to load locales from the resource files\n"); 315 return; 316 } 317 size_t selected = 0; 318 SelectAndShowBackgroundText(locales_entries, selected); 319 320 FlushKeys(); 321 while (true) { 322 int key = WaitKey(); 323 if (key == KEY_POWER || key == KEY_ENTER) { 324 break; 325 } else if (key == KEY_UP || key == KEY_VOLUMEUP) { 326 selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1; 327 SelectAndShowBackgroundText(locales_entries, selected); 328 } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) { 329 selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1; 330 SelectAndShowBackgroundText(locales_entries, selected); 331 } 332 } 333 334 SetLocale(saved_locale); 335 } 336 337 int ScreenRecoveryUI::ScreenWidth() const { 338 return gr_fb_width(); 339 } 340 341 int ScreenRecoveryUI::ScreenHeight() const { 342 return gr_fb_height(); 343 } 344 345 void ScreenRecoveryUI::DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, 346 int dy) const { 347 gr_blit(surface, sx, sy, w, h, dx, dy); 348 } 349 350 int ScreenRecoveryUI::DrawHorizontalRule(int y) const { 351 gr_fill(0, y + 4, ScreenWidth(), y + 6); 352 return 8; 353 } 354 355 void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const { 356 gr_fill(x, y, x + width, y + height); 357 } 358 359 void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const { 360 gr_fill(x, y, w, h); 361 } 362 363 void ScreenRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const { 364 gr_texticon(x, y, surface); 365 } 366 367 int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { 368 gr_text(gr_sys_font(), x, y, line, bold); 369 return char_height_ + 4; 370 } 371 372 int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) const { 373 int offset = 0; 374 for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { 375 offset += DrawTextLine(x, y + offset, lines[i], false); 376 } 377 return offset; 378 } 379 380 int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const { 381 int offset = 0; 382 for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { 383 // The line will be wrapped if it exceeds text_cols_. 384 std::string line(lines[i]); 385 size_t next_start = 0; 386 while (next_start < line.size()) { 387 std::string sub = line.substr(next_start, text_cols_ + 1); 388 if (sub.size() <= text_cols_) { 389 next_start += sub.size(); 390 } else { 391 // Line too long and must be wrapped to text_cols_ columns. 392 size_t last_space = sub.find_last_of(" \t\n"); 393 if (last_space == std::string::npos) { 394 // No space found, just draw as much as we can 395 sub.resize(text_cols_); 396 next_start += text_cols_; 397 } else { 398 sub.resize(last_space); 399 next_start += last_space + 1; 400 } 401 } 402 offset += DrawTextLine(x, y + offset, sub.c_str(), false); 403 } 404 } 405 return offset; 406 } 407 408 static const char* REGULAR_HELP[] = { 409 "Use volume up/down and power.", 410 NULL 411 }; 412 413 static const char* LONG_PRESS_HELP[] = { 414 "Any button cycles highlight.", 415 "Long-press activates.", 416 NULL 417 }; 418 419 // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex 420 // locked. 421 void ScreenRecoveryUI::draw_screen_locked() { 422 if (!show_text) { 423 draw_background_locked(); 424 draw_foreground_locked(); 425 return; 426 } 427 428 gr_color(0, 0, 0, 255); 429 gr_clear(); 430 431 int y = kMarginHeight; 432 if (show_menu) { 433 static constexpr int kMenuIndent = 4; 434 int x = kMarginWidth + kMenuIndent; 435 436 SetColor(INFO); 437 y += DrawTextLine(x, y, "Android Recovery", true); 438 std::string recovery_fingerprint = 439 android::base::GetProperty("ro.bootimage.build.fingerprint", ""); 440 for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) { 441 y += DrawTextLine(x, y, chunk.c_str(), false); 442 } 443 y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); 444 445 SetColor(HEADER); 446 // Ignore kMenuIndent, which is not taken into account by text_cols_. 447 y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_); 448 449 SetColor(MENU); 450 y += DrawHorizontalRule(y) + 4; 451 for (int i = 0; i < menu_items; ++i) { 452 if (i == menu_sel) { 453 // Draw the highlight bar. 454 SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); 455 DrawHighlightBar(0, y - 2, ScreenWidth(), char_height_ + 4); 456 // Bold white text for the selected item. 457 SetColor(MENU_SEL_FG); 458 y += DrawTextLine(x, y, menu_[i].c_str(), true); 459 SetColor(MENU); 460 } else { 461 y += DrawTextLine(x, y, menu_[i].c_str(), false); 462 } 463 } 464 y += DrawHorizontalRule(y); 465 } 466 467 // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or 468 // we've displayed the entire text buffer. 469 SetColor(LOG); 470 int row = text_row_; 471 size_t count = 0; 472 for (int ty = ScreenHeight() - kMarginHeight - char_height_; ty >= y && count < text_rows_; 473 ty -= char_height_, ++count) { 474 DrawTextLine(kMarginWidth, ty, text_[row], false); 475 --row; 476 if (row < 0) row = text_rows_ - 1; 477 } 478 } 479 480 // Redraw everything on the screen and flip the screen (make it visible). 481 // Should only be called with updateMutex locked. 482 void ScreenRecoveryUI::update_screen_locked() { 483 draw_screen_locked(); 484 gr_flip(); 485 } 486 487 // Updates only the progress bar, if possible, otherwise redraws the screen. 488 // Should only be called with updateMutex locked. 489 void ScreenRecoveryUI::update_progress_locked() { 490 if (show_text || !pagesIdentical) { 491 draw_screen_locked(); // Must redraw the whole screen 492 pagesIdentical = true; 493 } else { 494 draw_foreground_locked(); // Draw only the progress bar and overlays 495 } 496 gr_flip(); 497 } 498 499 // Keeps the progress bar updated, even when the process is otherwise busy. 500 void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { 501 reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); 502 return nullptr; 503 } 504 505 void ScreenRecoveryUI::ProgressThreadLoop() { 506 double interval = 1.0 / kAnimationFps; 507 while (true) { 508 double start = now(); 509 pthread_mutex_lock(&updateMutex); 510 511 bool redraw = false; 512 513 // update the installation animation, if active 514 // skip this if we have a text overlay (too expensive to update) 515 if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { 516 if (!intro_done) { 517 if (current_frame == intro_frames - 1) { 518 intro_done = true; 519 current_frame = 0; 520 } else { 521 ++current_frame; 522 } 523 } else { 524 current_frame = (current_frame + 1) % loop_frames; 525 } 526 527 redraw = true; 528 } 529 530 // move the progress bar forward on timed intervals, if configured 531 int duration = progressScopeDuration; 532 if (progressBarType == DETERMINATE && duration > 0) { 533 double elapsed = now() - progressScopeTime; 534 float p = 1.0 * elapsed / duration; 535 if (p > 1.0) p = 1.0; 536 if (p > progress) { 537 progress = p; 538 redraw = true; 539 } 540 } 541 542 if (redraw) update_progress_locked(); 543 544 pthread_mutex_unlock(&updateMutex); 545 double end = now(); 546 // minimum of 20ms delay between frames 547 double delay = interval - (end - start); 548 if (delay < 0.02) delay = 0.02; 549 usleep(static_cast<useconds_t>(delay * 1000000)); 550 } 551 } 552 553 void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { 554 int result = res_create_display_surface(filename, surface); 555 if (result < 0) { 556 LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; 557 } 558 } 559 560 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { 561 int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface); 562 if (result < 0) { 563 LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; 564 } 565 } 566 567 static char** Alloc2d(size_t rows, size_t cols) { 568 char** result = new char*[rows]; 569 for (size_t i = 0; i < rows; ++i) { 570 result[i] = new char[cols]; 571 memset(result[i], 0, cols); 572 } 573 return result; 574 } 575 576 // Choose the right background string to display during update. 577 void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { 578 if (security_update) { 579 LoadLocalizedBitmap("installing_security_text", &installing_text); 580 } else { 581 LoadLocalizedBitmap("installing_text", &installing_text); 582 } 583 Redraw(); 584 } 585 586 bool ScreenRecoveryUI::InitTextParams() { 587 if (gr_init() < 0) { 588 return false; 589 } 590 591 gr_font_size(gr_sys_font(), &char_width_, &char_height_); 592 text_rows_ = (ScreenHeight() - kMarginHeight * 2) / char_height_; 593 text_cols_ = (ScreenWidth() - kMarginWidth * 2) / char_width_; 594 return true; 595 } 596 597 bool ScreenRecoveryUI::Init(const std::string& locale) { 598 RecoveryUI::Init(locale); 599 600 if (!InitTextParams()) { 601 return false; 602 } 603 604 // Are we portrait or landscape? 605 layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; 606 // Are we the large variant of our base layout? 607 if (gr_fb_height() > PixelsFromDp(800)) ++layout_; 608 609 text_ = Alloc2d(text_rows_, text_cols_ + 1); 610 file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); 611 612 text_col_ = text_row_ = 0; 613 614 // Set up the locale info. 615 SetLocale(locale); 616 617 LoadBitmap("icon_error", &error_icon); 618 619 LoadBitmap("progress_empty", &progressBarEmpty); 620 LoadBitmap("progress_fill", &progressBarFill); 621 622 LoadBitmap("stage_empty", &stageMarkerEmpty); 623 LoadBitmap("stage_fill", &stageMarkerFill); 624 625 // Background text for "installing_update" could be "installing update" 626 // or "installing security update". It will be set after UI init according 627 // to commands in BCB. 628 installing_text = nullptr; 629 LoadLocalizedBitmap("erasing_text", &erasing_text); 630 LoadLocalizedBitmap("no_command_text", &no_command_text); 631 LoadLocalizedBitmap("error_text", &error_text); 632 633 LoadAnimation(); 634 635 pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); 636 637 return true; 638 } 639 640 void ScreenRecoveryUI::LoadAnimation() { 641 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir); 642 dirent* de; 643 std::vector<std::string> intro_frame_names; 644 std::vector<std::string> loop_frame_names; 645 646 while ((de = readdir(dir.get())) != nullptr) { 647 int value, num_chars; 648 if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { 649 intro_frame_names.emplace_back(de->d_name, num_chars); 650 } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { 651 loop_frame_names.emplace_back(de->d_name, num_chars); 652 } 653 } 654 655 intro_frames = intro_frame_names.size(); 656 loop_frames = loop_frame_names.size(); 657 658 // It's okay to not have an intro. 659 if (intro_frames == 0) intro_done = true; 660 // But you must have an animation. 661 if (loop_frames == 0) abort(); 662 663 std::sort(intro_frame_names.begin(), intro_frame_names.end()); 664 std::sort(loop_frame_names.begin(), loop_frame_names.end()); 665 666 introFrames = new GRSurface*[intro_frames]; 667 for (size_t i = 0; i < intro_frames; i++) { 668 LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]); 669 } 670 671 loopFrames = new GRSurface*[loop_frames]; 672 for (size_t i = 0; i < loop_frames; i++) { 673 LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]); 674 } 675 } 676 677 void ScreenRecoveryUI::SetBackground(Icon icon) { 678 pthread_mutex_lock(&updateMutex); 679 680 currentIcon = icon; 681 update_screen_locked(); 682 683 pthread_mutex_unlock(&updateMutex); 684 } 685 686 void ScreenRecoveryUI::SetProgressType(ProgressType type) { 687 pthread_mutex_lock(&updateMutex); 688 if (progressBarType != type) { 689 progressBarType = type; 690 } 691 progressScopeStart = 0; 692 progressScopeSize = 0; 693 progress = 0; 694 update_progress_locked(); 695 pthread_mutex_unlock(&updateMutex); 696 } 697 698 void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { 699 pthread_mutex_lock(&updateMutex); 700 progressBarType = DETERMINATE; 701 progressScopeStart += progressScopeSize; 702 progressScopeSize = portion; 703 progressScopeTime = now(); 704 progressScopeDuration = seconds; 705 progress = 0; 706 update_progress_locked(); 707 pthread_mutex_unlock(&updateMutex); 708 } 709 710 void ScreenRecoveryUI::SetProgress(float fraction) { 711 pthread_mutex_lock(&updateMutex); 712 if (fraction < 0.0) fraction = 0.0; 713 if (fraction > 1.0) fraction = 1.0; 714 if (progressBarType == DETERMINATE && fraction > progress) { 715 // Skip updates that aren't visibly different. 716 int width = gr_get_width(progressBarEmpty); 717 float scale = width * progressScopeSize; 718 if ((int)(progress * scale) != (int)(fraction * scale)) { 719 progress = fraction; 720 update_progress_locked(); 721 } 722 } 723 pthread_mutex_unlock(&updateMutex); 724 } 725 726 void ScreenRecoveryUI::SetStage(int current, int max) { 727 pthread_mutex_lock(&updateMutex); 728 stage = current; 729 max_stage = max; 730 pthread_mutex_unlock(&updateMutex); 731 } 732 733 void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { 734 std::string str; 735 android::base::StringAppendV(&str, fmt, ap); 736 737 if (copy_to_stdout) { 738 fputs(str.c_str(), stdout); 739 } 740 741 pthread_mutex_lock(&updateMutex); 742 if (text_rows_ > 0 && text_cols_ > 0) { 743 for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { 744 if (*ptr == '\n' || text_col_ >= text_cols_) { 745 text_[text_row_][text_col_] = '\0'; 746 text_col_ = 0; 747 text_row_ = (text_row_ + 1) % text_rows_; 748 } 749 if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; 750 } 751 text_[text_row_][text_col_] = '\0'; 752 update_screen_locked(); 753 } 754 pthread_mutex_unlock(&updateMutex); 755 } 756 757 void ScreenRecoveryUI::Print(const char* fmt, ...) { 758 va_list ap; 759 va_start(ap, fmt); 760 PrintV(fmt, true, ap); 761 va_end(ap); 762 } 763 764 void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { 765 va_list ap; 766 va_start(ap, fmt); 767 PrintV(fmt, false, ap); 768 va_end(ap); 769 } 770 771 void ScreenRecoveryUI::PutChar(char ch) { 772 pthread_mutex_lock(&updateMutex); 773 if (ch != '\n') text_[text_row_][text_col_++] = ch; 774 if (ch == '\n' || text_col_ >= text_cols_) { 775 text_col_ = 0; 776 ++text_row_; 777 } 778 pthread_mutex_unlock(&updateMutex); 779 } 780 781 void ScreenRecoveryUI::ClearText() { 782 pthread_mutex_lock(&updateMutex); 783 text_col_ = 0; 784 text_row_ = 0; 785 for (size_t i = 0; i < text_rows_; ++i) { 786 memset(text_[i], 0, text_cols_ + 1); 787 } 788 pthread_mutex_unlock(&updateMutex); 789 } 790 791 void ScreenRecoveryUI::ShowFile(FILE* fp) { 792 std::vector<off_t> offsets; 793 offsets.push_back(ftello(fp)); 794 ClearText(); 795 796 struct stat sb; 797 fstat(fileno(fp), &sb); 798 799 bool show_prompt = false; 800 while (true) { 801 if (show_prompt) { 802 PrintOnScreenOnly("--(%d%% of %d bytes)--", 803 static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), 804 static_cast<int>(sb.st_size)); 805 Redraw(); 806 while (show_prompt) { 807 show_prompt = false; 808 int key = WaitKey(); 809 if (key == KEY_POWER || key == KEY_ENTER) { 810 return; 811 } else if (key == KEY_UP || key == KEY_VOLUMEUP) { 812 if (offsets.size() <= 1) { 813 show_prompt = true; 814 } else { 815 offsets.pop_back(); 816 fseek(fp, offsets.back(), SEEK_SET); 817 } 818 } else { 819 if (feof(fp)) { 820 return; 821 } 822 offsets.push_back(ftello(fp)); 823 } 824 } 825 ClearText(); 826 } 827 828 int ch = getc(fp); 829 if (ch == EOF) { 830 while (text_row_ < text_rows_ - 1) PutChar('\n'); 831 show_prompt = true; 832 } else { 833 PutChar(ch); 834 if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { 835 show_prompt = true; 836 } 837 } 838 } 839 } 840 841 void ScreenRecoveryUI::ShowFile(const char* filename) { 842 FILE* fp = fopen_path(filename, "re"); 843 if (fp == nullptr) { 844 Print(" Unable to open %s: %s\n", filename, strerror(errno)); 845 return; 846 } 847 848 char** old_text = text_; 849 size_t old_text_col = text_col_; 850 size_t old_text_row = text_row_; 851 852 // Swap in the alternate screen and clear it. 853 text_ = file_viewer_text_; 854 ClearText(); 855 856 ShowFile(fp); 857 fclose(fp); 858 859 text_ = old_text; 860 text_col_ = old_text_col; 861 text_row_ = old_text_row; 862 } 863 864 void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items, 865 int initial_selection) { 866 pthread_mutex_lock(&updateMutex); 867 if (text_rows_ > 0 && text_cols_ > 0) { 868 menu_headers_ = headers; 869 menu_.clear(); 870 for (size_t i = 0; i < text_rows_ && items[i] != nullptr; ++i) { 871 menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1))); 872 } 873 menu_items = static_cast<int>(menu_.size()); 874 show_menu = true; 875 menu_sel = initial_selection; 876 update_screen_locked(); 877 } 878 pthread_mutex_unlock(&updateMutex); 879 } 880 881 int ScreenRecoveryUI::SelectMenu(int sel) { 882 pthread_mutex_lock(&updateMutex); 883 if (show_menu) { 884 int old_sel = menu_sel; 885 menu_sel = sel; 886 887 // Wrap at top and bottom. 888 if (menu_sel < 0) menu_sel = menu_items - 1; 889 if (menu_sel >= menu_items) menu_sel = 0; 890 891 sel = menu_sel; 892 if (menu_sel != old_sel) update_screen_locked(); 893 } 894 pthread_mutex_unlock(&updateMutex); 895 return sel; 896 } 897 898 void ScreenRecoveryUI::EndMenu() { 899 pthread_mutex_lock(&updateMutex); 900 if (show_menu && text_rows_ > 0 && text_cols_ > 0) { 901 show_menu = false; 902 update_screen_locked(); 903 } 904 pthread_mutex_unlock(&updateMutex); 905 } 906 907 bool ScreenRecoveryUI::IsTextVisible() { 908 pthread_mutex_lock(&updateMutex); 909 int visible = show_text; 910 pthread_mutex_unlock(&updateMutex); 911 return visible; 912 } 913 914 bool ScreenRecoveryUI::WasTextEverVisible() { 915 pthread_mutex_lock(&updateMutex); 916 int ever_visible = show_text_ever; 917 pthread_mutex_unlock(&updateMutex); 918 return ever_visible; 919 } 920 921 void ScreenRecoveryUI::ShowText(bool visible) { 922 pthread_mutex_lock(&updateMutex); 923 show_text = visible; 924 if (show_text) show_text_ever = true; 925 update_screen_locked(); 926 pthread_mutex_unlock(&updateMutex); 927 } 928 929 void ScreenRecoveryUI::Redraw() { 930 pthread_mutex_lock(&updateMutex); 931 update_screen_locked(); 932 pthread_mutex_unlock(&updateMutex); 933 } 934 935 void ScreenRecoveryUI::KeyLongPress(int) { 936 // Redraw so that if we're in the menu, the highlight 937 // will change color to indicate a successful long press. 938 Redraw(); 939 } 940 941 void ScreenRecoveryUI::SetLocale(const std::string& new_locale) { 942 locale_ = new_locale; 943 rtl_locale_ = false; 944 945 if (!new_locale.empty()) { 946 size_t underscore = new_locale.find('_'); 947 // lang has the language prefix prior to '_', or full string if '_' doesn't exist. 948 std::string lang = new_locale.substr(0, underscore); 949 950 // A bit cheesy: keep an explicit list of supported RTL languages. 951 if (lang == "ar" || // Arabic 952 lang == "fa" || // Persian (Farsi) 953 lang == "he" || // Hebrew (new language code) 954 lang == "iw" || // Hebrew (old language code) 955 lang == "ur") { // Urdu 956 rtl_locale_ = true; 957 } 958 } 959 } 960