Home | History | Annotate | Download | only in recovery
      1 /*
      2  * Copyright (C) 2014 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 <stdarg.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <sys/stat.h>
     23 #include <sys/time.h>
     24 #include <sys/types.h>
     25 #include <time.h>
     26 #include <unistd.h>
     27 
     28 #include <vector>
     29 
     30 #include "common.h"
     31 #include "device.h"
     32 #include "wear_ui.h"
     33 #include "cutils/properties.h"
     34 #include "android-base/strings.h"
     35 #include "android-base/stringprintf.h"
     36 
     37 // There's only (at most) one of these objects, and global callbacks
     38 // (for pthread_create, and the input event system) need to find it,
     39 // so use a global variable.
     40 static WearRecoveryUI* self = NULL;
     41 
     42 // Return the current time as a double (including fractions of a second).
     43 static double now() {
     44     struct timeval tv;
     45     gettimeofday(&tv, NULL);
     46     return tv.tv_sec + tv.tv_usec / 1000000.0;
     47 }
     48 
     49 WearRecoveryUI::WearRecoveryUI() :
     50     progress_bar_height(3),
     51     progress_bar_width(200),
     52     progress_bar_y(259),
     53     outer_height(0),
     54     outer_width(0),
     55     menu_unusable_rows(0),
     56     intro_frames(22),
     57     loop_frames(60),
     58     animation_fps(30),
     59     currentIcon(NONE),
     60     intro_done(false),
     61     current_frame(0),
     62     progressBarType(EMPTY),
     63     progressScopeStart(0),
     64     progressScopeSize(0),
     65     progress(0),
     66     text_cols(0),
     67     text_rows(0),
     68     text_col(0),
     69     text_row(0),
     70     text_top(0),
     71     show_text(false),
     72     show_text_ever(false),
     73     show_menu(false),
     74     menu_items(0),
     75     menu_sel(0) {
     76 
     77     for (size_t i = 0; i < 5; i++)
     78         backgroundIcon[i] = NULL;
     79 
     80     self = this;
     81 }
     82 
     83 // Draw background frame on the screen.  Does not flip pages.
     84 // Should only be called with updateMutex locked.
     85 void WearRecoveryUI::draw_background_locked(Icon icon)
     86 {
     87     gr_color(0, 0, 0, 255);
     88     gr_fill(0, 0, gr_fb_width(), gr_fb_height());
     89 
     90     if (icon) {
     91         GRSurface* surface;
     92         if (icon == INSTALLING_UPDATE || icon == ERASING) {
     93             if (!intro_done) {
     94                 surface = introFrames[current_frame];
     95             } else {
     96                 surface = loopFrames[current_frame];
     97             }
     98         }
     99         else {
    100             surface = backgroundIcon[icon];
    101         }
    102 
    103         int width = gr_get_width(surface);
    104         int height = gr_get_height(surface);
    105 
    106         int x = (gr_fb_width() - width) / 2;
    107         int y = (gr_fb_height() - height) / 2;
    108 
    109         gr_blit(surface, 0, 0, width, height, x, y);
    110     }
    111 }
    112 
    113 // Draw the progress bar (if any) on the screen.  Does not flip pages.
    114 // Should only be called with updateMutex locked.
    115 void WearRecoveryUI::draw_progress_locked()
    116 {
    117     if (currentIcon == ERROR) return;
    118     if (progressBarType != DETERMINATE) return;
    119 
    120     int width = progress_bar_width;
    121     int height = progress_bar_height;
    122     int dx = (gr_fb_width() - width)/2;
    123     int dy = progress_bar_y;
    124 
    125     float p = progressScopeStart + progress * progressScopeSize;
    126     int pos = (int) (p * width);
    127 
    128     gr_color(0x43, 0x43, 0x43, 0xff);
    129     gr_fill(dx, dy, dx + width, dy + height);
    130 
    131     if (pos > 0) {
    132         gr_color(0x02, 0xa8, 0xf3, 255);
    133         if (rtl_locale) {
    134             // Fill the progress bar from right to left.
    135             gr_fill(dx + width - pos, dy, dx + width, dy + height);
    136         } else {
    137             // Fill the progress bar from left to right.
    138             gr_fill(dx, dy, dx + pos, dy + height);
    139         }
    140     }
    141 }
    142 
    143 static const char* HEADERS[] = {
    144     "Swipe up/down to move.",
    145     "Swipe left/right to select.",
    146     "",
    147     NULL
    148 };
    149 
    150 void WearRecoveryUI::draw_screen_locked()
    151 {
    152     draw_background_locked(currentIcon);
    153     draw_progress_locked();
    154     char cur_selection_str[50];
    155 
    156     if (show_text) {
    157         SetColor(TEXT_FILL);
    158         gr_fill(0, 0, gr_fb_width(), gr_fb_height());
    159 
    160         int y = outer_height;
    161         int x = outer_width;
    162         if (show_menu) {
    163             char recovery_fingerprint[PROPERTY_VALUE_MAX];
    164             property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, "");
    165             SetColor(HEADER);
    166             DrawTextLine(x + 4, &y, "Android Recovery", true);
    167             for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) {
    168                 DrawTextLine(x +4, &y, chunk.c_str(), false);
    169             }
    170 
    171             // This is actually the help strings.
    172             DrawTextLines(x + 4, &y, HEADERS);
    173             SetColor(HEADER);
    174             DrawTextLines(x + 4, &y, menu_headers_);
    175 
    176             // Show the current menu item number in relation to total number if
    177             // items don't fit on the screen.
    178             if (menu_items > menu_end - menu_start) {
    179                 sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items);
    180                 gr_text(x+4, y, cur_selection_str, 1);
    181                 y += char_height_+4;
    182             }
    183 
    184             // Menu begins here
    185             SetColor(MENU);
    186 
    187             for (int i = menu_start; i < menu_end; ++i) {
    188 
    189                 if (i == menu_sel) {
    190                     // draw the highlight bar
    191                     SetColor(MENU_SEL_BG);
    192                     gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2);
    193                     // white text of selected item
    194                     SetColor(MENU_SEL_FG);
    195                     if (menu[i][0]) gr_text(x+4, y, menu[i], 1);
    196                     SetColor(MENU);
    197                 } else {
    198                     if (menu[i][0]) gr_text(x+4, y, menu[i], 0);
    199                 }
    200                 y += char_height_+4;
    201             }
    202             SetColor(MENU);
    203             y += 4;
    204             gr_fill(0, y, gr_fb_width(), y+2);
    205             y += 4;
    206         }
    207 
    208         SetColor(LOG);
    209 
    210         // display from the bottom up, until we hit the top of the
    211         // screen, the bottom of the menu, or we've displayed the
    212         // entire text buffer.
    213         int ty;
    214         int row = (text_top+text_rows-1) % text_rows;
    215         size_t count = 0;
    216         for (int ty = gr_fb_height() - char_height_ - outer_height;
    217              ty > y+2 && count < text_rows;
    218              ty -= char_height_, ++count) {
    219             gr_text(x+4, ty, text[row], 0);
    220             --row;
    221             if (row < 0) row = text_rows-1;
    222         }
    223     }
    224 }
    225 
    226 void WearRecoveryUI::update_screen_locked()
    227 {
    228     draw_screen_locked();
    229     gr_flip();
    230 }
    231 
    232 // Keeps the progress bar updated, even when the process is otherwise busy.
    233 void* WearRecoveryUI::progress_thread(void *cookie) {
    234     self->progress_loop();
    235     return NULL;
    236 }
    237 
    238 void WearRecoveryUI::progress_loop() {
    239     double interval = 1.0 / animation_fps;
    240     for (;;) {
    241         double start = now();
    242         pthread_mutex_lock(&updateMutex);
    243         int redraw = 0;
    244 
    245         if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING)
    246                                                             && !show_text) {
    247             if (!intro_done) {
    248                 if (current_frame >= intro_frames - 1) {
    249                     intro_done = true;
    250                     current_frame = 0;
    251                 } else {
    252                     current_frame++;
    253                 }
    254             } else {
    255                 current_frame = (current_frame + 1) % loop_frames;
    256             }
    257             redraw = 1;
    258         }
    259 
    260         // move the progress bar forward on timed intervals, if configured
    261         int duration = progressScopeDuration;
    262         if (progressBarType == DETERMINATE && duration > 0) {
    263             double elapsed = now() - progressScopeTime;
    264             float p = 1.0 * elapsed / duration;
    265             if (p > 1.0) p = 1.0;
    266             if (p > progress) {
    267                 progress = p;
    268                 redraw = 1;
    269             }
    270         }
    271 
    272         if (redraw)
    273             update_screen_locked();
    274 
    275         pthread_mutex_unlock(&updateMutex);
    276         double end = now();
    277         // minimum of 20ms delay between frames
    278         double delay = interval - (end-start);
    279         if (delay < 0.02) delay = 0.02;
    280         usleep((long)(delay * 1000000));
    281     }
    282 }
    283 
    284 void WearRecoveryUI::Init()
    285 {
    286     gr_init();
    287 
    288     gr_font_size(&char_width_, &char_height_);
    289 
    290     text_col = text_row = 0;
    291     text_rows = (gr_fb_height()) / char_height_;
    292     visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
    293     if (text_rows > kMaxRows) text_rows = kMaxRows;
    294     text_top = 1;
    295 
    296     text_cols = (gr_fb_width() - (outer_width * 2)) / char_width_;
    297     if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
    298 
    299     LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]);
    300     backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
    301     LoadBitmap("icon_error", &backgroundIcon[ERROR]);
    302     backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
    303 
    304     introFrames = (GRSurface**)malloc(intro_frames * sizeof(GRSurface*));
    305     for (int i = 0; i < intro_frames; ++i) {
    306         char filename[40];
    307         sprintf(filename, "intro%02d", i);
    308         LoadBitmap(filename, introFrames + i);
    309     }
    310 
    311     loopFrames = (GRSurface**)malloc(loop_frames * sizeof(GRSurface*));
    312     for (int i = 0; i < loop_frames; ++i) {
    313         char filename[40];
    314         sprintf(filename, "loop%02d", i);
    315         LoadBitmap(filename, loopFrames + i);
    316     }
    317 
    318     pthread_create(&progress_t, NULL, progress_thread, NULL);
    319     RecoveryUI::Init();
    320 }
    321 
    322 void WearRecoveryUI::SetBackground(Icon icon)
    323 {
    324     pthread_mutex_lock(&updateMutex);
    325     currentIcon = icon;
    326     update_screen_locked();
    327     pthread_mutex_unlock(&updateMutex);
    328 }
    329 
    330 void WearRecoveryUI::SetProgressType(ProgressType type)
    331 {
    332     pthread_mutex_lock(&updateMutex);
    333     if (progressBarType != type) {
    334         progressBarType = type;
    335     }
    336     progressScopeStart = 0;
    337     progressScopeSize = 0;
    338     progress = 0;
    339     update_screen_locked();
    340     pthread_mutex_unlock(&updateMutex);
    341 }
    342 
    343 void WearRecoveryUI::ShowProgress(float portion, float seconds)
    344 {
    345     pthread_mutex_lock(&updateMutex);
    346     progressBarType = DETERMINATE;
    347     progressScopeStart += progressScopeSize;
    348     progressScopeSize = portion;
    349     progressScopeTime = now();
    350     progressScopeDuration = seconds;
    351     progress = 0;
    352     update_screen_locked();
    353     pthread_mutex_unlock(&updateMutex);
    354 }
    355 
    356 void WearRecoveryUI::SetProgress(float fraction)
    357 {
    358     pthread_mutex_lock(&updateMutex);
    359     if (fraction < 0.0) fraction = 0.0;
    360     if (fraction > 1.0) fraction = 1.0;
    361     if (progressBarType == DETERMINATE && fraction > progress) {
    362         // Skip updates that aren't visibly different.
    363         int width = progress_bar_width;
    364         float scale = width * progressScopeSize;
    365         if ((int) (progress * scale) != (int) (fraction * scale)) {
    366             progress = fraction;
    367             update_screen_locked();
    368         }
    369     }
    370     pthread_mutex_unlock(&updateMutex);
    371 }
    372 
    373 void WearRecoveryUI::SetStage(int current, int max)
    374 {
    375 }
    376 
    377 void WearRecoveryUI::Print(const char *fmt, ...)
    378 {
    379     char buf[256];
    380     va_list ap;
    381     va_start(ap, fmt);
    382     vsnprintf(buf, 256, fmt, ap);
    383     va_end(ap);
    384 
    385     fputs(buf, stdout);
    386 
    387     // This can get called before ui_init(), so be careful.
    388     pthread_mutex_lock(&updateMutex);
    389     if (text_rows > 0 && text_cols > 0) {
    390         char *ptr;
    391         for (ptr = buf; *ptr != '\0'; ++ptr) {
    392             if (*ptr == '\n' || text_col >= text_cols) {
    393                 text[text_row][text_col] = '\0';
    394                 text_col = 0;
    395                 text_row = (text_row + 1) % text_rows;
    396                 if (text_row == text_top) text_top = (text_top + 1) % text_rows;
    397             }
    398             if (*ptr != '\n') text[text_row][text_col++] = *ptr;
    399         }
    400         text[text_row][text_col] = '\0';
    401         update_screen_locked();
    402     }
    403     pthread_mutex_unlock(&updateMutex);
    404 }
    405 
    406 void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
    407                                  int initial_selection) {
    408     pthread_mutex_lock(&updateMutex);
    409     if (text_rows > 0 && text_cols > 0) {
    410         menu_headers_ = headers;
    411         size_t i = 0;
    412         // "i < text_rows" is removed from the loop termination condition,
    413         // which is different from the one in ScreenRecoveryUI::StartMenu().
    414         // Because WearRecoveryUI supports scrollable menu, it's fine to have
    415         // more entries than text_rows. The menu may be truncated otherwise.
    416         // Bug: 23752519
    417         for (; items[i] != nullptr; i++) {
    418             strncpy(menu[i], items[i], text_cols - 1);
    419             menu[i][text_cols - 1] = '\0';
    420         }
    421         menu_items = i;
    422         show_menu = 1;
    423         menu_sel = initial_selection;
    424         menu_start = 0;
    425         menu_end = visible_text_rows - 1 - menu_unusable_rows;
    426         if (menu_items <= menu_end)
    427           menu_end = menu_items;
    428         update_screen_locked();
    429     }
    430     pthread_mutex_unlock(&updateMutex);
    431 }
    432 
    433 int WearRecoveryUI::SelectMenu(int sel) {
    434     int old_sel;
    435     pthread_mutex_lock(&updateMutex);
    436     if (show_menu > 0) {
    437         old_sel = menu_sel;
    438         menu_sel = sel;
    439         if (menu_sel < 0) menu_sel = 0;
    440         if (menu_sel >= menu_items) menu_sel = menu_items-1;
    441         if (menu_sel < menu_start) {
    442           menu_start--;
    443           menu_end--;
    444         } else if (menu_sel >= menu_end && menu_sel < menu_items) {
    445           menu_end++;
    446           menu_start++;
    447         }
    448         sel = menu_sel;
    449         if (menu_sel != old_sel) update_screen_locked();
    450     }
    451     pthread_mutex_unlock(&updateMutex);
    452     return sel;
    453 }
    454 
    455 void WearRecoveryUI::EndMenu() {
    456     int i;
    457     pthread_mutex_lock(&updateMutex);
    458     if (show_menu > 0 && text_rows > 0 && text_cols > 0) {
    459         show_menu = 0;
    460         update_screen_locked();
    461     }
    462     pthread_mutex_unlock(&updateMutex);
    463 }
    464 
    465 bool WearRecoveryUI::IsTextVisible()
    466 {
    467     pthread_mutex_lock(&updateMutex);
    468     int visible = show_text;
    469     pthread_mutex_unlock(&updateMutex);
    470     return visible;
    471 }
    472 
    473 bool WearRecoveryUI::WasTextEverVisible()
    474 {
    475     pthread_mutex_lock(&updateMutex);
    476     int ever_visible = show_text_ever;
    477     pthread_mutex_unlock(&updateMutex);
    478     return ever_visible;
    479 }
    480 
    481 void WearRecoveryUI::ShowText(bool visible)
    482 {
    483     pthread_mutex_lock(&updateMutex);
    484     // Don't show text during ota install or factory reset
    485     if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
    486         pthread_mutex_unlock(&updateMutex);
    487         return;
    488     }
    489     show_text = visible;
    490     if (show_text) show_text_ever = 1;
    491     update_screen_locked();
    492     pthread_mutex_unlock(&updateMutex);
    493 }
    494 
    495 void WearRecoveryUI::Redraw()
    496 {
    497     pthread_mutex_lock(&updateMutex);
    498     update_screen_locked();
    499     pthread_mutex_unlock(&updateMutex);
    500 }
    501 
    502 void WearRecoveryUI::ShowFile(FILE* fp) {
    503     std::vector<long> offsets;
    504     offsets.push_back(ftell(fp));
    505     ClearText();
    506 
    507     struct stat sb;
    508     fstat(fileno(fp), &sb);
    509 
    510     bool show_prompt = false;
    511     while (true) {
    512         if (show_prompt) {
    513             Print("--(%d%% of %d bytes)--",
    514                   static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))),
    515                   static_cast<int>(sb.st_size));
    516             Redraw();
    517             while (show_prompt) {
    518                 show_prompt = false;
    519                 int key = WaitKey();
    520                 if (key == KEY_POWER || key == KEY_ENTER) {
    521                     return;
    522                 } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
    523                     if (offsets.size() <= 1) {
    524                         show_prompt = true;
    525                     } else {
    526                         offsets.pop_back();
    527                         fseek(fp, offsets.back(), SEEK_SET);
    528                     }
    529                 } else {
    530                     if (feof(fp)) {
    531                         return;
    532                     }
    533                     offsets.push_back(ftell(fp));
    534                 }
    535             }
    536             ClearText();
    537         }
    538 
    539         int ch = getc(fp);
    540         if (ch == EOF) {
    541             text_row = text_top = text_rows - 2;
    542             show_prompt = true;
    543         } else {
    544             PutChar(ch);
    545             if (text_col == 0 && text_row >= text_rows - 2) {
    546                 text_top = text_row;
    547                 show_prompt = true;
    548             }
    549         }
    550     }
    551 }
    552 
    553 void WearRecoveryUI::PutChar(char ch) {
    554     pthread_mutex_lock(&updateMutex);
    555     if (ch != '\n') text[text_row][text_col++] = ch;
    556     if (ch == '\n' || text_col >= text_cols) {
    557         text_col = 0;
    558         ++text_row;
    559     }
    560     pthread_mutex_unlock(&updateMutex);
    561 }
    562 
    563 void WearRecoveryUI::ShowFile(const char* filename) {
    564     FILE* fp = fopen_path(filename, "re");
    565     if (fp == nullptr) {
    566         Print("  Unable to open %s: %s\n", filename, strerror(errno));
    567         return;
    568     }
    569     ShowFile(fp);
    570     fclose(fp);
    571 }
    572 
    573 void WearRecoveryUI::ClearText() {
    574     pthread_mutex_lock(&updateMutex);
    575     text_col = 0;
    576     text_row = 0;
    577     text_top = 1;
    578     for (size_t i = 0; i < text_rows; ++i) {
    579         memset(text[i], 0, text_cols + 1);
    580     }
    581     pthread_mutex_unlock(&updateMutex);
    582 }
    583 
    584 void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
    585     va_list ap;
    586     va_start(ap, fmt);
    587     PrintV(fmt, false, ap);
    588     va_end(ap);
    589 }
    590 
    591 void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
    592     std::string str;
    593     android::base::StringAppendV(&str, fmt, ap);
    594 
    595     if (copy_to_stdout) {
    596         fputs(str.c_str(), stdout);
    597     }
    598 
    599     pthread_mutex_lock(&updateMutex);
    600     if (text_rows > 0 && text_cols > 0) {
    601         for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
    602             if (*ptr == '\n' || text_col >= text_cols) {
    603                 text[text_row][text_col] = '\0';
    604                 text_col = 0;
    605                 text_row = (text_row + 1) % text_rows;
    606                 if (text_row == text_top) text_top = (text_top + 1) % text_rows;
    607             }
    608             if (*ptr != '\n') text[text_row][text_col++] = *ptr;
    609         }
    610         text[text_row][text_col] = '\0';
    611         update_screen_locked();
    612     }
    613     pthread_mutex_unlock(&updateMutex);
    614 }
    615