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