• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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).
now()47 static double now() {
48   struct timeval tv;
49   gettimeofday(&tv, nullptr);
50   return tv.tv_sec + tv.tv_usec / 1000000.0;
51 }
52 
ScreenRecoveryUI()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 
GetCurrentFrame() const85 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 
GetCurrentText() const92 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 
PixelsFromDp(int dp) const107 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 
GetAnimationBaseline() const135 int ScreenRecoveryUI::GetAnimationBaseline() const {
136   return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]);
137 }
138 
GetTextBaseline() const139 int ScreenRecoveryUI::GetTextBaseline() const {
140   return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
141          gr_get_height(installing_text);
142 }
143 
GetProgressBaseline() const144 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.
draw_background_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.
draw_foreground_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 
SetColor(UIElement e) const229 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 
DrawHorizontalRule(int y) const259 int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
260   gr_fill(0, y + 4, gr_fb_width(), y + 6);
261   return 8;
262 }
263 
DrawHighlightBar(int x,int y,int width,int height) const264 void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
265   gr_fill(x, y, x + width, y + height);
266 }
267 
DrawTextLine(int x,int y,const char * line,bool bold) const268 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 
DrawTextLines(int x,int y,const char * const * lines) const273 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 
DrawWrappedTextLines(int x,int y,const char * const * lines) const281 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.
draw_screen_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.
update_screen_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.
update_progress_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.
ProgressThreadStartRoutine(void * data)401 void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) {
402   reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop();
403   return nullptr;
404 }
405 
ProgressThreadLoop()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 
LoadBitmap(const char * filename,GRSurface ** surface)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 
LoadLocalizedBitmap(const char * filename,GRSurface ** surface)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 
Alloc2d(size_t rows,size_t cols)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.
SetSystemUpdateText(bool security_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 
InitTextParams()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 
Init(const std::string & locale)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 
LoadAnimation()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 
SetBackground(Icon icon)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 
SetProgressType(ProgressType type)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 
ShowProgress(float portion,float seconds)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 
SetProgress(float fraction)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 
SetStage(int current,int max)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 
PrintV(const char * fmt,bool copy_to_stdout,va_list ap)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 
Print(const char * fmt,...)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 
PrintOnScreenOnly(const char * fmt,...)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 
PutChar(char ch)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 
ClearText()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 
ShowFile(FILE * fp)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 
ShowFile(const char * filename)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 
StartMenu(const char * const * headers,const char * const * items,int initial_selection)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 
SelectMenu(int sel)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 
EndMenu()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 
IsTextVisible()811 bool ScreenRecoveryUI::IsTextVisible() {
812   pthread_mutex_lock(&updateMutex);
813   int visible = show_text;
814   pthread_mutex_unlock(&updateMutex);
815   return visible;
816 }
817 
WasTextEverVisible()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 
ShowText(bool visible)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 
Redraw()833 void ScreenRecoveryUI::Redraw() {
834   pthread_mutex_lock(&updateMutex);
835   update_screen_locked();
836   pthread_mutex_unlock(&updateMutex);
837 }
838 
KeyLongPress(int)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