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).
now()43 static double now() {
44     struct timeval tv;
45     gettimeofday(&tv, NULL);
46     return tv.tv_sec + tv.tv_usec / 1000000.0;
47 }
48 
WearRecoveryUI()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.
draw_background_locked(Icon icon)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.
draw_progress_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 
draw_screen_locked()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 
update_screen_locked()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.
progress_thread(void * cookie)233 void* WearRecoveryUI::progress_thread(void *cookie) {
234     self->progress_loop();
235     return NULL;
236 }
237 
progress_loop()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 
Init()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 
SetBackground(Icon icon)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 
SetProgressType(ProgressType type)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 
ShowProgress(float portion,float seconds)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 
SetProgress(float fraction)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 
SetStage(int current,int max)373 void WearRecoveryUI::SetStage(int current, int max)
374 {
375 }
376 
Print(const char * fmt,...)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 
StartMenu(const char * const * headers,const char * const * items,int initial_selection)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 
SelectMenu(int sel)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 
EndMenu()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 
IsTextVisible()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 
WasTextEverVisible()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 
ShowText(bool visible)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 
Redraw()495 void WearRecoveryUI::Redraw()
496 {
497     pthread_mutex_lock(&updateMutex);
498     update_screen_locked();
499     pthread_mutex_unlock(&updateMutex);
500 }
501 
ShowFile(FILE * fp)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 
PutChar(char ch)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 
ShowFile(const char * filename)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 
ClearText()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 
PrintOnScreenOnly(const char * fmt,...)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 
PrintV(const char * fmt,bool copy_to_stdout,va_list ap)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