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). 43 static double now() { 44 struct timeval tv; 45 gettimeofday(&tv, NULL); 46 return tv.tv_sec + tv.tv_usec / 1000000.0; 47 } 48 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. 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. 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 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 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. 233 void* WearRecoveryUI::progress_thread(void *cookie) { 234 self->progress_loop(); 235 return NULL; 236 } 237 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 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 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 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 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 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 373 void WearRecoveryUI::SetStage(int current, int max) 374 { 375 } 376 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 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 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 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 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 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 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 495 void WearRecoveryUI::Redraw() 496 { 497 pthread_mutex_lock(&updateMutex); 498 update_screen_locked(); 499 pthread_mutex_unlock(&updateMutex); 500 } 501 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 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 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 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 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 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