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