screen_ui.cpp 24.6 KB
Newer Older
1
/*
2
 * Copyright (C) 2011 The Android Open Source Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

17
#include <dirent.h>
18 19
#include <errno.h>
#include <fcntl.h>
20 21 22 23 24 25
#include <linux/input.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
26
#include <sys/stat.h>
27
#include <sys/time.h>
28
#include <sys/types.h>
29 30 31
#include <time.h>
#include <unistd.h>

Tao Bao's avatar
Tao Bao committed
32
#include <string>
Elliott Hughes's avatar
Elliott Hughes committed
33 34
#include <vector>

35
#include <android-base/logging.h>
36
#include <android-base/properties.h>
37 38
#include <android-base/strings.h>
#include <android-base/stringprintf.h>
Tao Bao's avatar
Tao Bao committed
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 47
// Return the current time as a double (including fractions of a second).
static double now() {
48 49 50
  struct timeval tv;
  gettimeofday(&tv, nullptr);
  return tv.tv_sec + tv.tv_usec / 1000000.0;
51 52
}

Tao Bao's avatar
Tao Bao committed
53
ScreenRecoveryUI::ScreenRecoveryUI()
54 55
    : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH),
      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),
Tao Bao's avatar
Tao Bao committed
59 60 61 62 63 64 65 66 67 68 69 70 71
      progressBarType(EMPTY),
      progressScopeStart(0),
      progressScopeSize(0),
      progress(0),
      pagesIdentical(false),
      text_cols_(0),
      text_rows_(0),
      text_(nullptr),
      text_col_(0),
      text_row_(0),
      text_top_(0),
      show_text(false),
      show_text_ever(false),
Tao Bao's avatar
Tao Bao committed
72
      menu_headers_(nullptr),
Tao Bao's avatar
Tao Bao committed
73 74 75 76 77 78 79 80 81 82 83
      show_menu(false),
      menu_items(0),
      menu_sel(0),
      file_viewer_text_(nullptr),
      intro_frames(0),
      loop_frames(0),
      current_frame(0),
      intro_done(false),
      stage(-1),
      max_stage(-1),
      updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
84

85
GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
86 87 88 89
  if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
    return intro_done ? loopFrames[current_frame] : introFrames[current_frame];
  }
  return error_icon;
90 91
}

92
GRSurface* ScreenRecoveryUI::GetCurrentText() const {
93 94 95 96 97 98 99 100 101 102 103 104
  switch (currentIcon) {
    case ERASING:
      return erasing_text;
    case ERROR:
      return error_text;
    case INSTALLING_UPDATE:
      return installing_text;
    case NO_COMMAND:
      return no_command_text;
    case NONE:
      abort();
  }
105 106
}

Mikhail Lappo's avatar
Mikhail Lappo committed
107
int ScreenRecoveryUI::PixelsFromDp(int dp) const {
108
  return dp * density_;
Elliott Hughes's avatar
Elliott Hughes committed
109 110 111 112
}

// Here's the intended layout:

Elliott Hughes's avatar
Elliott Hughes committed
113 114
//          | portrait    large        landscape      large
// ---------+-------------------------------------------------
115
//      gap |
Elliott Hughes's avatar
Elliott Hughes committed
116 117 118 119 120
// icon     |                   (200dp)
//      gap |    68dp      68dp             56dp      112dp
// text     |                    (14sp)
//      gap |    32dp      32dp             26dp       52dp
// progress |                     (2dp)
121
//      gap |
Elliott Hughes's avatar
Elliott Hughes committed
122

123 124
// Note that "baseline" is actually the *top* of each icon (because that's how our drawing routines
// work), so that's the more useful measurement for calling code. We use even top and bottom gaps.
Elliott Hughes's avatar
Elliott Hughes committed
125

Elliott Hughes's avatar
Elliott Hughes committed
126
enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX };
127
enum Dimension { TEXT = 0, ICON = 1, DIMENSION_MAX };
Elliott Hughes's avatar
Elliott Hughes committed
128
static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = {
129 130 131 132
  { 32,  68, },  // PORTRAIT
  { 32,  68, },  // PORTRAIT_LARGE
  { 26,  56, },  // LANDSCAPE
  { 52, 112, },  // LANDSCAPE_LARGE
Elliott Hughes's avatar
Elliott Hughes committed
133 134
};

135
int ScreenRecoveryUI::GetAnimationBaseline() const {
136
  return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]);
Elliott Hughes's avatar
Elliott Hughes committed
137 138
}

139
int ScreenRecoveryUI::GetTextBaseline() const {
140 141
  return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
         gr_get_height(installing_text);
Elliott Hughes's avatar
Elliott Hughes committed
142 143
}

144
int ScreenRecoveryUI::GetProgressBaseline() const {
145 146 147 148 149
  int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) +
                     gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) +
                     gr_get_height(progressBarFill);
  int bottom_gap = (gr_fb_height() - elements_sum) / 2;
  return gr_fb_height() - bottom_gap - gr_get_height(progressBarFill);
Elliott Hughes's avatar
Elliott Hughes committed
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 156 157
  pagesIdentical = false;
  gr_color(0, 0, 0, 255);
  gr_clear();
158

159 160 161 162 163 164 165 166 167 168 169
  if (currentIcon != NONE) {
    if (max_stage != -1) {
      int stage_height = gr_get_height(stageMarkerEmpty);
      int stage_width = gr_get_width(stageMarkerEmpty);
      int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
      int y = gr_fb_height() - stage_height;
      for (int i = 0; i < max_stage; ++i) {
        GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
        gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y);
        x += stage_width;
      }
170
    }
171 172 173 174 175 176 177

    GRSurface* text_surface = GetCurrentText();
    int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2;
    int text_y = GetTextBaseline();
    gr_color(255, 255, 255, 255);
    gr_texticon(text_x, text_y, text_surface);
  }
178 179
}

180 181
// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
// called with updateMutex locked.
Elliott Hughes's avatar
Elliott Hughes committed
182
void ScreenRecoveryUI::draw_foreground_locked() {
Tao Bao's avatar
Tao Bao committed
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
  if (currentIcon != NONE) {
    GRSurface* frame = GetCurrentFrame();
    int frame_width = gr_get_width(frame);
    int frame_height = gr_get_height(frame);
    int frame_x = (gr_fb_width() - frame_width) / 2;
    int frame_y = GetAnimationBaseline();
    gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
  }

  if (progressBarType != EMPTY) {
    int width = gr_get_width(progressBarEmpty);
    int height = gr_get_height(progressBarEmpty);

    int progress_x = (gr_fb_width() - width) / 2;
    int progress_y = GetProgressBaseline();

    // Erase behind the progress bar (in case this was a progress-only update)
    gr_color(0, 0, 0, 255);
    gr_fill(progress_x, progress_y, width, height);
202

Tao Bao's avatar
Tao Bao committed
203 204 205
    if (progressBarType == DETERMINATE) {
      float p = progressScopeStart + progress * progressScopeSize;
      int pos = static_cast<int>(p * width);
206

Tao Bao's avatar
Tao Bao committed
207 208 209 210 211 212 213 214 215 216 217 218 219
      if (rtl_locale_) {
        // Fill the progress bar from right to left.
        if (pos > 0) {
          gr_blit(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
                  progress_y);
        }
        if (pos < width - 1) {
          gr_blit(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
        }
      } else {
        // Fill the progress bar from left to right.
        if (pos > 0) {
          gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
220
        }
Tao Bao's avatar
Tao Bao committed
221 222 223 224
        if (pos < width - 1) {
          gr_blit(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
        }
      }
225
    }
Tao Bao's avatar
Tao Bao committed
226
  }
227 228
}

229
void ScreenRecoveryUI::SetColor(UIElement e) const {
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
  switch (e) {
    case INFO:
      gr_color(249, 194, 0, 255);
      break;
    case HEADER:
      gr_color(247, 0, 6, 255);
      break;
    case MENU:
    case MENU_SEL_BG:
      gr_color(0, 106, 157, 255);
      break;
    case MENU_SEL_BG_ACTIVE:
      gr_color(0, 156, 100, 255);
      break;
    case MENU_SEL_FG:
      gr_color(255, 255, 255, 255);
      break;
    case LOG:
      gr_color(196, 196, 196, 255);
      break;
    case TEXT_FILL:
      gr_color(0, 0, 0, 160);
      break;
    default:
      gr_color(255, 255, 255, 255);
      break;
  }
257
}
258

259 260 261
int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
  gr_fill(0, y + 4, gr_fb_width(), y + 6);
  return 8;
262 263
}

Luke Song's avatar
Luke Song committed
264
void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
265
  gr_fill(x, y, x + width, y + height);
Luke Song's avatar
Luke Song committed
266 267
}

268 269 270
int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
  gr_text(gr_sys_font(), x, y, line, bold);
  return char_height_ + 4;
Elliott Hughes's avatar
Elliott Hughes committed
271 272
}

273 274
int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) const {
  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
}

Tao Bao's avatar
Tao Bao committed
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const {
  int offset = 0;
  for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
    // The line will be wrapped if it exceeds text_cols_.
    std::string line(lines[i]);
    size_t next_start = 0;
    while (next_start < line.size()) {
      std::string sub = line.substr(next_start, text_cols_ + 1);
      if (sub.size() <= text_cols_) {
        next_start += sub.size();
      } else {
        // Line too long and must be wrapped to text_cols_ columns.
        size_t last_space = sub.find_last_of(" \t\n");
        if (last_space == std::string::npos) {
          // No space found, just draw as much as we can
          sub.resize(text_cols_);
          next_start += text_cols_;
        } else {
          sub.resize(last_space);
          next_start += last_space + 1;
        }
      }
      offset += DrawTextLine(x, y + offset, sub.c_str(), false);
    }
  }
  return offset;
}

309
static const char* REGULAR_HELP[] = {
310 311
  "Use volume up/down and power.",
  NULL
312 313 314
};

static const char* LONG_PRESS_HELP[] = {
315 316 317
  "Any button cycles highlight.",
  "Long-press activates.",
  NULL
318 319
};

320 321
// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
// locked.
322
void ScreenRecoveryUI::draw_screen_locked() {
323 324 325 326 327
  if (!show_text) {
    draw_background_locked();
    draw_foreground_locked();
    return;
  }
328

329 330 331
  gr_color(0, 0, 0, 255);
  gr_clear();

332
  int y = kMarginHeight;
333
  if (show_menu) {
334 335
    static constexpr int kMenuIndent = 4;
    int x = kMarginWidth + kMenuIndent;
336 337

    SetColor(INFO);
338
    y += DrawTextLine(x, y, "Android Recovery", true);
339 340
    std::string recovery_fingerprint =
        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 348
    // Ignore kMenuIndent, which is not taken into account by text_cols_.
    y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_);
349 350

    SetColor(MENU);
351
    y += DrawHorizontalRule(y) + 4;
352 353 354 355 356 357 358
    for (int i = 0; i < menu_items; ++i) {
      if (i == menu_sel) {
        // Draw the highlight bar.
        SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG);
        DrawHighlightBar(0, y - 2, gr_fb_width(), char_height_ + 4);
        // Bold white text for the selected item.
        SetColor(MENU_SEL_FG);
Tao Bao's avatar
Tao Bao committed
359
        y += DrawTextLine(x, y, menu_[i].c_str(), true);
360 361
        SetColor(MENU);
      } else {
Tao Bao's avatar
Tao Bao committed
362
        y += DrawTextLine(x, y, menu_[i].c_str(), false);
363 364
      }
    }
365
    y += DrawHorizontalRule(y);
366 367 368 369 370 371 372
  }

  // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
  // we've displayed the entire text buffer.
  SetColor(LOG);
  int row = (text_top_ + text_rows_ - 1) % text_rows_;
  size_t count = 0;
373 374
  for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
       ty -= char_height_, ++count) {
375
    DrawTextLine(kMarginWidth, ty, text_[row], false);
376 377 378
    --row;
    if (row < 0) row = text_rows_ - 1;
  }
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 385
  draw_screen_locked();
  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 392 393 394 395 396 397
  if (show_text || !pagesIdentical) {
    draw_screen_locked();  // Must redraw the whole screen
    pagesIdentical = true;
  } else {
    draw_foreground_locked();  // Draw only the progress bar and overlays
  }
  gr_flip();
398 399 400
}

// Keeps the progress bar updated, even when the process is otherwise busy.
Elliott Hughes's avatar
Elliott Hughes committed
401
void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) {
402 403
  reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop();
  return nullptr;
404 405
}

Elliott Hughes's avatar
Elliott Hughes committed
406
void ScreenRecoveryUI::ProgressThreadLoop() {
407
  double interval = 1.0 / kAnimationFps;
408 409 410
  while (true) {
    double start = now();
    pthread_mutex_lock(&updateMutex);
411

412 413 414 415 416 417 418 419 420 421 422
    bool redraw = false;

    // update the installation animation, if active
    // skip this if we have a text overlay (too expensive to update)
    if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) {
      if (!intro_done) {
        if (current_frame == intro_frames - 1) {
          intro_done = true;
          current_frame = 0;
        } else {
          ++current_frame;
423
        }
424 425 426
      } else {
        current_frame = (current_frame + 1) % loop_frames;
      }
427

428 429
      redraw = true;
    }
430

431 432 433 434 435 436 437 438 439 440
    // move the progress bar forward on timed intervals, if configured
    int duration = progressScopeDuration;
    if (progressBarType == DETERMINATE && duration > 0) {
      double elapsed = now() - progressScopeTime;
      float p = 1.0 * elapsed / duration;
      if (p > 1.0) p = 1.0;
      if (p > progress) {
        progress = p;
        redraw = true;
      }
441
    }
442 443 444 445 446 447 448 449 450 451

    if (redraw) update_progress_locked();

    pthread_mutex_unlock(&updateMutex);
    double end = now();
    // minimum of 20ms delay between frames
    double delay = interval - (end - start);
    if (delay < 0.02) delay = 0.02;
    usleep(static_cast<useconds_t>(delay * 1000000));
  }
452 453
}

454
void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
455 456 457 458
  int result = res_create_display_surface(filename, surface);
  if (result < 0) {
    LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
  }
459 460
}

461
void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
Tao Bao's avatar
Tao Bao committed
462 463 464 465
  int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
  if (result < 0) {
    LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
  }
466 467
}

468
static char** Alloc2d(size_t rows, size_t cols) {
469 470 471 472 473 474
  char** result = new char*[rows];
  for (size_t i = 0; i < rows; ++i) {
    result[i] = new char[cols];
    memset(result[i], 0, cols);
  }
  return result;
475 476
}

477 478
// Choose the right background string to display during update.
void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
479 480 481 482 483 484
  if (security_update) {
    LoadLocalizedBitmap("installing_security_text", &installing_text);
  } else {
    LoadLocalizedBitmap("installing_text", &installing_text);
  }
  Redraw();
485 486
}

Sen Jiang's avatar
Sen Jiang committed
487
bool ScreenRecoveryUI::InitTextParams() {
488 489 490
  if (gr_init() < 0) {
    return false;
  }
491

492
  gr_font_size(gr_sys_font(), &char_width_, &char_height_);
493 494
  text_rows_ = (gr_fb_height() - kMarginHeight * 2) / char_height_;
  text_cols_ = (gr_fb_width() - kMarginWidth * 2) / char_width_;
495
  return true;
496 497
}

Tao Bao's avatar
Tao Bao committed
498 499 500 501 502
bool ScreenRecoveryUI::Init(const std::string& locale) {
  RecoveryUI::Init(locale);
  if (!InitTextParams()) {
    return false;
  }
503

Tao Bao's avatar
Tao Bao committed
504 505 506 507
  // Are we portrait or landscape?
  layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
  // Are we the large variant of our base layout?
  if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
Elliott Hughes's avatar
Elliott Hughes committed
508

Tao Bao's avatar
Tao Bao committed
509 510
  text_ = Alloc2d(text_rows_, text_cols_ + 1);
  file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
511

Tao Bao's avatar
Tao Bao committed
512 513
  text_col_ = text_row_ = 0;
  text_top_ = 1;
514

Tao Bao's avatar
Tao Bao committed
515
  LoadBitmap("icon_error", &error_icon);
516

Tao Bao's avatar
Tao Bao committed
517 518
  LoadBitmap("progress_empty", &progressBarEmpty);
  LoadBitmap("progress_fill", &progressBarFill);
519

Tao Bao's avatar
Tao Bao committed
520 521
  LoadBitmap("stage_empty", &stageMarkerEmpty);
  LoadBitmap("stage_fill", &stageMarkerFill);
522

Tao Bao's avatar
Tao Bao committed
523 524 525 526 527 528 529
  // Background text for "installing_update" could be "installing update"
  // or "installing security update". It will be set after UI init according
  // to commands in BCB.
  installing_text = nullptr;
  LoadLocalizedBitmap("erasing_text", &erasing_text);
  LoadLocalizedBitmap("no_command_text", &no_command_text);
  LoadLocalizedBitmap("error_text", &error_text);
530

Tao Bao's avatar
Tao Bao committed
531
  LoadAnimation();
532

Tao Bao's avatar
Tao Bao committed
533
  pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
Sen Jiang's avatar
Sen Jiang committed
534

Tao Bao's avatar
Tao Bao committed
535
  return true;
536 537
}

538
void ScreenRecoveryUI::LoadAnimation() {
539 540 541 542 543 544 545 546 547 548 549
  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir);
  dirent* de;
  std::vector<std::string> intro_frame_names;
  std::vector<std::string> loop_frame_names;

  while ((de = readdir(dir.get())) != nullptr) {
    int value, num_chars;
    if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
      intro_frame_names.emplace_back(de->d_name, num_chars);
    } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
      loop_frame_names.emplace_back(de->d_name, num_chars);
550
    }
551
  }
552

553 554
  intro_frames = intro_frame_names.size();
  loop_frames = loop_frame_names.size();
555

556 557 558 559
  // It's okay to not have an intro.
  if (intro_frames == 0) intro_done = true;
  // But you must have an animation.
  if (loop_frames == 0) abort();
560

561 562
  std::sort(intro_frame_names.begin(), intro_frame_names.end());
  std::sort(loop_frame_names.begin(), loop_frame_names.end());
563

564 565 566 567
  introFrames = new GRSurface*[intro_frames];
  for (size_t i = 0; i < intro_frames; i++) {
    LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]);
  }
568

569 570 571 572
  loopFrames = new GRSurface*[loop_frames];
  for (size_t i = 0; i < loop_frames; i++) {
    LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]);
  }
573 574
}

575
void ScreenRecoveryUI::SetBackground(Icon icon) {
576
  pthread_mutex_lock(&updateMutex);
577

578 579
  currentIcon = icon;
  update_screen_locked();
Doug Zongker's avatar
Doug Zongker committed
580

581
  pthread_mutex_unlock(&updateMutex);
582 583
}

584
void ScreenRecoveryUI::SetProgressType(ProgressType type) {
585 586 587 588 589 590 591 592 593
  pthread_mutex_lock(&updateMutex);
  if (progressBarType != type) {
    progressBarType = type;
  }
  progressScopeStart = 0;
  progressScopeSize = 0;
  progress = 0;
  update_progress_locked();
  pthread_mutex_unlock(&updateMutex);
594 595
}

596
void ScreenRecoveryUI::ShowProgress(float portion, float seconds) {
597 598 599 600 601 602 603 604 605
  pthread_mutex_lock(&updateMutex);
  progressBarType = DETERMINATE;
  progressScopeStart += progressScopeSize;
  progressScopeSize = portion;
  progressScopeTime = now();
  progressScopeDuration = seconds;
  progress = 0;
  update_progress_locked();
  pthread_mutex_unlock(&updateMutex);
606 607
}

608
void ScreenRecoveryUI::SetProgress(float fraction) {
609 610 611 612 613 614 615 616 617 618
  pthread_mutex_lock(&updateMutex);
  if (fraction < 0.0) fraction = 0.0;
  if (fraction > 1.0) fraction = 1.0;
  if (progressBarType == DETERMINATE && fraction > progress) {
    // Skip updates that aren't visibly different.
    int width = gr_get_width(progressBarEmpty);
    float scale = width * progressScopeSize;
    if ((int)(progress * scale) != (int)(fraction * scale)) {
      progress = fraction;
      update_progress_locked();
619
    }
620 621
  }
  pthread_mutex_unlock(&updateMutex);
622 623
}

624
void ScreenRecoveryUI::SetStage(int current, int max) {
625 626 627 628
  pthread_mutex_lock(&updateMutex);
  stage = current;
  max_stage = max;
  pthread_mutex_unlock(&updateMutex);
629 630
}

Tao Bao's avatar
Tao Bao committed
631
void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
632 633
  std::string str;
  android::base::StringAppendV(&str, fmt, ap);
634

635 636 637
  if (copy_to_stdout) {
    fputs(str.c_str(), stdout);
  }
638

639 640 641 642
  pthread_mutex_lock(&updateMutex);
  if (text_rows_ > 0 && text_cols_ > 0) {
    for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
      if (*ptr == '\n' || text_col_ >= text_cols_) {
643
        text_[text_row_][text_col_] = '\0';
644 645 646 647 648
        text_col_ = 0;
        text_row_ = (text_row_ + 1) % text_rows_;
        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
      }
      if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
649
    }
650 651 652 653
    text_[text_row_][text_col_] = '\0';
    update_screen_locked();
  }
  pthread_mutex_unlock(&updateMutex);
654 655
}

Tao Bao's avatar
Tao Bao committed
656
void ScreenRecoveryUI::Print(const char* fmt, ...) {
657 658 659 660
  va_list ap;
  va_start(ap, fmt);
  PrintV(fmt, true, ap);
  va_end(ap);
Tao Bao's avatar
Tao Bao committed
661 662 663
}

void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
664 665 666 667
  va_list ap;
  va_start(ap, fmt);
  PrintV(fmt, false, ap);
  va_end(ap);
Tao Bao's avatar
Tao Bao committed
668 669
}

Elliott Hughes's avatar
Elliott Hughes committed
670
void ScreenRecoveryUI::PutChar(char ch) {
671 672 673 674 675
  pthread_mutex_lock(&updateMutex);
  if (ch != '\n') text_[text_row_][text_col_++] = ch;
  if (ch == '\n' || text_col_ >= text_cols_) {
    text_col_ = 0;
    ++text_row_;
676

677 678 679
    if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
  }
  pthread_mutex_unlock(&updateMutex);
680 681
}

Elliott Hughes's avatar
Elliott Hughes committed
682
void ScreenRecoveryUI::ClearText() {
683 684 685 686 687 688 689 690
  pthread_mutex_lock(&updateMutex);
  text_col_ = 0;
  text_row_ = 0;
  text_top_ = 1;
  for (size_t i = 0; i < text_rows_; ++i) {
    memset(text_[i], 0, text_cols_ + 1);
  }
  pthread_mutex_unlock(&updateMutex);
Elliott Hughes's avatar
Elliott Hughes committed
691
}
692

Elliott Hughes's avatar
Elliott Hughes committed
693
void ScreenRecoveryUI::ShowFile(FILE* fp) {
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
  std::vector<off_t> offsets;
  offsets.push_back(ftello(fp));
  ClearText();

  struct stat sb;
  fstat(fileno(fp), &sb);

  bool show_prompt = false;
  while (true) {
    if (show_prompt) {
      PrintOnScreenOnly("--(%d%% of %d bytes)--",
                        static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
                        static_cast<int>(sb.st_size));
      Redraw();
      while (show_prompt) {
        show_prompt = false;
        int key = WaitKey();
        if (key == KEY_POWER || key == KEY_ENTER) {
          return;
        } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
          if (offsets.size() <= 1) {
Elliott Hughes's avatar
Elliott Hughes committed
715
            show_prompt = true;
716 717 718 719
          } else {
            offsets.pop_back();
            fseek(fp, offsets.back(), SEEK_SET);
          }
Elliott Hughes's avatar
Elliott Hughes committed
720
        } else {
721 722 723 724
          if (feof(fp)) {
            return;
          }
          offsets.push_back(ftello(fp));
725
        }
726 727
      }
      ClearText();
728
    }
729 730 731 732 733 734 735 736 737 738 739 740

    int ch = getc(fp);
    if (ch == EOF) {
      while (text_row_ < text_rows_ - 1) PutChar('\n');
      show_prompt = true;
    } else {
      PutChar(ch);
      if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
        show_prompt = true;
      }
    }
  }
Elliott Hughes's avatar
Elliott Hughes committed
741
}
742

Elliott Hughes's avatar
Elliott Hughes committed
743
void ScreenRecoveryUI::ShowFile(const char* filename) {
744 745 746 747 748
  FILE* fp = fopen_path(filename, "re");
  if (fp == nullptr) {
    Print("  Unable to open %s: %s\n", filename, strerror(errno));
    return;
  }
749

750 751 752 753
  char** old_text = text_;
  size_t old_text_col = text_col_;
  size_t old_text_row = text_row_;
  size_t old_text_top = text_top_;
754

755 756 757
  // Swap in the alternate screen and clear it.
  text_ = file_viewer_text_;
  ClearText();
758

759 760
  ShowFile(fp);
  fclose(fp);
761

762 763 764 765
  text_ = old_text;
  text_col_ = old_text_col;
  text_row_ = old_text_row;
  text_top_ = old_text_top;
766 767
}

768
void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
769
                                 int initial_selection) {
770 771 772
  pthread_mutex_lock(&updateMutex);
  if (text_rows_ > 0 && text_cols_ > 0) {
    menu_headers_ = headers;
Tao Bao's avatar
Tao Bao committed
773 774 775
    menu_.clear();
    for (size_t i = 0; i < text_rows_ && items[i] != nullptr; ++i) {
      menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1)));
776
    }
Tao Bao's avatar
Tao Bao committed
777
    menu_items = static_cast<int>(menu_.size());
778 779 780 781 782
    show_menu = true;
    menu_sel = initial_selection;
    update_screen_locked();
  }
  pthread_mutex_unlock(&updateMutex);
783 784
}

785
int ScreenRecoveryUI::SelectMenu(int sel) {
786 787 788 789
  pthread_mutex_lock(&updateMutex);
  if (show_menu) {
    int old_sel = menu_sel;
    menu_sel = sel;
790

791 792 793
    // Wrap at top and bottom.
    if (menu_sel < 0) menu_sel = menu_items - 1;
    if (menu_sel >= menu_items) menu_sel = 0;
794

795 796 797 798 799
    sel = menu_sel;
    if (menu_sel != old_sel) update_screen_locked();
  }
  pthread_mutex_unlock(&updateMutex);
  return sel;
800 801
}

802
void ScreenRecoveryUI::EndMenu() {
803 804 805 806 807 808
  pthread_mutex_lock(&updateMutex);
  if (show_menu && text_rows_ > 0 && text_cols_ > 0) {
    show_menu = false;
    update_screen_locked();
  }
  pthread_mutex_unlock(&updateMutex);
809 810
}

811
bool ScreenRecoveryUI::IsTextVisible() {
812 813 814 815
  pthread_mutex_lock(&updateMutex);
  int visible = show_text;
  pthread_mutex_unlock(&updateMutex);
  return visible;
816 817
}

818
bool ScreenRecoveryUI::WasTextEverVisible() {
819 820 821 822
  pthread_mutex_lock(&updateMutex);
  int ever_visible = show_text_ever;
  pthread_mutex_unlock(&updateMutex);
  return ever_visible;
823 824
}

825
void ScreenRecoveryUI::ShowText(bool visible) {
826 827 828 829 830
  pthread_mutex_lock(&updateMutex);
  show_text = visible;
  if (show_text) show_text_ever = true;
  update_screen_locked();
  pthread_mutex_unlock(&updateMutex);
831
}
832

833
void ScreenRecoveryUI::Redraw() {
834 835 836
  pthread_mutex_lock(&updateMutex);
  update_screen_locked();
  pthread_mutex_unlock(&updateMutex);
837
}
838 839

void ScreenRecoveryUI::KeyLongPress(int) {
840 841 842
  // Redraw so that if we're in the menu, the highlight
  // will change color to indicate a successful long press.
  Redraw();
843
}