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 #define TEXT_INDENT     4
47 
48 // Return the current time as a double (including fractions of a second).
now()49 static double now() {
50     struct timeval tv;
51     gettimeofday(&tv, nullptr);
52     return tv.tv_sec + tv.tv_usec / 1000000.0;
53 }
54 
ScreenRecoveryUI()55 ScreenRecoveryUI::ScreenRecoveryUI()
56     : currentIcon(NONE),
57       progressBarType(EMPTY),
58       progressScopeStart(0),
59       progressScopeSize(0),
60       progress(0),
61       pagesIdentical(false),
62       text_cols_(0),
63       text_rows_(0),
64       text_(nullptr),
65       text_col_(0),
66       text_row_(0),
67       text_top_(0),
68       show_text(false),
69       show_text_ever(false),
70       menu_(nullptr),
71       show_menu(false),
72       menu_items(0),
73       menu_sel(0),
74       file_viewer_text_(nullptr),
75       intro_frames(0),
76       loop_frames(0),
77       current_frame(0),
78       intro_done(false),
79       animation_fps(30),  // TODO: there's currently no way to infer this.
80       stage(-1),
81       max_stage(-1),
82       updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
83 
GetCurrentFrame()84 GRSurface* ScreenRecoveryUI::GetCurrentFrame() {
85     if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
86         return intro_done ? loopFrames[current_frame] : introFrames[current_frame];
87     }
88     return error_icon;
89 }
90 
GetCurrentText()91 GRSurface* ScreenRecoveryUI::GetCurrentText() {
92     switch (currentIcon) {
93         case ERASING: return erasing_text;
94         case ERROR: return error_text;
95         case INSTALLING_UPDATE: return installing_text;
96         case NO_COMMAND: return no_command_text;
97         case NONE: abort();
98     }
99 }
100 
PixelsFromDp(int dp) const101 int ScreenRecoveryUI::PixelsFromDp(int dp) const {
102     return dp * density_;
103 }
104 
105 // Here's the intended layout:
106 
107 //          | portrait    large        landscape      large
108 // ---------+-------------------------------------------------
109 //      gap |   220dp     366dp            142dp      284dp
110 // icon     |                   (200dp)
111 //      gap |    68dp      68dp             56dp      112dp
112 // text     |                    (14sp)
113 //      gap |    32dp      32dp             26dp       52dp
114 // progress |                     (2dp)
115 //      gap |   194dp     340dp            131dp      262dp
116 
117 // Note that "baseline" is actually the *top* of each icon (because that's how our drawing
118 // routines work), so that's the more useful measurement for calling code.
119 
120 enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX };
121 enum Dimension { PROGRESS = 0, TEXT = 1, ICON = 2, DIMENSION_MAX };
122 static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = {
123     { 194,  32,  68, }, // PORTRAIT
124     { 340,  32,  68, }, // PORTRAIT_LARGE
125     { 131,  26,  56, }, // LANDSCAPE
126     { 262,  52, 112, }, // LANDSCAPE_LARGE
127 };
128 
GetAnimationBaseline()129 int ScreenRecoveryUI::GetAnimationBaseline() {
130     return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
131             gr_get_height(loopFrames[0]);
132 }
133 
GetTextBaseline()134 int ScreenRecoveryUI::GetTextBaseline() {
135     return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
136             gr_get_height(installing_text);
137 }
138 
GetProgressBaseline()139 int ScreenRecoveryUI::GetProgressBaseline() {
140     return gr_fb_height() - PixelsFromDp(kLayouts[layout_][PROGRESS]) -
141             gr_get_height(progressBarFill);
142 }
143 
144 // Clear the screen and draw the currently selected background icon (if any).
145 // Should only be called with updateMutex locked.
draw_background_locked()146 void ScreenRecoveryUI::draw_background_locked() {
147     pagesIdentical = false;
148     gr_color(0, 0, 0, 255);
149     gr_clear();
150 
151     if (currentIcon != NONE) {
152         if (max_stage != -1) {
153             int stage_height = gr_get_height(stageMarkerEmpty);
154             int stage_width = gr_get_width(stageMarkerEmpty);
155             int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
156             int y = gr_fb_height() - stage_height;
157             for (int i = 0; i < max_stage; ++i) {
158                 GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
159                 gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y);
160                 x += stage_width;
161             }
162         }
163 
164         GRSurface* text_surface = GetCurrentText();
165         int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2;
166         int text_y = GetTextBaseline();
167         gr_color(255, 255, 255, 255);
168         gr_texticon(text_x, text_y, text_surface);
169     }
170 }
171 
172 // Draws the animation and progress bar (if any) on the screen.
173 // Does not flip pages.
174 // Should only be called with updateMutex locked.
draw_foreground_locked()175 void ScreenRecoveryUI::draw_foreground_locked() {
176   if (currentIcon != NONE) {
177     GRSurface* frame = GetCurrentFrame();
178     int frame_width = gr_get_width(frame);
179     int frame_height = gr_get_height(frame);
180     int frame_x = (gr_fb_width() - frame_width) / 2;
181     int frame_y = GetAnimationBaseline();
182     gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
183   }
184 
185   if (progressBarType != EMPTY) {
186     int width = gr_get_width(progressBarEmpty);
187     int height = gr_get_height(progressBarEmpty);
188 
189     int progress_x = (gr_fb_width() - width) / 2;
190     int progress_y = GetProgressBaseline();
191 
192     // Erase behind the progress bar (in case this was a progress-only update)
193     gr_color(0, 0, 0, 255);
194     gr_fill(progress_x, progress_y, width, height);
195 
196     if (progressBarType == DETERMINATE) {
197       float p = progressScopeStart + progress * progressScopeSize;
198       int pos = static_cast<int>(p * width);
199 
200       if (rtl_locale_) {
201         // Fill the progress bar from right to left.
202         if (pos > 0) {
203           gr_blit(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
204                   progress_y);
205         }
206         if (pos < width - 1) {
207           gr_blit(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
208         }
209       } else {
210         // Fill the progress bar from left to right.
211         if (pos > 0) {
212           gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
213         }
214         if (pos < width - 1) {
215           gr_blit(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
216         }
217       }
218     }
219   }
220 }
221 
SetColor(UIElement e)222 void ScreenRecoveryUI::SetColor(UIElement e) {
223     switch (e) {
224         case INFO:
225             gr_color(249, 194, 0, 255);
226             break;
227         case HEADER:
228             gr_color(247, 0, 6, 255);
229             break;
230         case MENU:
231         case MENU_SEL_BG:
232             gr_color(0, 106, 157, 255);
233             break;
234         case MENU_SEL_BG_ACTIVE:
235             gr_color(0, 156, 100, 255);
236             break;
237         case MENU_SEL_FG:
238             gr_color(255, 255, 255, 255);
239             break;
240         case LOG:
241             gr_color(196, 196, 196, 255);
242             break;
243         case TEXT_FILL:
244             gr_color(0, 0, 0, 160);
245             break;
246         default:
247             gr_color(255, 255, 255, 255);
248             break;
249     }
250 }
251 
DrawHorizontalRule(int * y)252 void ScreenRecoveryUI::DrawHorizontalRule(int* y) {
253     SetColor(MENU);
254     *y += 4;
255     gr_fill(0, *y, gr_fb_width(), *y + 2);
256     *y += 4;
257 }
258 
DrawTextLine(int x,int * y,const char * line,bool bold) const259 void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const {
260     gr_text(gr_sys_font(), x, *y, line, bold);
261     *y += char_height_ + 4;
262 }
263 
DrawTextLines(int x,int * y,const char * const * lines) const264 void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const {
265     for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
266         DrawTextLine(x, y, lines[i], false);
267     }
268 }
269 
270 static const char* REGULAR_HELP[] = {
271     "Use volume up/down and power.",
272     NULL
273 };
274 
275 static const char* LONG_PRESS_HELP[] = {
276     "Any button cycles highlight.",
277     "Long-press activates.",
278     NULL
279 };
280 
281 // Redraw everything on the screen.  Does not flip pages.
282 // Should only be called with updateMutex locked.
draw_screen_locked()283 void ScreenRecoveryUI::draw_screen_locked() {
284     if (!show_text) {
285         draw_background_locked();
286         draw_foreground_locked();
287     } else {
288         gr_color(0, 0, 0, 255);
289         gr_clear();
290 
291         int y = 0;
292         if (show_menu) {
293             std::string recovery_fingerprint =
294                     android::base::GetProperty("ro.bootimage.build.fingerprint", "");
295 
296             SetColor(INFO);
297             DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true);
298             for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) {
299                 DrawTextLine(TEXT_INDENT, &y, chunk.c_str(), false);
300             }
301             DrawTextLines(TEXT_INDENT, &y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
302 
303             SetColor(HEADER);
304             DrawTextLines(TEXT_INDENT, &y, menu_headers_);
305 
306             SetColor(MENU);
307             DrawHorizontalRule(&y);
308             y += 4;
309             for (int i = 0; i < menu_items; ++i) {
310                 if (i == menu_sel) {
311                     // Draw the highlight bar.
312                     SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG);
313                     gr_fill(0, y - 2, gr_fb_width(), y + char_height_ + 2);
314                     // Bold white text for the selected item.
315                     SetColor(MENU_SEL_FG);
316                     gr_text(gr_sys_font(), 4, y, menu_[i], true);
317                     SetColor(MENU);
318                 } else {
319                     gr_text(gr_sys_font(), 4, y, menu_[i], false);
320                 }
321                 y += char_height_ + 4;
322             }
323             DrawHorizontalRule(&y);
324         }
325 
326         // display from the bottom up, until we hit the top of the
327         // screen, the bottom of the menu, or we've displayed the
328         // entire text buffer.
329         SetColor(LOG);
330         int row = (text_top_ + text_rows_ - 1) % text_rows_;
331         size_t count = 0;
332         for (int ty = gr_fb_height() - char_height_;
333              ty >= y && count < text_rows_;
334              ty -= char_height_, ++count) {
335             gr_text(gr_sys_font(), 0, ty, text_[row], false);
336             --row;
337             if (row < 0) row = text_rows_ - 1;
338         }
339     }
340 }
341 
342 // Redraw everything on the screen and flip the screen (make it visible).
343 // Should only be called with updateMutex locked.
update_screen_locked()344 void ScreenRecoveryUI::update_screen_locked() {
345     draw_screen_locked();
346     gr_flip();
347 }
348 
349 // Updates only the progress bar, if possible, otherwise redraws the screen.
350 // Should only be called with updateMutex locked.
update_progress_locked()351 void ScreenRecoveryUI::update_progress_locked() {
352     if (show_text || !pagesIdentical) {
353         draw_screen_locked();    // Must redraw the whole screen
354         pagesIdentical = true;
355     } else {
356         draw_foreground_locked();  // Draw only the progress bar and overlays
357     }
358     gr_flip();
359 }
360 
361 // Keeps the progress bar updated, even when the process is otherwise busy.
ProgressThreadStartRoutine(void * data)362 void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) {
363     reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop();
364     return nullptr;
365 }
366 
ProgressThreadLoop()367 void ScreenRecoveryUI::ProgressThreadLoop() {
368     double interval = 1.0 / animation_fps;
369     while (true) {
370         double start = now();
371         pthread_mutex_lock(&updateMutex);
372 
373         bool redraw = false;
374 
375         // update the installation animation, if active
376         // skip this if we have a text overlay (too expensive to update)
377         if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) {
378             if (!intro_done) {
379                 if (current_frame == intro_frames - 1) {
380                     intro_done = true;
381                     current_frame = 0;
382                 } else {
383                     ++current_frame;
384                 }
385             } else {
386                 current_frame = (current_frame + 1) % loop_frames;
387             }
388 
389             redraw = true;
390         }
391 
392         // move the progress bar forward on timed intervals, if configured
393         int duration = progressScopeDuration;
394         if (progressBarType == DETERMINATE && duration > 0) {
395             double elapsed = now() - progressScopeTime;
396             float p = 1.0 * elapsed / duration;
397             if (p > 1.0) p = 1.0;
398             if (p > progress) {
399                 progress = p;
400                 redraw = true;
401             }
402         }
403 
404         if (redraw) update_progress_locked();
405 
406         pthread_mutex_unlock(&updateMutex);
407         double end = now();
408         // minimum of 20ms delay between frames
409         double delay = interval - (end-start);
410         if (delay < 0.02) delay = 0.02;
411         usleep(static_cast<useconds_t>(delay * 1000000));
412     }
413 }
414 
LoadBitmap(const char * filename,GRSurface ** surface)415 void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
416     int result = res_create_display_surface(filename, surface);
417     if (result < 0) {
418         LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
419     }
420 }
421 
LoadLocalizedBitmap(const char * filename,GRSurface ** surface)422 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
423   int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
424   if (result < 0) {
425     LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
426   }
427 }
428 
Alloc2d(size_t rows,size_t cols)429 static char** Alloc2d(size_t rows, size_t cols) {
430     char** result = new char*[rows];
431     for (size_t i = 0; i < rows; ++i) {
432         result[i] = new char[cols];
433         memset(result[i], 0, cols);
434     }
435     return result;
436 }
437 
438 // Choose the right background string to display during update.
SetSystemUpdateText(bool security_update)439 void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
440     if (security_update) {
441         LoadLocalizedBitmap("installing_security_text", &installing_text);
442     } else {
443         LoadLocalizedBitmap("installing_text", &installing_text);
444     }
445     Redraw();
446 }
447 
InitTextParams()448 bool ScreenRecoveryUI::InitTextParams() {
449     if (gr_init() < 0) {
450       return false;
451     }
452 
453     gr_font_size(gr_sys_font(), &char_width_, &char_height_);
454     text_rows_ = gr_fb_height() / char_height_;
455     text_cols_ = gr_fb_width() / char_width_;
456     return true;
457 }
458 
Init(const std::string & locale)459 bool ScreenRecoveryUI::Init(const std::string& locale) {
460   RecoveryUI::Init(locale);
461   if (!InitTextParams()) {
462     return false;
463   }
464 
465   density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f;
466 
467   // Are we portrait or landscape?
468   layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
469   // Are we the large variant of our base layout?
470   if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
471 
472   text_ = Alloc2d(text_rows_, text_cols_ + 1);
473   file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
474   menu_ = Alloc2d(text_rows_, text_cols_ + 1);
475 
476   text_col_ = text_row_ = 0;
477   text_top_ = 1;
478 
479   LoadBitmap("icon_error", &error_icon);
480 
481   LoadBitmap("progress_empty", &progressBarEmpty);
482   LoadBitmap("progress_fill", &progressBarFill);
483 
484   LoadBitmap("stage_empty", &stageMarkerEmpty);
485   LoadBitmap("stage_fill", &stageMarkerFill);
486 
487   // Background text for "installing_update" could be "installing update"
488   // or "installing security update". It will be set after UI init according
489   // to commands in BCB.
490   installing_text = nullptr;
491   LoadLocalizedBitmap("erasing_text", &erasing_text);
492   LoadLocalizedBitmap("no_command_text", &no_command_text);
493   LoadLocalizedBitmap("error_text", &error_text);
494 
495   LoadAnimation();
496 
497   pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
498 
499   return true;
500 }
501 
LoadAnimation()502 void ScreenRecoveryUI::LoadAnimation() {
503     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir);
504     dirent* de;
505     std::vector<std::string> intro_frame_names;
506     std::vector<std::string> loop_frame_names;
507 
508     while ((de = readdir(dir.get())) != nullptr) {
509         int value, num_chars;
510         if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
511             intro_frame_names.emplace_back(de->d_name, num_chars);
512         } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
513             loop_frame_names.emplace_back(de->d_name, num_chars);
514         }
515     }
516 
517     intro_frames = intro_frame_names.size();
518     loop_frames = loop_frame_names.size();
519 
520     // It's okay to not have an intro.
521     if (intro_frames == 0) intro_done = true;
522     // But you must have an animation.
523     if (loop_frames == 0) abort();
524 
525     std::sort(intro_frame_names.begin(), intro_frame_names.end());
526     std::sort(loop_frame_names.begin(), loop_frame_names.end());
527 
528     introFrames = new GRSurface*[intro_frames];
529     for (size_t i = 0; i < intro_frames; i++) {
530         LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]);
531     }
532 
533     loopFrames = new GRSurface*[loop_frames];
534     for (size_t i = 0; i < loop_frames; i++) {
535         LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]);
536     }
537 }
538 
SetBackground(Icon icon)539 void ScreenRecoveryUI::SetBackground(Icon icon) {
540     pthread_mutex_lock(&updateMutex);
541 
542     currentIcon = icon;
543     update_screen_locked();
544 
545     pthread_mutex_unlock(&updateMutex);
546 }
547 
SetProgressType(ProgressType type)548 void ScreenRecoveryUI::SetProgressType(ProgressType type) {
549     pthread_mutex_lock(&updateMutex);
550     if (progressBarType != type) {
551         progressBarType = type;
552     }
553     progressScopeStart = 0;
554     progressScopeSize = 0;
555     progress = 0;
556     update_progress_locked();
557     pthread_mutex_unlock(&updateMutex);
558 }
559 
ShowProgress(float portion,float seconds)560 void ScreenRecoveryUI::ShowProgress(float portion, float seconds) {
561     pthread_mutex_lock(&updateMutex);
562     progressBarType = DETERMINATE;
563     progressScopeStart += progressScopeSize;
564     progressScopeSize = portion;
565     progressScopeTime = now();
566     progressScopeDuration = seconds;
567     progress = 0;
568     update_progress_locked();
569     pthread_mutex_unlock(&updateMutex);
570 }
571 
SetProgress(float fraction)572 void ScreenRecoveryUI::SetProgress(float fraction) {
573     pthread_mutex_lock(&updateMutex);
574     if (fraction < 0.0) fraction = 0.0;
575     if (fraction > 1.0) fraction = 1.0;
576     if (progressBarType == DETERMINATE && fraction > progress) {
577         // Skip updates that aren't visibly different.
578         int width = gr_get_width(progressBarEmpty);
579         float scale = width * progressScopeSize;
580         if ((int) (progress * scale) != (int) (fraction * scale)) {
581             progress = fraction;
582             update_progress_locked();
583         }
584     }
585     pthread_mutex_unlock(&updateMutex);
586 }
587 
SetStage(int current,int max)588 void ScreenRecoveryUI::SetStage(int current, int max) {
589     pthread_mutex_lock(&updateMutex);
590     stage = current;
591     max_stage = max;
592     pthread_mutex_unlock(&updateMutex);
593 }
594 
PrintV(const char * fmt,bool copy_to_stdout,va_list ap)595 void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
596     std::string str;
597     android::base::StringAppendV(&str, fmt, ap);
598 
599     if (copy_to_stdout) {
600         fputs(str.c_str(), stdout);
601     }
602 
603     pthread_mutex_lock(&updateMutex);
604     if (text_rows_ > 0 && text_cols_ > 0) {
605         for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
606             if (*ptr == '\n' || text_col_ >= text_cols_) {
607                 text_[text_row_][text_col_] = '\0';
608                 text_col_ = 0;
609                 text_row_ = (text_row_ + 1) % text_rows_;
610                 if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
611             }
612             if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
613         }
614         text_[text_row_][text_col_] = '\0';
615         update_screen_locked();
616     }
617     pthread_mutex_unlock(&updateMutex);
618 }
619 
Print(const char * fmt,...)620 void ScreenRecoveryUI::Print(const char* fmt, ...) {
621     va_list ap;
622     va_start(ap, fmt);
623     PrintV(fmt, true, ap);
624     va_end(ap);
625 }
626 
PrintOnScreenOnly(const char * fmt,...)627 void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
628     va_list ap;
629     va_start(ap, fmt);
630     PrintV(fmt, false, ap);
631     va_end(ap);
632 }
633 
PutChar(char ch)634 void ScreenRecoveryUI::PutChar(char ch) {
635     pthread_mutex_lock(&updateMutex);
636     if (ch != '\n') text_[text_row_][text_col_++] = ch;
637     if (ch == '\n' || text_col_ >= text_cols_) {
638         text_col_ = 0;
639         ++text_row_;
640 
641         if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
642     }
643     pthread_mutex_unlock(&updateMutex);
644 }
645 
ClearText()646 void ScreenRecoveryUI::ClearText() {
647     pthread_mutex_lock(&updateMutex);
648     text_col_ = 0;
649     text_row_ = 0;
650     text_top_ = 1;
651     for (size_t i = 0; i < text_rows_; ++i) {
652         memset(text_[i], 0, text_cols_ + 1);
653     }
654     pthread_mutex_unlock(&updateMutex);
655 }
656 
ShowFile(FILE * fp)657 void ScreenRecoveryUI::ShowFile(FILE* fp) {
658     std::vector<off_t> offsets;
659     offsets.push_back(ftello(fp));
660     ClearText();
661 
662     struct stat sb;
663     fstat(fileno(fp), &sb);
664 
665     bool show_prompt = false;
666     while (true) {
667         if (show_prompt) {
668             PrintOnScreenOnly("--(%d%% of %d bytes)--",
669                   static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
670                   static_cast<int>(sb.st_size));
671             Redraw();
672             while (show_prompt) {
673                 show_prompt = false;
674                 int key = WaitKey();
675                 if (key == KEY_POWER || key == KEY_ENTER) {
676                     return;
677                 } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
678                     if (offsets.size() <= 1) {
679                         show_prompt = true;
680                     } else {
681                         offsets.pop_back();
682                         fseek(fp, offsets.back(), SEEK_SET);
683                     }
684                 } else {
685                     if (feof(fp)) {
686                         return;
687                     }
688                     offsets.push_back(ftello(fp));
689                 }
690             }
691             ClearText();
692         }
693 
694         int ch = getc(fp);
695         if (ch == EOF) {
696             while (text_row_ < text_rows_ - 1) PutChar('\n');
697             show_prompt = true;
698         } else {
699             PutChar(ch);
700             if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
701                 show_prompt = true;
702             }
703         }
704     }
705 }
706 
ShowFile(const char * filename)707 void ScreenRecoveryUI::ShowFile(const char* filename) {
708     FILE* fp = fopen_path(filename, "re");
709     if (fp == nullptr) {
710         Print("  Unable to open %s: %s\n", filename, strerror(errno));
711         return;
712     }
713 
714     char** old_text = text_;
715     size_t old_text_col = text_col_;
716     size_t old_text_row = text_row_;
717     size_t old_text_top = text_top_;
718 
719     // Swap in the alternate screen and clear it.
720     text_ = file_viewer_text_;
721     ClearText();
722 
723     ShowFile(fp);
724     fclose(fp);
725 
726     text_ = old_text;
727     text_col_ = old_text_col;
728     text_row_ = old_text_row;
729     text_top_ = old_text_top;
730 }
731 
StartMenu(const char * const * headers,const char * const * items,int initial_selection)732 void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
733                                  int initial_selection) {
734     pthread_mutex_lock(&updateMutex);
735     if (text_rows_ > 0 && text_cols_ > 0) {
736         menu_headers_ = headers;
737         size_t i = 0;
738         for (; i < text_rows_ && items[i] != nullptr; ++i) {
739             strncpy(menu_[i], items[i], text_cols_ - 1);
740             menu_[i][text_cols_ - 1] = '\0';
741         }
742         menu_items = i;
743         show_menu = true;
744         menu_sel = initial_selection;
745         update_screen_locked();
746     }
747     pthread_mutex_unlock(&updateMutex);
748 }
749 
SelectMenu(int sel)750 int ScreenRecoveryUI::SelectMenu(int sel) {
751     pthread_mutex_lock(&updateMutex);
752     if (show_menu) {
753         int old_sel = menu_sel;
754         menu_sel = sel;
755 
756         // Wrap at top and bottom.
757         if (menu_sel < 0) menu_sel = menu_items - 1;
758         if (menu_sel >= menu_items) menu_sel = 0;
759 
760         sel = menu_sel;
761         if (menu_sel != old_sel) update_screen_locked();
762     }
763     pthread_mutex_unlock(&updateMutex);
764     return sel;
765 }
766 
EndMenu()767 void ScreenRecoveryUI::EndMenu() {
768     pthread_mutex_lock(&updateMutex);
769     if (show_menu && text_rows_ > 0 && text_cols_ > 0) {
770         show_menu = false;
771         update_screen_locked();
772     }
773     pthread_mutex_unlock(&updateMutex);
774 }
775 
IsTextVisible()776 bool ScreenRecoveryUI::IsTextVisible() {
777     pthread_mutex_lock(&updateMutex);
778     int visible = show_text;
779     pthread_mutex_unlock(&updateMutex);
780     return visible;
781 }
782 
WasTextEverVisible()783 bool ScreenRecoveryUI::WasTextEverVisible() {
784     pthread_mutex_lock(&updateMutex);
785     int ever_visible = show_text_ever;
786     pthread_mutex_unlock(&updateMutex);
787     return ever_visible;
788 }
789 
ShowText(bool visible)790 void ScreenRecoveryUI::ShowText(bool visible) {
791     pthread_mutex_lock(&updateMutex);
792     show_text = visible;
793     if (show_text) show_text_ever = true;
794     update_screen_locked();
795     pthread_mutex_unlock(&updateMutex);
796 }
797 
Redraw()798 void ScreenRecoveryUI::Redraw() {
799     pthread_mutex_lock(&updateMutex);
800     update_screen_locked();
801     pthread_mutex_unlock(&updateMutex);
802 }
803 
KeyLongPress(int)804 void ScreenRecoveryUI::KeyLongPress(int) {
805     // Redraw so that if we're in the menu, the highlight
806     // will change color to indicate a successful long press.
807     Redraw();
808 }
809