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