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