From 6809c51f8db86f1ab00a076cf570439d11920d3a Mon Sep 17 00:00:00 2001 From: Doug Zongker Date: Tue, 1 Mar 2011 14:04:34 -0800 Subject: [PATCH] make recovery UI images more general; allow for installation animation Change some of the UI parameters (# of indeterminate progress bar frames, fps, etc.) from #defined constants to variables that can be set by the device-specific recovery_ui code (via a new function). Support overlaying different images on top of the base installation icon to animate it. Make the FPS control more accurate. Change-Id: I9268b389b7ea6b3ed9e0c7eae37baf4272e60edd --- common.h | 19 ++++ default_recovery_ui.c | 3 + minui/resources.c | 2 + recovery.c | 3 + recovery_ui.h | 6 ++ ui.c | 198 ++++++++++++++++++++++++++++++------------ 6 files changed, 175 insertions(+), 56 deletions(-) diff --git a/common.h b/common.h index e6e8f85..cba4c86 100644 --- a/common.h +++ b/common.h @@ -107,4 +107,23 @@ typedef struct { // (that much). } Volume; +typedef struct { + // number of frames in indeterminate progress bar animation + int indeterminate_frames; + + // number of frames per second to try to maintain when animating + int update_fps; + + // number of frames in installing animation. may be zero for a + // static installation icon. + int installing_frames; + + // the install icon is animated by drawing images containing the + // changing part over the base icon. These specify the + // coordinates of the upper-left corner. + int install_overlay_offset_x; + int install_overlay_offset_y; + +} UIParameters; + #endif // RECOVERY_COMMON_H diff --git a/default_recovery_ui.c b/default_recovery_ui.c index bcba888..7c4017e 100644 --- a/default_recovery_ui.c +++ b/default_recovery_ui.c @@ -29,6 +29,9 @@ char* MENU_ITEMS[] = { "reboot system now", "wipe cache partition", NULL }; +void device_ui_init(UIParameters* ui_parameters) { +} + int device_recovery_start() { return 0; } diff --git a/minui/resources.c b/minui/resources.c index 3d2c727..2170dca 100644 --- a/minui/resources.c +++ b/minui/resources.c @@ -49,6 +49,8 @@ int res_create_surface(const char* name, gr_surface* pSurface) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; + *pSurface = NULL; + snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name); resPath[sizeof(resPath)-1] = '\0'; FILE* fp = fopen(resPath, "rb"); diff --git a/recovery.c b/recovery.c index 671cfbe..14fc905 100644 --- a/recovery.c +++ b/recovery.c @@ -56,6 +56,8 @@ static const char *SDCARD_ROOT = "/sdcard"; static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload"; +extern UIParameters ui_parameters; // from ui.c + /* * The recovery tool communicates with the main system through /cache files. * /cache/recovery/command - INPUT - command line for tool, one arg per line @@ -688,6 +690,7 @@ main(int argc, char **argv) { freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL); printf("Starting recovery on %s", ctime(&start)); + device_ui_init(&ui_parameters); ui_init(); ui_set_background(BACKGROUND_ICON_INSTALLING); load_volume_table(); diff --git a/recovery_ui.h b/recovery_ui.h index 77ce7f9..e56a24b 100644 --- a/recovery_ui.h +++ b/recovery_ui.h @@ -17,6 +17,12 @@ #ifndef _RECOVERY_UI_H #define _RECOVERY_UI_H +#include "common.h" + +// Called before UI library is initialized. Can change things like +// how many frames are included in various animations, etc. +extern void device_ui_init(UIParameters* ui_parameters); + // Called when recovery starts up. Returns 0. extern int device_recovery_start(); diff --git a/ui.c b/ui.c index 82004f0..054e53f 100644 --- a/ui.c +++ b/ui.c @@ -36,32 +36,32 @@ #define CHAR_WIDTH 10 #define CHAR_HEIGHT 18 -#define PROGRESSBAR_INDETERMINATE_STATES 6 -#define PROGRESSBAR_INDETERMINATE_FPS 15 - #define UI_WAIT_KEY_TIMEOUT_SEC 120 +UIParameters ui_parameters = { + 6, // indeterminate progress bar frames + 15, // fps + 0, // installation icon frames (0 == static image) + 0, 0, // installation icon overlay offset +}; + static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER; static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS]; -static gr_surface gProgressBarIndeterminate[PROGRESSBAR_INDETERMINATE_STATES]; +static gr_surface *gInstallationOverlay; +static gr_surface *gProgressBarIndeterminate; static gr_surface gProgressBarEmpty; static gr_surface gProgressBarFill; static const struct { gr_surface* surface; const char *name; } BITMAPS[] = { { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" }, { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" }, - { &gProgressBarIndeterminate[0], "indeterminate1" }, - { &gProgressBarIndeterminate[1], "indeterminate2" }, - { &gProgressBarIndeterminate[2], "indeterminate3" }, - { &gProgressBarIndeterminate[3], "indeterminate4" }, - { &gProgressBarIndeterminate[4], "indeterminate5" }, - { &gProgressBarIndeterminate[5], "indeterminate6" }, { &gProgressBarEmpty, "progress_empty" }, { &gProgressBarFill, "progress_fill" }, { NULL, NULL }, }; -static gr_surface gCurrentIcon = NULL; +static int gCurrentIcon = 0; +static int gInstallingFrame = 0; static enum ProgressBarType { PROGRESSBAR_TYPE_NONE, @@ -71,7 +71,7 @@ static enum ProgressBarType { // Progress bar scope of current operation static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0; -static time_t gProgressScopeTime, gProgressScopeDuration; +static double gProgressScopeTime, gProgressScopeDuration; // Set to 1 when both graphics pages are the same (except for the progress bar) static int gPagesIdentical = 0; @@ -93,20 +93,46 @@ static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER; static int key_queue[256], key_queue_len = 0; static volatile char key_pressed[KEY_MAX + 1]; +// Return the current time as a double (including fractions of a second). +static double now() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +// Draw the given frame over the installation overlay animation. The +// background is not cleared or draw with the base icon first; we +// assume that the frame already contains some other frame of the +// animation. Does nothing if no overlay animation is defined. +// Should only be called with gUpdateMutex locked. +static void draw_install_overlay_locked(int frame) { + if (gInstallationOverlay == NULL) return; + gr_surface surface = gInstallationOverlay[frame]; + int iconWidth = gr_get_width(surface); + int iconHeight = gr_get_height(surface); + gr_blit(surface, 0, 0, iconWidth, iconHeight, + ui_parameters.install_overlay_offset_x, + ui_parameters.install_overlay_offset_y); +} + // Clear the screen and draw the currently selected background icon (if any). // Should only be called with gUpdateMutex locked. -static void draw_background_locked(gr_surface icon) +static void draw_background_locked(int icon) { gPagesIdentical = 0; gr_color(0, 0, 0, 255); gr_fill(0, 0, gr_fb_width(), gr_fb_height()); if (icon) { - int iconWidth = gr_get_width(icon); - int iconHeight = gr_get_height(icon); + gr_surface surface = gBackgroundIcon[icon]; + int iconWidth = gr_get_width(surface); + int iconHeight = gr_get_height(surface); int iconX = (gr_fb_width() - iconWidth) / 2; int iconY = (gr_fb_height() - iconHeight) / 2; - gr_blit(icon, 0, 0, iconWidth, iconHeight, iconX, iconY); + gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); + if (icon == BACKGROUND_ICON_INSTALLING) { + draw_install_overlay_locked(gInstallingFrame); + } } } @@ -114,35 +140,39 @@ static void draw_background_locked(gr_surface icon) // Should only be called with gUpdateMutex locked. static void draw_progress_locked() { - if (gProgressBarType == PROGRESSBAR_TYPE_NONE) return; - - int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]); - int width = gr_get_width(gProgressBarEmpty); - int height = gr_get_height(gProgressBarEmpty); - - int dx = (gr_fb_width() - width)/2; - int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; - - // Erase behind the progress bar (in case this was a progress-only update) - gr_color(0, 0, 0, 255); - gr_fill(dx, dy, width, height); - - if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) { - float progress = gProgressScopeStart + gProgress * gProgressScopeSize; - int pos = (int) (progress * width); - - if (pos > 0) { - gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy); - } - if (pos < width-1) { - gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); - } + if (gCurrentIcon == BACKGROUND_ICON_INSTALLING) { + draw_install_overlay_locked(gInstallingFrame); } - if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) { - static int frame = 0; - gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy); - frame = (frame + 1) % PROGRESSBAR_INDETERMINATE_STATES; + if (gProgressBarType != PROGRESSBAR_TYPE_NONE) { + int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]); + int width = gr_get_width(gProgressBarEmpty); + int height = gr_get_height(gProgressBarEmpty); + + int dx = (gr_fb_width() - width)/2; + int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; + + // Erase behind the progress bar (in case this was a progress-only update) + gr_color(0, 0, 0, 255); + gr_fill(dx, dy, width, height); + + if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) { + float progress = gProgressScopeStart + gProgress * gProgressScopeSize; + int pos = (int) (progress * width); + + if (pos > 0) { + gr_blit(gProgressBarFill, 0, 0, pos, height, dx, dy); + } + if (pos < width-1) { + gr_blit(gProgressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); + } + } + + if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) { + static int frame = 0; + gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy); + frame = (frame + 1) % ui_parameters.indeterminate_frames; + } } } @@ -207,7 +237,7 @@ static void update_progress_locked(void) draw_screen_locked(); // Must redraw the whole screen gPagesIdentical = 1; } else { - draw_progress_locked(); // Draw only the progress bar + draw_progress_locked(); // Draw only the progress bar and overlays } gr_flip(); } @@ -215,29 +245,49 @@ static void update_progress_locked(void) // Keeps the progress bar updated, even when the process is otherwise busy. static void *progress_thread(void *cookie) { + double interval = 1.0 / ui_parameters.update_fps; for (;;) { - usleep(1000000 / PROGRESSBAR_INDETERMINATE_FPS); + double start = now(); pthread_mutex_lock(&gUpdateMutex); + int redraw = 0; + + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if (gCurrentIcon == BACKGROUND_ICON_INSTALLING && + ui_parameters.installing_frames > 0 && + !show_text) { + gInstallingFrame = + (gInstallingFrame + 1) % ui_parameters.installing_frames; + redraw = 1; + } + // update the progress bar animation, if active // skip this if we have a text overlay (too expensive to update) if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) { - update_progress_locked(); + redraw = 1; } // move the progress bar forward on timed intervals, if configured int duration = gProgressScopeDuration; if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) { - int elapsed = time(NULL) - gProgressScopeTime; + double elapsed = now() - gProgressScopeTime; float progress = 1.0 * elapsed / duration; if (progress > 1.0) progress = 1.0; if (progress > gProgress) { gProgress = progress; - update_progress_locked(); + redraw = 1; } } + if (redraw) update_progress_locked(); + pthread_mutex_unlock(&gUpdateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end-start); + if (delay < 0.02) delay = 0.02; + usleep((long)(delay * 1000000)); } return NULL; } @@ -328,15 +378,51 @@ void ui_init(void) for (i = 0; BITMAPS[i].name != NULL; ++i) { int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface); if (result < 0) { - if (result == -2) { - LOGI("Bitmap %s missing header\n", BITMAPS[i].name); - } else { - LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result); - } - *BITMAPS[i].surface = NULL; + LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result); } } + gProgressBarIndeterminate = malloc(ui_parameters.indeterminate_frames * + sizeof(gr_surface)); + for (i = 0; i < ui_parameters.indeterminate_frames; ++i) { + char filename[40]; + // "indeterminateN" if fewer than 10 frames, else "indeterminateNN". + sprintf(filename, "indeterminate%0*d", + ui_parameters.indeterminate_frames < 10 ? 1 : 2, + i+1); + int result = res_create_surface(filename, gProgressBarIndeterminate+i); + if (result < 0) { + LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); + } + } + + if (ui_parameters.installing_frames > 0) { + gInstallationOverlay = malloc(ui_parameters.installing_frames * + sizeof(gr_surface)); + for (i = 0; i < ui_parameters.installing_frames; ++i) { + char filename[40]; + sprintf(filename, "icon_installing_overlay%0*d", + ui_parameters.installing_frames < 10 ? 1 : 2, + i+1); + int result = res_create_surface(filename, gInstallationOverlay+i); + if (result < 0) { + LOGE("Missing bitmap %s\n(Code %d)\n", filename, result); + } + } + + // Adjust the offset to account for the positioning of the + // base image on the screen. + if (gBackgroundIcon[BACKGROUND_ICON_INSTALLING] != NULL) { + gr_surface bg = gBackgroundIcon[BACKGROUND_ICON_INSTALLING]; + ui_parameters.install_overlay_offset_x += + (gr_fb_width() - gr_get_width(bg)) / 2; + ui_parameters.install_overlay_offset_y += + (gr_fb_height() - gr_get_height(bg)) / 2; + } + } else { + gInstallationOverlay = NULL; + } + pthread_t t; pthread_create(&t, NULL, progress_thread, NULL); pthread_create(&t, NULL, input_thread, NULL); @@ -345,7 +431,7 @@ void ui_init(void) void ui_set_background(int icon) { pthread_mutex_lock(&gUpdateMutex); - gCurrentIcon = gBackgroundIcon[icon]; + gCurrentIcon = icon; update_screen_locked(); pthread_mutex_unlock(&gUpdateMutex); } @@ -366,7 +452,7 @@ void ui_show_progress(float portion, int seconds) gProgressBarType = PROGRESSBAR_TYPE_NORMAL; gProgressScopeStart += gProgressScopeSize; gProgressScopeSize = portion; - gProgressScopeTime = time(NULL); + gProgressScopeTime = now(); gProgressScopeDuration = seconds; gProgress = 0; update_progress_locked();