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 progressBarType(EMPTY), 56 progressScopeStart(0), 57 progressScopeSize(0), 58 progress(0), 59 pagesIdentical(false), 60 text_cols(0), 61 text_rows(0), 62 text_col(0), 63 text_row(0), 64 text_top(0), 65 show_text(false), 66 show_text_ever(false), 67 show_menu(false), 68 menu_top(0), 69 menu_items(0), 70 menu_sel(0), 71 72 // These values are correct for the default image resources 73 // provided with the android platform. Devices which use 74 // different resources should have a subclass of ScreenRecoveryUI 75 // that overrides Init() to set these values appropriately and 76 // then call the superclass Init(). 77 animation_fps(20), 78 indeterminate_frames(6), 79 installing_frames(7), 80 install_overlay_offset_x(13), 81 install_overlay_offset_y(190) { 82 pthread_mutex_init(&updateMutex, NULL); 83 self = this; 84 } 85 86 // Draw the given frame over the installation overlay animation. The 87 // background is not cleared or draw with the base icon first; we 88 // assume that the frame already contains some other frame of the 89 // animation. Does nothing if no overlay animation is defined. 90 // Should only be called with updateMutex locked. 91 void ScreenRecoveryUI::draw_install_overlay_locked(int frame) { 92 if (installationOverlay == NULL) return; 93 gr_surface surface = installationOverlay[frame]; 94 int iconWidth = gr_get_width(surface); 95 int iconHeight = gr_get_height(surface); 96 gr_blit(surface, 0, 0, iconWidth, iconHeight, 97 install_overlay_offset_x, install_overlay_offset_y); 98 } 99 100 // Clear the screen and draw the currently selected background icon (if any). 101 // Should only be called with updateMutex locked. 102 void ScreenRecoveryUI::draw_background_locked(Icon icon) 103 { 104 pagesIdentical = false; 105 gr_color(0, 0, 0, 255); 106 gr_fill(0, 0, gr_fb_width(), gr_fb_height()); 107 108 if (icon) { 109 gr_surface surface = backgroundIcon[icon]; 110 int iconWidth = gr_get_width(surface); 111 int iconHeight = gr_get_height(surface); 112 int iconX = (gr_fb_width() - iconWidth) / 2; 113 int iconY = (gr_fb_height() - iconHeight) / 2; 114 gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); 115 if (icon == INSTALLING) { 116 draw_install_overlay_locked(installingFrame); 117 } 118 } 119 } 120 121 // Draw the progress bar (if any) on the screen. Does not flip pages. 122 // Should only be called with updateMutex locked. 123 void ScreenRecoveryUI::draw_progress_locked() 124 { 125 if (currentIcon == ERROR) return; 126 127 if (currentIcon == INSTALLING) { 128 draw_install_overlay_locked(installingFrame); 129 } 130 131 if (progressBarType != EMPTY) { 132 int iconHeight = gr_get_height(backgroundIcon[INSTALLING]); 133 int width = gr_get_width(progressBarEmpty); 134 int height = gr_get_height(progressBarEmpty); 135 136 int dx = (gr_fb_width() - width)/2; 137 int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; 138 139 // Erase behind the progress bar (in case this was a progress-only update) 140 gr_color(0, 0, 0, 255); 141 gr_fill(dx, dy, width, height); 142 143 if (progressBarType == DETERMINATE) { 144 float p = progressScopeStart + progress * progressScopeSize; 145 int pos = (int) (p * width); 146 147 if (pos > 0) { 148 gr_blit(progressBarFill, 0, 0, pos, height, dx, dy); 149 } 150 if (pos < width-1) { 151 gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); 152 } 153 } 154 155 if (progressBarType == INDETERMINATE) { 156 static int frame = 0; 157 gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy); 158 frame = (frame + 1) % indeterminate_frames; 159 } 160 } 161 } 162 163 void ScreenRecoveryUI::draw_text_line(int row, const char* t) { 164 if (t[0] != '\0') { 165 gr_text(0, (row+1)*CHAR_HEIGHT-1, t); 166 } 167 } 168 169 // Redraw everything on the screen. Does not flip pages. 170 // Should only be called with updateMutex locked. 171 void ScreenRecoveryUI::draw_screen_locked() 172 { 173 draw_background_locked(currentIcon); 174 draw_progress_locked(); 175 176 if (show_text) { 177 gr_color(0, 0, 0, 160); 178 gr_fill(0, 0, gr_fb_width(), gr_fb_height()); 179 180 int i = 0; 181 if (show_menu) { 182 gr_color(64, 96, 255, 255); 183 gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT, 184 gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1); 185 186 for (; i < menu_top + menu_items; ++i) { 187 if (i == menu_top + menu_sel) { 188 gr_color(255, 255, 255, 255); 189 draw_text_line(i, menu[i]); 190 gr_color(64, 96, 255, 255); 191 } else { 192 draw_text_line(i, menu[i]); 193 } 194 } 195 gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1, 196 gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1); 197 ++i; 198 } 199 200 gr_color(255, 255, 0, 255); 201 202 for (; i < text_rows; ++i) { 203 draw_text_line(i, text[(i+text_top) % text_rows]); 204 } 205 } 206 } 207 208 // Redraw everything on the screen and flip the screen (make it visible). 209 // Should only be called with updateMutex locked. 210 void ScreenRecoveryUI::update_screen_locked() 211 { 212 draw_screen_locked(); 213 gr_flip(); 214 } 215 216 // Updates only the progress bar, if possible, otherwise redraws the screen. 217 // Should only be called with updateMutex locked. 218 void ScreenRecoveryUI::update_progress_locked() 219 { 220 if (show_text || !pagesIdentical) { 221 draw_screen_locked(); // Must redraw the whole screen 222 pagesIdentical = true; 223 } else { 224 draw_progress_locked(); // Draw only the progress bar and overlays 225 } 226 gr_flip(); 227 } 228 229 // Keeps the progress bar updated, even when the process is otherwise busy. 230 void* ScreenRecoveryUI::progress_thread(void *cookie) { 231 self->progress_loop(); 232 return NULL; 233 } 234 235 void ScreenRecoveryUI::progress_loop() { 236 double interval = 1.0 / animation_fps; 237 for (;;) { 238 double start = now(); 239 pthread_mutex_lock(&updateMutex); 240 241 int redraw = 0; 242 243 // update the installation animation, if active 244 // skip this if we have a text overlay (too expensive to update) 245 if (currentIcon == INSTALLING && installing_frames > 0 && !show_text) { 246 installingFrame = (installingFrame + 1) % installing_frames; 247 redraw = 1; 248 } 249 250 // update the progress bar animation, if active 251 // skip this if we have a text overlay (too expensive to update) 252 if (progressBarType == INDETERMINATE && !show_text) { 253 redraw = 1; 254 } 255 256 // move the progress bar forward on timed intervals, if configured 257 int duration = progressScopeDuration; 258 if (progressBarType == DETERMINATE && duration > 0) { 259 double elapsed = now() - progressScopeTime; 260 float p = 1.0 * elapsed / duration; 261 if (p > 1.0) p = 1.0; 262 if (p > progress) { 263 progress = p; 264 redraw = 1; 265 } 266 } 267 268 if (redraw) update_progress_locked(); 269 270 pthread_mutex_unlock(&updateMutex); 271 double end = now(); 272 // minimum of 20ms delay between frames 273 double delay = interval - (end-start); 274 if (delay < 0.02) delay = 0.02; 275 usleep((long)(delay * 1000000)); 276 } 277 } 278 279 void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) { 280 int result = res_create_surface(filename, surface); 281 if (result < 0) { 282 LOGE("missing bitmap %s\n(Code %d)\n", filename, result); 283 } 284 } 285 286 void ScreenRecoveryUI::Init() 287 { 288 gr_init(); 289 290 text_col = text_row = 0; 291 text_rows = gr_fb_height() / CHAR_HEIGHT; 292 if (text_rows > kMaxRows) text_rows = kMaxRows; 293 text_top = 1; 294 295 text_cols = gr_fb_width() / CHAR_WIDTH; 296 if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; 297 298 LoadBitmap("icon_installing", &backgroundIcon[INSTALLING]); 299 LoadBitmap("icon_error", &backgroundIcon[ERROR]); 300 LoadBitmap("progress_empty", &progressBarEmpty); 301 LoadBitmap("progress_fill", &progressBarFill); 302 303 int i; 304 305 progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames * 306 sizeof(gr_surface)); 307 for (i = 0; i < indeterminate_frames; ++i) { 308 char filename[40]; 309 // "indeterminate01.png", "indeterminate02.png", ... 310 sprintf(filename, "indeterminate%02d", i+1); 311 LoadBitmap(filename, progressBarIndeterminate+i); 312 } 313 314 if (installing_frames > 0) { 315 installationOverlay = (gr_surface*)malloc(installing_frames * 316 sizeof(gr_surface)); 317 for (i = 0; i < installing_frames; ++i) { 318 char filename[40]; 319 // "icon_installing_overlay01.png", 320 // "icon_installing_overlay02.png", ... 321 sprintf(filename, "icon_installing_overlay%02d", i+1); 322 LoadBitmap(filename, installationOverlay+i); 323 } 324 325 // Adjust the offset to account for the positioning of the 326 // base image on the screen. 327 if (backgroundIcon[INSTALLING] != NULL) { 328 gr_surface bg = backgroundIcon[INSTALLING]; 329 install_overlay_offset_x += (gr_fb_width() - gr_get_width(bg)) / 2; 330 install_overlay_offset_y += (gr_fb_height() - gr_get_height(bg)) / 2; 331 } 332 } else { 333 installationOverlay = NULL; 334 } 335 336 pthread_create(&progress_t, NULL, progress_thread, NULL); 337 338 RecoveryUI::Init(); 339 } 340 341 void ScreenRecoveryUI::SetBackground(Icon icon) 342 { 343 pthread_mutex_lock(&updateMutex); 344 currentIcon = icon; 345 update_screen_locked(); 346 pthread_mutex_unlock(&updateMutex); 347 } 348 349 void ScreenRecoveryUI::SetProgressType(ProgressType type) 350 { 351 pthread_mutex_lock(&updateMutex); 352 if (progressBarType != type) { 353 progressBarType = type; 354 update_progress_locked(); 355 } 356 progressScopeStart = 0; 357 progress = 0; 358 pthread_mutex_unlock(&updateMutex); 359 } 360 361 void ScreenRecoveryUI::ShowProgress(float portion, float seconds) 362 { 363 pthread_mutex_lock(&updateMutex); 364 progressBarType = DETERMINATE; 365 progressScopeStart += progressScopeSize; 366 progressScopeSize = portion; 367 progressScopeTime = now(); 368 progressScopeDuration = seconds; 369 progress = 0; 370 update_progress_locked(); 371 pthread_mutex_unlock(&updateMutex); 372 } 373 374 void ScreenRecoveryUI::SetProgress(float fraction) 375 { 376 pthread_mutex_lock(&updateMutex); 377 if (fraction < 0.0) fraction = 0.0; 378 if (fraction > 1.0) fraction = 1.0; 379 if (progressBarType == DETERMINATE && fraction > progress) { 380 // Skip updates that aren't visibly different. 381 int width = gr_get_width(progressBarIndeterminate[0]); 382 float scale = width * progressScopeSize; 383 if ((int) (progress * scale) != (int) (fraction * scale)) { 384 progress = fraction; 385 update_progress_locked(); 386 } 387 } 388 pthread_mutex_unlock(&updateMutex); 389 } 390 391 void ScreenRecoveryUI::Print(const char *fmt, ...) 392 { 393 char buf[256]; 394 va_list ap; 395 va_start(ap, fmt); 396 vsnprintf(buf, 256, fmt, ap); 397 va_end(ap); 398 399 fputs(buf, stdout); 400 401 // This can get called before ui_init(), so be careful. 402 pthread_mutex_lock(&updateMutex); 403 if (text_rows > 0 && text_cols > 0) { 404 char *ptr; 405 for (ptr = buf; *ptr != '\0'; ++ptr) { 406 if (*ptr == '\n' || text_col >= text_cols) { 407 text[text_row][text_col] = '\0'; 408 text_col = 0; 409 text_row = (text_row + 1) % text_rows; 410 if (text_row == text_top) text_top = (text_top + 1) % text_rows; 411 } 412 if (*ptr != '\n') text[text_row][text_col++] = *ptr; 413 } 414 text[text_row][text_col] = '\0'; 415 update_screen_locked(); 416 } 417 pthread_mutex_unlock(&updateMutex); 418 } 419 420 void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items, 421 int initial_selection) { 422 int i; 423 pthread_mutex_lock(&updateMutex); 424 if (text_rows > 0 && text_cols > 0) { 425 for (i = 0; i < text_rows; ++i) { 426 if (headers[i] == NULL) break; 427 strncpy(menu[i], headers[i], text_cols-1); 428 menu[i][text_cols-1] = '\0'; 429 } 430 menu_top = i; 431 for (; i < text_rows; ++i) { 432 if (items[i-menu_top] == NULL) break; 433 strncpy(menu[i], items[i-menu_top], text_cols-1); 434 menu[i][text_cols-1] = '\0'; 435 } 436 menu_items = i - menu_top; 437 show_menu = 1; 438 menu_sel = initial_selection; 439 update_screen_locked(); 440 } 441 pthread_mutex_unlock(&updateMutex); 442 } 443 444 int ScreenRecoveryUI::SelectMenu(int sel) { 445 int old_sel; 446 pthread_mutex_lock(&updateMutex); 447 if (show_menu > 0) { 448 old_sel = menu_sel; 449 menu_sel = sel; 450 if (menu_sel < 0) menu_sel = 0; 451 if (menu_sel >= menu_items) menu_sel = menu_items-1; 452 sel = menu_sel; 453 if (menu_sel != old_sel) update_screen_locked(); 454 } 455 pthread_mutex_unlock(&updateMutex); 456 return sel; 457 } 458 459 void ScreenRecoveryUI::EndMenu() { 460 int i; 461 pthread_mutex_lock(&updateMutex); 462 if (show_menu > 0 && text_rows > 0 && text_cols > 0) { 463 show_menu = 0; 464 update_screen_locked(); 465 } 466 pthread_mutex_unlock(&updateMutex); 467 } 468 469 bool ScreenRecoveryUI::IsTextVisible() 470 { 471 pthread_mutex_lock(&updateMutex); 472 int visible = show_text; 473 pthread_mutex_unlock(&updateMutex); 474 return visible; 475 } 476 477 bool ScreenRecoveryUI::WasTextEverVisible() 478 { 479 pthread_mutex_lock(&updateMutex); 480 int ever_visible = show_text_ever; 481 pthread_mutex_unlock(&updateMutex); 482 return ever_visible; 483 } 484 485 void ScreenRecoveryUI::ShowText(bool visible) 486 { 487 pthread_mutex_lock(&updateMutex); 488 show_text = visible; 489 if (show_text) show_text_ever = 1; 490 update_screen_locked(); 491 pthread_mutex_unlock(&updateMutex); 492 } 493