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