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 <errno.h> 18 #include <fcntl.h> 19 #include <linux/input.h> 20 #include <pthread.h> 21 #include <stdarg.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <sys/stat.h> 26 #include <sys/time.h> 27 #include <sys/types.h> 28 #include <time.h> 29 #include <unistd.h> 30 31 #include <vector> 32 33 #include "base/strings.h" 34 #include "cutils/properties.h" 35 #include "common.h" 36 #include "device.h" 37 #include "minui/minui.h" 38 #include "screen_ui.h" 39 #include "ui.h" 40 41 static int char_width; 42 static int char_height; 43 44 // Return the current time as a double (including fractions of a second). 45 static double now() { 46 struct timeval tv; 47 gettimeofday(&tv, nullptr); 48 return tv.tv_sec + tv.tv_usec / 1000000.0; 49 } 50 51 ScreenRecoveryUI::ScreenRecoveryUI() : 52 currentIcon(NONE), 53 installingFrame(0), 54 locale(nullptr), 55 rtl_locale(false), 56 progressBarType(EMPTY), 57 progressScopeStart(0), 58 progressScopeSize(0), 59 progress(0), 60 pagesIdentical(false), 61 text_cols_(0), 62 text_rows_(0), 63 text_(nullptr), 64 text_col_(0), 65 text_row_(0), 66 text_top_(0), 67 show_text(false), 68 show_text_ever(false), 69 menu_(nullptr), 70 show_menu(false), 71 menu_items(0), 72 menu_sel(0), 73 file_viewer_text_(nullptr), 74 animation_fps(20), 75 installing_frames(-1), 76 stage(-1), 77 max_stage(-1) { 78 79 for (int i = 0; i < 5; i++) { 80 backgroundIcon[i] = nullptr; 81 } 82 pthread_mutex_init(&updateMutex, nullptr); 83 } 84 85 // Clear the screen and draw the currently selected background icon (if any). 86 // Should only be called with updateMutex locked. 87 void ScreenRecoveryUI::draw_background_locked(Icon icon) { 88 pagesIdentical = false; 89 gr_color(0, 0, 0, 255); 90 gr_clear(); 91 92 if (icon) { 93 GRSurface* surface = backgroundIcon[icon]; 94 if (icon == INSTALLING_UPDATE || icon == ERASING) { 95 surface = installation[installingFrame]; 96 } 97 GRSurface* text_surface = backgroundText[icon]; 98 99 int iconWidth = gr_get_width(surface); 100 int iconHeight = gr_get_height(surface); 101 int textWidth = gr_get_width(text_surface); 102 int textHeight = gr_get_height(text_surface); 103 int stageHeight = gr_get_height(stageMarkerEmpty); 104 105 int sh = (max_stage >= 0) ? stageHeight : 0; 106 107 iconX = (gr_fb_width() - iconWidth) / 2; 108 iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2; 109 110 int textX = (gr_fb_width() - textWidth) / 2; 111 int textY = ((gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40; 112 113 gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); 114 if (stageHeight > 0) { 115 int sw = gr_get_width(stageMarkerEmpty); 116 int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; 117 int y = iconY + iconHeight + 20; 118 for (int i = 0; i < max_stage; ++i) { 119 gr_blit((i < stage) ? stageMarkerFill : stageMarkerEmpty, 120 0, 0, sw, stageHeight, x, y); 121 x += sw; 122 } 123 } 124 125 gr_color(255, 255, 255, 255); 126 gr_texticon(textX, textY, text_surface); 127 } 128 } 129 130 // Draw the progress bar (if any) on the screen. Does not flip pages. 131 // Should only be called with updateMutex locked. 132 void ScreenRecoveryUI::draw_progress_locked() { 133 if (currentIcon == ERROR) return; 134 135 if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { 136 GRSurface* icon = installation[installingFrame]; 137 gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY); 138 } 139 140 if (progressBarType != EMPTY) { 141 int iconHeight = gr_get_height(backgroundIcon[INSTALLING_UPDATE]); 142 int width = gr_get_width(progressBarEmpty); 143 int height = gr_get_height(progressBarEmpty); 144 145 int dx = (gr_fb_width() - width)/2; 146 int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; 147 148 // Erase behind the progress bar (in case this was a progress-only update) 149 gr_color(0, 0, 0, 255); 150 gr_fill(dx, dy, width, height); 151 152 if (progressBarType == DETERMINATE) { 153 float p = progressScopeStart + progress * progressScopeSize; 154 int pos = (int) (p * width); 155 156 if (rtl_locale) { 157 // Fill the progress bar from right to left. 158 if (pos > 0) { 159 gr_blit(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy); 160 } 161 if (pos < width-1) { 162 gr_blit(progressBarEmpty, 0, 0, width-pos, height, dx, dy); 163 } 164 } else { 165 // Fill the progress bar from left to right. 166 if (pos > 0) { 167 gr_blit(progressBarFill, 0, 0, pos, height, dx, dy); 168 } 169 if (pos < width-1) { 170 gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); 171 } 172 } 173 } 174 } 175 } 176 177 void ScreenRecoveryUI::SetColor(UIElement e) { 178 switch (e) { 179 case INFO: 180 gr_color(249, 194, 0, 255); 181 break; 182 case HEADER: 183 gr_color(247, 0, 6, 255); 184 break; 185 case MENU: 186 case MENU_SEL_BG: 187 gr_color(0, 106, 157, 255); 188 break; 189 case MENU_SEL_BG_ACTIVE: 190 gr_color(0, 156, 100, 255); 191 break; 192 case MENU_SEL_FG: 193 gr_color(255, 255, 255, 255); 194 break; 195 case LOG: 196 gr_color(196, 196, 196, 255); 197 break; 198 case TEXT_FILL: 199 gr_color(0, 0, 0, 160); 200 break; 201 default: 202 gr_color(255, 255, 255, 255); 203 break; 204 } 205 } 206 207 void ScreenRecoveryUI::DrawHorizontalRule(int* y) { 208 SetColor(MENU); 209 *y += 4; 210 gr_fill(0, *y, gr_fb_width(), *y + 2); 211 *y += 4; 212 } 213 214 void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) { 215 gr_text(4, *y, line, bold); 216 *y += char_height + 4; 217 } 218 219 void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) { 220 for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { 221 DrawTextLine(y, lines[i], false); 222 } 223 } 224 225 static const char* REGULAR_HELP[] = { 226 "Use volume up/down and power.", 227 NULL 228 }; 229 230 static const char* LONG_PRESS_HELP[] = { 231 "Any button cycles highlight.", 232 "Long-press activates.", 233 NULL 234 }; 235 236 // Redraw everything on the screen. Does not flip pages. 237 // Should only be called with updateMutex locked. 238 void ScreenRecoveryUI::draw_screen_locked() { 239 if (!show_text) { 240 draw_background_locked(currentIcon); 241 draw_progress_locked(); 242 } else { 243 gr_color(0, 0, 0, 255); 244 gr_clear(); 245 246 int y = 0; 247 if (show_menu) { 248 char recovery_fingerprint[PROPERTY_VALUE_MAX]; 249 property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); 250 251 SetColor(INFO); 252 DrawTextLine(&y, "Android Recovery", true); 253 for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { 254 DrawTextLine(&y, chunk.c_str(), false); 255 } 256 DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); 257 258 SetColor(HEADER); 259 DrawTextLines(&y, menu_headers_); 260 261 SetColor(MENU); 262 DrawHorizontalRule(&y); 263 y += 4; 264 for (int i = 0; i < menu_items; ++i) { 265 if (i == menu_sel) { 266 // Draw the highlight bar. 267 SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); 268 gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2); 269 // Bold white text for the selected item. 270 SetColor(MENU_SEL_FG); 271 gr_text(4, y, menu_[i], true); 272 SetColor(MENU); 273 } else { 274 gr_text(4, y, menu_[i], false); 275 } 276 y += char_height + 4; 277 } 278 DrawHorizontalRule(&y); 279 } 280 281 // display from the bottom up, until we hit the top of the 282 // screen, the bottom of the menu, or we've displayed the 283 // entire text buffer. 284 SetColor(LOG); 285 int row = (text_top_ + text_rows_ - 1) % text_rows_; 286 size_t count = 0; 287 for (int ty = gr_fb_height() - char_height; 288 ty >= y && count < text_rows_; 289 ty -= char_height, ++count) { 290 gr_text(0, ty, text_[row], false); 291 --row; 292 if (row < 0) row = text_rows_ - 1; 293 } 294 } 295 } 296 297 // Redraw everything on the screen and flip the screen (make it visible). 298 // Should only be called with updateMutex locked. 299 void ScreenRecoveryUI::update_screen_locked() { 300 draw_screen_locked(); 301 gr_flip(); 302 } 303 304 // Updates only the progress bar, if possible, otherwise redraws the screen. 305 // Should only be called with updateMutex locked. 306 void ScreenRecoveryUI::update_progress_locked() { 307 if (show_text || !pagesIdentical) { 308 draw_screen_locked(); // Must redraw the whole screen 309 pagesIdentical = true; 310 } else { 311 draw_progress_locked(); // Draw only the progress bar and overlays 312 } 313 gr_flip(); 314 } 315 316 // Keeps the progress bar updated, even when the process is otherwise busy. 317 void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { 318 reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); 319 return nullptr; 320 } 321 322 void ScreenRecoveryUI::ProgressThreadLoop() { 323 double interval = 1.0 / animation_fps; 324 while (true) { 325 double start = now(); 326 pthread_mutex_lock(&updateMutex); 327 328 int redraw = 0; 329 330 // update the installation animation, if active 331 // skip this if we have a text overlay (too expensive to update) 332 if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && 333 installing_frames > 0 && !show_text) { 334 installingFrame = (installingFrame + 1) % installing_frames; 335 redraw = 1; 336 } 337 338 // move the progress bar forward on timed intervals, if configured 339 int duration = progressScopeDuration; 340 if (progressBarType == DETERMINATE && duration > 0) { 341 double elapsed = now() - progressScopeTime; 342 float p = 1.0 * elapsed / duration; 343 if (p > 1.0) p = 1.0; 344 if (p > progress) { 345 progress = p; 346 redraw = 1; 347 } 348 } 349 350 if (redraw) update_progress_locked(); 351 352 pthread_mutex_unlock(&updateMutex); 353 double end = now(); 354 // minimum of 20ms delay between frames 355 double delay = interval - (end-start); 356 if (delay < 0.02) delay = 0.02; 357 usleep((long)(delay * 1000000)); 358 } 359 } 360 361 void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { 362 int result = res_create_display_surface(filename, surface); 363 if (result < 0) { 364 LOGE("missing bitmap %s\n(Code %d)\n", filename, result); 365 } 366 } 367 368 void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, GRSurface*** surface) { 369 int result = res_create_multi_display_surface(filename, frames, surface); 370 if (result < 0) { 371 LOGE("missing bitmap %s\n(Code %d)\n", filename, result); 372 } 373 } 374 375 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { 376 int result = res_create_localized_alpha_surface(filename, locale, surface); 377 if (result < 0) { 378 LOGE("missing bitmap %s\n(Code %d)\n", filename, result); 379 } 380 } 381 382 static char** Alloc2d(size_t rows, size_t cols) { 383 char** result = new char*[rows]; 384 for (size_t i = 0; i < rows; ++i) { 385 result[i] = new char[cols]; 386 memset(result[i], 0, cols); 387 } 388 return result; 389 } 390 391 void ScreenRecoveryUI::Init() { 392 gr_init(); 393 394 gr_font_size(&char_width, &char_height); 395 text_rows_ = gr_fb_height() / char_height; 396 text_cols_ = gr_fb_width() / char_width; 397 398 text_ = Alloc2d(text_rows_, text_cols_ + 1); 399 file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); 400 menu_ = Alloc2d(text_rows_, text_cols_ + 1); 401 402 text_col_ = text_row_ = 0; 403 text_top_ = 1; 404 405 backgroundIcon[NONE] = nullptr; 406 LoadBitmapArray("icon_installing", &installing_frames, &installation); 407 backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr; 408 backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; 409 LoadBitmap("icon_error", &backgroundIcon[ERROR]); 410 backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; 411 412 LoadBitmap("progress_empty", &progressBarEmpty); 413 LoadBitmap("progress_fill", &progressBarFill); 414 LoadBitmap("stage_empty", &stageMarkerEmpty); 415 LoadBitmap("stage_fill", &stageMarkerFill); 416 417 LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); 418 LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); 419 LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); 420 LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); 421 422 pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); 423 424 RecoveryUI::Init(); 425 } 426 427 void ScreenRecoveryUI::SetLocale(const char* new_locale) { 428 if (new_locale) { 429 this->locale = new_locale; 430 char* lang = strdup(locale); 431 for (char* p = lang; *p; ++p) { 432 if (*p == '_') { 433 *p = '\0'; 434 break; 435 } 436 } 437 438 // A bit cheesy: keep an explicit list of supported languages 439 // that are RTL. 440 if (strcmp(lang, "ar") == 0 || // Arabic 441 strcmp(lang, "fa") == 0 || // Persian (Farsi) 442 strcmp(lang, "he") == 0 || // Hebrew (new language code) 443 strcmp(lang, "iw") == 0 || // Hebrew (old language code) 444 strcmp(lang, "ur") == 0) { // Urdu 445 rtl_locale = true; 446 } 447 free(lang); 448 } else { 449 new_locale = nullptr; 450 } 451 } 452 453 void ScreenRecoveryUI::SetBackground(Icon icon) { 454 pthread_mutex_lock(&updateMutex); 455 456 currentIcon = icon; 457 update_screen_locked(); 458 459 pthread_mutex_unlock(&updateMutex); 460 } 461 462 void ScreenRecoveryUI::SetProgressType(ProgressType type) { 463 pthread_mutex_lock(&updateMutex); 464 if (progressBarType != type) { 465 progressBarType = type; 466 } 467 progressScopeStart = 0; 468 progressScopeSize = 0; 469 progress = 0; 470 update_progress_locked(); 471 pthread_mutex_unlock(&updateMutex); 472 } 473 474 void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { 475 pthread_mutex_lock(&updateMutex); 476 progressBarType = DETERMINATE; 477 progressScopeStart += progressScopeSize; 478 progressScopeSize = portion; 479 progressScopeTime = now(); 480 progressScopeDuration = seconds; 481 progress = 0; 482 update_progress_locked(); 483 pthread_mutex_unlock(&updateMutex); 484 } 485 486 void ScreenRecoveryUI::SetProgress(float fraction) { 487 pthread_mutex_lock(&updateMutex); 488 if (fraction < 0.0) fraction = 0.0; 489 if (fraction > 1.0) fraction = 1.0; 490 if (progressBarType == DETERMINATE && fraction > progress) { 491 // Skip updates that aren't visibly different. 492 int width = gr_get_width(progressBarEmpty); 493 float scale = width * progressScopeSize; 494 if ((int) (progress * scale) != (int) (fraction * scale)) { 495 progress = fraction; 496 update_progress_locked(); 497 } 498 } 499 pthread_mutex_unlock(&updateMutex); 500 } 501 502 void ScreenRecoveryUI::SetStage(int current, int max) { 503 pthread_mutex_lock(&updateMutex); 504 stage = current; 505 max_stage = max; 506 pthread_mutex_unlock(&updateMutex); 507 } 508 509 void ScreenRecoveryUI::Print(const char *fmt, ...) { 510 char buf[256]; 511 va_list ap; 512 va_start(ap, fmt); 513 vsnprintf(buf, 256, fmt, ap); 514 va_end(ap); 515 516 fputs(buf, stdout); 517 518 pthread_mutex_lock(&updateMutex); 519 if (text_rows_ > 0 && text_cols_ > 0) { 520 for (const char* ptr = buf; *ptr != '\0'; ++ptr) { 521 if (*ptr == '\n' || text_col_ >= text_cols_) { 522 text_[text_row_][text_col_] = '\0'; 523 text_col_ = 0; 524 text_row_ = (text_row_ + 1) % text_rows_; 525 if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; 526 } 527 if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; 528 } 529 text_[text_row_][text_col_] = '\0'; 530 update_screen_locked(); 531 } 532 pthread_mutex_unlock(&updateMutex); 533 } 534 535 void ScreenRecoveryUI::PutChar(char ch) { 536 pthread_mutex_lock(&updateMutex); 537 if (ch != '\n') text_[text_row_][text_col_++] = ch; 538 if (ch == '\n' || text_col_ >= text_cols_) { 539 text_col_ = 0; 540 ++text_row_; 541 542 if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; 543 } 544 pthread_mutex_unlock(&updateMutex); 545 } 546 547 void ScreenRecoveryUI::ClearText() { 548 pthread_mutex_lock(&updateMutex); 549 text_col_ = 0; 550 text_row_ = 0; 551 text_top_ = 1; 552 for (size_t i = 0; i < text_rows_; ++i) { 553 memset(text_[i], 0, text_cols_ + 1); 554 } 555 pthread_mutex_unlock(&updateMutex); 556 } 557 558 void ScreenRecoveryUI::ShowFile(FILE* fp) { 559 std::vector<long> offsets; 560 offsets.push_back(ftell(fp)); 561 ClearText(); 562 563 struct stat sb; 564 fstat(fileno(fp), &sb); 565 566 bool show_prompt = false; 567 while (true) { 568 if (show_prompt) { 569 Print("--(%d%% of %d bytes)--", 570 static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), 571 static_cast<int>(sb.st_size)); 572 Redraw(); 573 while (show_prompt) { 574 show_prompt = false; 575 int key = WaitKey(); 576 if (key == KEY_POWER || key == KEY_ENTER) { 577 return; 578 } else if (key == KEY_UP || key == KEY_VOLUMEUP) { 579 if (offsets.size() <= 1) { 580 show_prompt = true; 581 } else { 582 offsets.pop_back(); 583 fseek(fp, offsets.back(), SEEK_SET); 584 } 585 } else { 586 if (feof(fp)) { 587 return; 588 } 589 offsets.push_back(ftell(fp)); 590 } 591 } 592 ClearText(); 593 } 594 595 int ch = getc(fp); 596 if (ch == EOF) { 597 while (text_row_ < text_rows_ - 1) PutChar('\n'); 598 show_prompt = true; 599 } else { 600 PutChar(ch); 601 if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { 602 show_prompt = true; 603 } 604 } 605 } 606 } 607 608 void ScreenRecoveryUI::ShowFile(const char* filename) { 609 FILE* fp = fopen_path(filename, "re"); 610 if (fp == nullptr) { 611 Print(" Unable to open %s: %s\n", filename, strerror(errno)); 612 return; 613 } 614 615 char** old_text = text_; 616 size_t old_text_col = text_col_; 617 size_t old_text_row = text_row_; 618 size_t old_text_top = text_top_; 619 620 // Swap in the alternate screen and clear it. 621 text_ = file_viewer_text_; 622 ClearText(); 623 624 ShowFile(fp); 625 fclose(fp); 626 627 text_ = old_text; 628 text_col_ = old_text_col; 629 text_row_ = old_text_row; 630 text_top_ = old_text_top; 631 } 632 633 void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items, 634 int initial_selection) { 635 pthread_mutex_lock(&updateMutex); 636 if (text_rows_ > 0 && text_cols_ > 0) { 637 menu_headers_ = headers; 638 size_t i = 0; 639 for (; i < text_rows_ && items[i] != nullptr; ++i) { 640 strncpy(menu_[i], items[i], text_cols_ - 1); 641 menu_[i][text_cols_ - 1] = '\0'; 642 } 643 menu_items = i; 644 show_menu = true; 645 menu_sel = initial_selection; 646 update_screen_locked(); 647 } 648 pthread_mutex_unlock(&updateMutex); 649 } 650 651 int ScreenRecoveryUI::SelectMenu(int sel) { 652 pthread_mutex_lock(&updateMutex); 653 if (show_menu) { 654 int old_sel = menu_sel; 655 menu_sel = sel; 656 657 // Wrap at top and bottom. 658 if (menu_sel < 0) menu_sel = menu_items - 1; 659 if (menu_sel >= menu_items) menu_sel = 0; 660 661 sel = menu_sel; 662 if (menu_sel != old_sel) update_screen_locked(); 663 } 664 pthread_mutex_unlock(&updateMutex); 665 return sel; 666 } 667 668 void ScreenRecoveryUI::EndMenu() { 669 pthread_mutex_lock(&updateMutex); 670 if (show_menu && text_rows_ > 0 && text_cols_ > 0) { 671 show_menu = false; 672 update_screen_locked(); 673 } 674 pthread_mutex_unlock(&updateMutex); 675 } 676 677 bool ScreenRecoveryUI::IsTextVisible() { 678 pthread_mutex_lock(&updateMutex); 679 int visible = show_text; 680 pthread_mutex_unlock(&updateMutex); 681 return visible; 682 } 683 684 bool ScreenRecoveryUI::WasTextEverVisible() { 685 pthread_mutex_lock(&updateMutex); 686 int ever_visible = show_text_ever; 687 pthread_mutex_unlock(&updateMutex); 688 return ever_visible; 689 } 690 691 void ScreenRecoveryUI::ShowText(bool visible) { 692 pthread_mutex_lock(&updateMutex); 693 show_text = visible; 694 if (show_text) show_text_ever = true; 695 update_screen_locked(); 696 pthread_mutex_unlock(&updateMutex); 697 } 698 699 void ScreenRecoveryUI::Redraw() { 700 pthread_mutex_lock(&updateMutex); 701 update_screen_locked(); 702 pthread_mutex_unlock(&updateMutex); 703 } 704 705 void ScreenRecoveryUI::KeyLongPress(int) { 706 // Redraw so that if we're in the menu, the highlight 707 // will change color to indicate a successful long press. 708 Redraw(); 709 } 710