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