Home | History | Annotate | Download | only in recovery
      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