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