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