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 <errno.h> 18 #include <fcntl.h> 19 #include <linux/input.h> 20 #include <pthread.h> 21 #include <stdarg.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <sys/stat.h> 26 #include <sys/time.h> 27 #include <sys/types.h> 28 #include <time.h> 29 #include <unistd.h> 30 31 #include "common.h" 32 #include "device.h" 33 #include "minui/minui.h" 34 #include "screen_ui.h" 35 #include "ui.h" 36 37 static int char_width; 38 static 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. 43 static ScreenRecoveryUI* self = NULL; 44 45 // Return the current time as a double (including fractions of a second). 46 static double now() { 47 struct timeval tv; 48 gettimeofday(&tv, NULL); 49 return tv.tv_sec + tv.tv_usec / 1000000.0; 50 } 51 52 ScreenRecoveryUI::ScreenRecoveryUI() : 53 currentIcon(NONE), 54 installingFrame(0), 55 rtl_locale(false), 56 progressBarType(EMPTY), 57 progressScopeStart(0), 58 progressScopeSize(0), 59 progress(0), 60 pagesIdentical(false), 61 text_cols(0), 62 text_rows(0), 63 text_col(0), 64 text_row(0), 65 text_top(0), 66 show_text(false), 67 show_text_ever(false), 68 show_menu(false), 69 menu_top(0), 70 menu_items(0), 71 menu_sel(0), 72 73 // These values are correct for the default image resources 74 // provided with the android platform. Devices which use 75 // different resources should have a subclass of ScreenRecoveryUI 76 // that overrides Init() to set these values appropriately and 77 // then call the superclass Init(). 78 animation_fps(20), 79 indeterminate_frames(6), 80 installing_frames(7), 81 install_overlay_offset_x(13), 82 install_overlay_offset_y(190), 83 overlay_offset_x(-1), 84 overlay_offset_y(-1), 85 stage(-1), 86 max_stage(-1) { 87 88 for (int i = 0; i < 5; i++) 89 backgroundIcon[i] = NULL; 90 91 pthread_mutex_init(&updateMutex, NULL); 92 self = this; 93 } 94 95 // Draw the given frame over the installation overlay animation. The 96 // background is not cleared or draw with the base icon first; we 97 // assume that the frame already contains some other frame of the 98 // animation. Does nothing if no overlay animation is defined. 99 // Should only be called with updateMutex locked. 100 void ScreenRecoveryUI::draw_install_overlay_locked(int frame) { 101 if (installationOverlay == NULL || overlay_offset_x < 0) return; 102 gr_surface surface = installationOverlay[frame]; 103 int iconWidth = gr_get_width(surface); 104 int iconHeight = gr_get_height(surface); 105 gr_blit(surface, 0, 0, iconWidth, iconHeight, 106 overlay_offset_x, overlay_offset_y); 107 } 108 109 // Clear the screen and draw the currently selected background icon (if any). 110 // Should only be called with updateMutex locked. 111 void ScreenRecoveryUI::draw_background_locked(Icon icon) 112 { 113 pagesIdentical = false; 114 gr_color(0, 0, 0, 255); 115 gr_fill(0, 0, gr_fb_width(), gr_fb_height()); 116 117 if (icon) { 118 gr_surface surface = backgroundIcon[icon]; 119 gr_surface text_surface = backgroundText[icon]; 120 121 int iconWidth = gr_get_width(surface); 122 int iconHeight = gr_get_height(surface); 123 int textWidth = gr_get_width(text_surface); 124 int textHeight = gr_get_height(text_surface); 125 int stageHeight = gr_get_height(stageMarkerEmpty); 126 127 int sh = (max_stage >= 0) ? stageHeight : 0; 128 129 int iconX = (gr_fb_width() - iconWidth) / 2; 130 int iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2; 131 132 int textX = (gr_fb_width() - textWidth) / 2; 133 int textY = ((gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40; 134 135 gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); 136 if (stageHeight > 0) { 137 int sw = gr_get_width(stageMarkerEmpty); 138 int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; 139 int y = iconY + iconHeight + 20; 140 for (int i = 0; i < max_stage; ++i) { 141 gr_blit((i < stage) ? stageMarkerFill : stageMarkerEmpty, 142 0, 0, sw, stageHeight, x, y); 143 x += sw; 144 } 145 } 146 147 if (icon == INSTALLING_UPDATE || icon == ERASING) { 148 draw_install_overlay_locked(installingFrame); 149 } 150 151 gr_color(255, 255, 255, 255); 152 gr_texticon(textX, textY, text_surface); 153 } 154 } 155 156 // Draw the progress bar (if any) on the screen. Does not flip pages. 157 // Should only be called with updateMutex locked. 158 void ScreenRecoveryUI::draw_progress_locked() 159 { 160 if (currentIcon == ERROR) return; 161 162 if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { 163 draw_install_overlay_locked(installingFrame); 164 } 165 166 if (progressBarType != EMPTY) { 167 int iconHeight = gr_get_height(backgroundIcon[INSTALLING_UPDATE]); 168 int width = gr_get_width(progressBarEmpty); 169 int height = gr_get_height(progressBarEmpty); 170 171 int dx = (gr_fb_width() - width)/2; 172 int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; 173 174 // Erase behind the progress bar (in case this was a progress-only update) 175 gr_color(0, 0, 0, 255); 176 gr_fill(dx, dy, width, height); 177 178 if (progressBarType == DETERMINATE) { 179 float p = progressScopeStart + progress * progressScopeSize; 180 int pos = (int) (p * width); 181 182 if (rtl_locale) { 183 // Fill the progress bar from right to left. 184 if (pos > 0) { 185 gr_blit(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy); 186 } 187 if (pos < width-1) { 188 gr_blit(progressBarEmpty, 0, 0, width-pos, height, dx, dy); 189 } 190 } else { 191 // Fill the progress bar from left to right. 192 if (pos > 0) { 193 gr_blit(progressBarFill, 0, 0, pos, height, dx, dy); 194 } 195 if (pos < width-1) { 196 gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); 197 } 198 } 199 } 200 201 if (progressBarType == INDETERMINATE) { 202 static int frame = 0; 203 gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy); 204 // in RTL locales, we run the animation backwards, which 205 // makes the spinner spin the other way. 206 if (rtl_locale) { 207 frame = (frame + indeterminate_frames - 1) % indeterminate_frames; 208 } else { 209 frame = (frame + 1) % indeterminate_frames; 210 } 211 } 212 } 213 } 214 215 void ScreenRecoveryUI::SetColor(UIElement e) { 216 switch (e) { 217 case HEADER: 218 gr_color(247, 0, 6, 255); 219 break; 220 case MENU: 221 case MENU_SEL_BG: 222 gr_color(0, 106, 157, 255); 223 break; 224 case MENU_SEL_FG: 225 gr_color(255, 255, 255, 255); 226 break; 227 case LOG: 228 gr_color(249, 194, 0, 255); 229 break; 230 case TEXT_FILL: 231 gr_color(0, 0, 0, 160); 232 break; 233 default: 234 gr_color(255, 255, 255, 255); 235 break; 236 } 237 } 238 239 // Redraw everything on the screen. Does not flip pages. 240 // Should only be called with updateMutex locked. 241 void ScreenRecoveryUI::draw_screen_locked() 242 { 243 draw_background_locked(currentIcon); 244 draw_progress_locked(); 245 246 if (show_text) { 247 SetColor(TEXT_FILL); 248 gr_fill(0, 0, gr_fb_width(), gr_fb_height()); 249 250 int y = 0; 251 int i = 0; 252 if (show_menu) { 253 SetColor(HEADER); 254 255 for (; i < menu_top + menu_items; ++i) { 256 if (i == menu_top) SetColor(MENU); 257 258 if (i == menu_top + menu_sel) { 259 // draw the highlight bar 260 SetColor(MENU_SEL_BG); 261 gr_fill(0, y-2, gr_fb_width(), y+char_height+2); 262 // white text of selected item 263 SetColor(MENU_SEL_FG); 264 if (menu[i][0]) gr_text(4, y, menu[i], 1); 265 SetColor(MENU); 266 } else { 267 if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top); 268 } 269 y += char_height+4; 270 } 271 SetColor(MENU); 272 y += 4; 273 gr_fill(0, y, gr_fb_width(), y+2); 274 y += 4; 275 ++i; 276 } 277 278 SetColor(LOG); 279 280 // display from the bottom up, until we hit the top of the 281 // screen, the bottom of the menu, or we've displayed the 282 // entire text buffer. 283 int ty; 284 int row = (text_top+text_rows-1) % text_rows; 285 for (int ty = gr_fb_height() - char_height, count = 0; 286 ty > y+2 && count < text_rows; 287 ty -= char_height, ++count) { 288 gr_text(4, ty, text[row], 0); 289 --row; 290 if (row < 0) row = text_rows-1; 291 } 292 } 293 } 294 295 // Redraw everything on the screen and flip the screen (make it visible). 296 // Should only be called with updateMutex locked. 297 void ScreenRecoveryUI::update_screen_locked() 298 { 299 draw_screen_locked(); 300 gr_flip(); 301 } 302 303 // Updates only the progress bar, if possible, otherwise redraws the screen. 304 // Should only be called with updateMutex locked. 305 void ScreenRecoveryUI::update_progress_locked() 306 { 307 if (show_text || !pagesIdentical) { 308 draw_screen_locked(); // Must redraw the whole screen 309 pagesIdentical = true; 310 } else { 311 draw_progress_locked(); // Draw only the progress bar and overlays 312 } 313 gr_flip(); 314 } 315 316 // Keeps the progress bar updated, even when the process is otherwise busy. 317 void* ScreenRecoveryUI::progress_thread(void *cookie) { 318 self->progress_loop(); 319 return NULL; 320 } 321 322 void ScreenRecoveryUI::progress_loop() { 323 double interval = 1.0 / animation_fps; 324 for (;;) { 325 double start = now(); 326 pthread_mutex_lock(&updateMutex); 327 328 int redraw = 0; 329 330 // update the installation animation, if active 331 // skip this if we have a text overlay (too expensive to update) 332 if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && 333 installing_frames > 0 && !show_text) { 334 installingFrame = (installingFrame + 1) % installing_frames; 335 redraw = 1; 336 } 337 338 // update the progress bar animation, if active 339 // skip this if we have a text overlay (too expensive to update) 340 if (progressBarType == INDETERMINATE && !show_text) { 341 redraw = 1; 342 } 343 344 // move the progress bar forward on timed intervals, if configured 345 int duration = progressScopeDuration; 346 if (progressBarType == DETERMINATE && duration > 0) { 347 double elapsed = now() - progressScopeTime; 348 float p = 1.0 * elapsed / duration; 349 if (p > 1.0) p = 1.0; 350 if (p > progress) { 351 progress = p; 352 redraw = 1; 353 } 354 } 355 356 if (redraw) update_progress_locked(); 357 358 pthread_mutex_unlock(&updateMutex); 359 double end = now(); 360 // minimum of 20ms delay between frames 361 double delay = interval - (end-start); 362 if (delay < 0.02) delay = 0.02; 363 usleep((long)(delay * 1000000)); 364 } 365 } 366 367 void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) { 368 int result = res_create_surface(filename, surface); 369 if (result < 0) { 370 LOGE("missing bitmap %s\n(Code %d)\n", filename, result); 371 } 372 } 373 374 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) { 375 int result = res_create_localized_surface(filename, surface); 376 if (result < 0) { 377 LOGE("missing bitmap %s\n(Code %d)\n", filename, result); 378 } 379 } 380 381 void ScreenRecoveryUI::Init() 382 { 383 gr_init(); 384 385 gr_font_size(&char_width, &char_height); 386 387 text_col = text_row = 0; 388 text_rows = gr_fb_height() / char_height; 389 if (text_rows > kMaxRows) text_rows = kMaxRows; 390 text_top = 1; 391 392 text_cols = gr_fb_width() / char_width; 393 if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; 394 395 LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); 396 backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; 397 LoadBitmap("icon_error", &backgroundIcon[ERROR]); 398 backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; 399 400 LoadBitmap("progress_empty", &progressBarEmpty); 401 LoadBitmap("progress_fill", &progressBarFill); 402 LoadBitmap("stage_empty", &stageMarkerEmpty); 403 LoadBitmap("stage_fill", &stageMarkerFill); 404 405 LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); 406 LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); 407 LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); 408 LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); 409 410 int i; 411 412 progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames * 413 sizeof(gr_surface)); 414 for (i = 0; i < indeterminate_frames; ++i) { 415 char filename[40]; 416 // "indeterminate01.png", "indeterminate02.png", ... 417 sprintf(filename, "indeterminate%02d", i+1); 418 LoadBitmap(filename, progressBarIndeterminate+i); 419 } 420 421 if (installing_frames > 0) { 422 installationOverlay = (gr_surface*)malloc(installing_frames * 423 sizeof(gr_surface)); 424 for (i = 0; i < installing_frames; ++i) { 425 char filename[40]; 426 // "icon_installing_overlay01.png", 427 // "icon_installing_overlay02.png", ... 428 sprintf(filename, "icon_installing_overlay%02d", i+1); 429 LoadBitmap(filename, installationOverlay+i); 430 } 431 } else { 432 installationOverlay = NULL; 433 } 434 435 pthread_create(&progress_t, NULL, progress_thread, NULL); 436 437 RecoveryUI::Init(); 438 } 439 440 void ScreenRecoveryUI::SetLocale(const char* locale) { 441 if (locale) { 442 char* lang = strdup(locale); 443 for (char* p = lang; *p; ++p) { 444 if (*p == '_') { 445 *p = '\0'; 446 break; 447 } 448 } 449 450 // A bit cheesy: keep an explicit list of supported languages 451 // that are RTL. 452 if (strcmp(lang, "ar") == 0 || // Arabic 453 strcmp(lang, "fa") == 0 || // Persian (Farsi) 454 strcmp(lang, "he") == 0 || // Hebrew (new language code) 455 strcmp(lang, "iw") == 0 || // Hebrew (old language code) 456 strcmp(lang, "ur") == 0) { // Urdu 457 rtl_locale = true; 458 } 459 free(lang); 460 } 461 } 462 463 void ScreenRecoveryUI::SetBackground(Icon icon) 464 { 465 pthread_mutex_lock(&updateMutex); 466 467 // Adjust the offset to account for the positioning of the 468 // base image on the screen. 469 if (backgroundIcon[icon] != NULL) { 470 gr_surface bg = backgroundIcon[icon]; 471 gr_surface text = backgroundText[icon]; 472 overlay_offset_x = install_overlay_offset_x + (gr_fb_width() - gr_get_width(bg)) / 2; 473 overlay_offset_y = install_overlay_offset_y + 474 (gr_fb_height() - (gr_get_height(bg) + 475 gr_get_height(text) + 476 40 + 477 ((max_stage >= 0) ? gr_get_height(stageMarkerEmpty) : 0))) / 2; 478 } 479 480 currentIcon = icon; 481 update_screen_locked(); 482 483 pthread_mutex_unlock(&updateMutex); 484 } 485 486 void ScreenRecoveryUI::SetProgressType(ProgressType type) 487 { 488 pthread_mutex_lock(&updateMutex); 489 if (progressBarType != type) { 490 progressBarType = type; 491 } 492 progressScopeStart = 0; 493 progressScopeSize = 0; 494 progress = 0; 495 update_progress_locked(); 496 pthread_mutex_unlock(&updateMutex); 497 } 498 499 void ScreenRecoveryUI::ShowProgress(float portion, float seconds) 500 { 501 pthread_mutex_lock(&updateMutex); 502 progressBarType = DETERMINATE; 503 progressScopeStart += progressScopeSize; 504 progressScopeSize = portion; 505 progressScopeTime = now(); 506 progressScopeDuration = seconds; 507 progress = 0; 508 update_progress_locked(); 509 pthread_mutex_unlock(&updateMutex); 510 } 511 512 void ScreenRecoveryUI::SetProgress(float fraction) 513 { 514 pthread_mutex_lock(&updateMutex); 515 if (fraction < 0.0) fraction = 0.0; 516 if (fraction > 1.0) fraction = 1.0; 517 if (progressBarType == DETERMINATE && fraction > progress) { 518 // Skip updates that aren't visibly different. 519 int width = gr_get_width(progressBarIndeterminate[0]); 520 float scale = width * progressScopeSize; 521 if ((int) (progress * scale) != (int) (fraction * scale)) { 522 progress = fraction; 523 update_progress_locked(); 524 } 525 } 526 pthread_mutex_unlock(&updateMutex); 527 } 528 529 void ScreenRecoveryUI::SetStage(int current, int max) { 530 pthread_mutex_lock(&updateMutex); 531 stage = current; 532 max_stage = max; 533 pthread_mutex_unlock(&updateMutex); 534 } 535 536 void ScreenRecoveryUI::Print(const char *fmt, ...) 537 { 538 char buf[256]; 539 va_list ap; 540 va_start(ap, fmt); 541 vsnprintf(buf, 256, fmt, ap); 542 va_end(ap); 543 544 fputs(buf, stdout); 545 546 // This can get called before ui_init(), so be careful. 547 pthread_mutex_lock(&updateMutex); 548 if (text_rows > 0 && text_cols > 0) { 549 char *ptr; 550 for (ptr = buf; *ptr != '\0'; ++ptr) { 551 if (*ptr == '\n' || text_col >= text_cols) { 552 text[text_row][text_col] = '\0'; 553 text_col = 0; 554 text_row = (text_row + 1) % text_rows; 555 if (text_row == text_top) text_top = (text_top + 1) % text_rows; 556 } 557 if (*ptr != '\n') text[text_row][text_col++] = *ptr; 558 } 559 text[text_row][text_col] = '\0'; 560 update_screen_locked(); 561 } 562 pthread_mutex_unlock(&updateMutex); 563 } 564 565 void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items, 566 int initial_selection) { 567 int i; 568 pthread_mutex_lock(&updateMutex); 569 if (text_rows > 0 && text_cols > 0) { 570 for (i = 0; i < text_rows; ++i) { 571 if (headers[i] == NULL) break; 572 strncpy(menu[i], headers[i], text_cols-1); 573 menu[i][text_cols-1] = '\0'; 574 } 575 menu_top = i; 576 for (; i < text_rows; ++i) { 577 if (items[i-menu_top] == NULL) break; 578 strncpy(menu[i], items[i-menu_top], text_cols-1); 579 menu[i][text_cols-1] = '\0'; 580 } 581 menu_items = i - menu_top; 582 show_menu = 1; 583 menu_sel = initial_selection; 584 update_screen_locked(); 585 } 586 pthread_mutex_unlock(&updateMutex); 587 } 588 589 int ScreenRecoveryUI::SelectMenu(int sel) { 590 int old_sel; 591 pthread_mutex_lock(&updateMutex); 592 if (show_menu > 0) { 593 old_sel = menu_sel; 594 menu_sel = sel; 595 if (menu_sel < 0) menu_sel = 0; 596 if (menu_sel >= menu_items) menu_sel = menu_items-1; 597 sel = menu_sel; 598 if (menu_sel != old_sel) update_screen_locked(); 599 } 600 pthread_mutex_unlock(&updateMutex); 601 return sel; 602 } 603 604 void ScreenRecoveryUI::EndMenu() { 605 int i; 606 pthread_mutex_lock(&updateMutex); 607 if (show_menu > 0 && text_rows > 0 && text_cols > 0) { 608 show_menu = 0; 609 update_screen_locked(); 610 } 611 pthread_mutex_unlock(&updateMutex); 612 } 613 614 bool ScreenRecoveryUI::IsTextVisible() 615 { 616 pthread_mutex_lock(&updateMutex); 617 int visible = show_text; 618 pthread_mutex_unlock(&updateMutex); 619 return visible; 620 } 621 622 bool ScreenRecoveryUI::WasTextEverVisible() 623 { 624 pthread_mutex_lock(&updateMutex); 625 int ever_visible = show_text_ever; 626 pthread_mutex_unlock(&updateMutex); 627 return ever_visible; 628 } 629 630 void ScreenRecoveryUI::ShowText(bool visible) 631 { 632 pthread_mutex_lock(&updateMutex); 633 show_text = visible; 634 if (show_text) show_text_ever = 1; 635 update_screen_locked(); 636 pthread_mutex_unlock(&updateMutex); 637 } 638 639 void ScreenRecoveryUI::Redraw() 640 { 641 pthread_mutex_lock(&updateMutex); 642 update_screen_locked(); 643 pthread_mutex_unlock(&updateMutex); 644 } 645