From 02ec6b88ed4e6cf40cc257572b07c7277b7b6341 Mon Sep 17 00:00:00 2001 From: Doug Zongker Date: Wed, 22 Aug 2012 17:26:40 -0700 Subject: add simple text to recovery UI - recovery takes a --locale argument, which will be passed by the main system - the locale is saved in cache, in case the --locale argument is missing (eg, when recovery is started from fastboot) - we include images that have prerendered text for many locales - we split the background states into four (installing update, erasing, no command, error) so that appropriate text can be shown. Change-Id: I731b8108e83d5ccc09a4aacfc1dbf7e86b397aaf --- install.cpp | 2 +- install.h | 2 +- minui/graphics.c | 16 +++++ minui/minui.h | 2 + minui/resources.c | 150 +++++++++++++++++++++++++++++++++++++++++ recovery.cpp | 63 ++++++++++++++--- res/images/erasing_text.png | Bin 0 -> 4511 bytes res/images/error_text.png | Bin 0 -> 3119 bytes res/images/installing_text.png | Bin 0 -> 6433 bytes res/images/no_command_text.png | Bin 0 -> 2528 bytes screen_ui.cpp | 61 ++++++++++++----- screen_ui.h | 6 +- ui.h | 2 +- 13 files changed, 274 insertions(+), 30 deletions(-) create mode 100644 res/images/erasing_text.png create mode 100644 res/images/error_text.png create mode 100644 res/images/installing_text.png create mode 100644 res/images/no_command_text.png diff --git a/install.cpp b/install.cpp index 819650e1f..b8f478130 100644 --- a/install.cpp +++ b/install.cpp @@ -277,7 +277,7 @@ exit: static int really_install_package(const char *path, int* wipe_cache) { - ui->SetBackground(RecoveryUI::INSTALLING); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); ui->Print("Finding update package...\n"); ui->SetProgressType(RecoveryUI::INDETERMINATE); LOGI("Update location: %s\n", path); diff --git a/install.h b/install.h index 1943f02d3..2ada529ef 100644 --- a/install.h +++ b/install.h @@ -23,7 +23,7 @@ extern "C" { #endif -enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; +enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE }; // Install the package specified by root_path. If INSTALL_SUCCESS is // returned and *wipe_cache is true on exit, caller should wipe the // cache partition. diff --git a/minui/graphics.c b/minui/graphics.c index 81f13ad2c..88572a878 100644 --- a/minui/graphics.c +++ b/minui/graphics.c @@ -244,6 +244,22 @@ int gr_text(int x, int y, const char *s) return x; } +void gr_texticon(int x, int y, gr_surface icon) { + GGLContext* gl = gr_context; + + gl->bindTexture(gl, (GGLSurface*) icon); + gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); + gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); + gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); + gl->enable(gl, GGL_TEXTURE_2D); + + int w = gr_get_width(icon); + int h = gr_get_height(icon); + + gl->texCoord2i(gl, -x, -y); + gl->recti(gl, x, y, x+gr_get_width(icon), y+gr_get_height(icon)); +} + void gr_fill(int x, int y, int w, int h) { GGLContext *gl = gr_context; diff --git a/minui/minui.h b/minui/minui.h index 74da4e9c0..767ffcb50 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -38,6 +38,7 @@ void gr_fb_blank(bool blank); void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); void gr_fill(int x, int y, int w, int h); int gr_text(int x, int y, const char *s); + void gr_texticon(int x, int y, gr_surface icon); int gr_measure(const char *s); void gr_font_size(int *x, int *y); @@ -71,6 +72,7 @@ void ev_dispatch(void); // Returns 0 if no error, else negative. int res_create_surface(const char* name, gr_surface* pSurface); +int res_create_localized_surface(const char* name, gr_surface* pSurface); void res_free_surface(gr_surface surface); #ifdef __cplusplus diff --git a/minui/resources.c b/minui/resources.c index b437a87cb..af8720a56 100644 --- a/minui/resources.c +++ b/minui/resources.c @@ -33,6 +33,8 @@ #include "minui.h" +extern char* locale; + // libpng gives "undefined reference to 'pow'" errors, and I have no // idea how to convince the build system to link with -lm. We don't // need this functionality (it's used for gamma adjustment) so provide @@ -173,6 +175,154 @@ exit: return result; } +static int matches_locale(const char* loc) { + if (locale == NULL) return 0; + + printf("matching loc=[%s] vs locale=[%s]\n", loc, locale); + + if (strcmp(loc, locale) == 0) return 1; + + // if loc does *not* have an underscore, and it matches the start + // of locale, and the next character in locale *is* an underscore, + // that's a match. For instance, loc == "en" matches locale == + // "en_US". + + int i; + for (i = 0; loc[i] != 0 && loc[i] != '_'; ++i); + if (loc[i] == '_') return 0; + printf(" partial match possible; i = %d\n", i); + + return (strncmp(locale, loc, i) == 0 && locale[i] == '_'); +} + +int res_create_localized_surface(const char* name, gr_surface* pSurface) { + char resPath[256]; + GGLSurface* surface = NULL; + int result = 0; + unsigned char header[8]; + 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"); + if (fp == NULL) { + result = -1; + goto exit; + } + + size_t bytesRead = fread(header, 1, sizeof(header), fp); + if (bytesRead != sizeof(header)) { + result = -2; + goto exit; + } + + if (png_sig_cmp(header, 0, sizeof(header))) { + result = -3; + goto exit; + } + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + result = -4; + goto exit; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + result = -5; + goto exit; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + result = -6; + goto exit; + } + + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, sizeof(header)); + png_read_info(png_ptr, info_ptr); + + size_t width = info_ptr->width; + size_t height = info_ptr->height; + size_t stride = 4 * width; + + printf("image size is %d x %d\n", width, height); + + int color_type = info_ptr->color_type; + int bit_depth = info_ptr->bit_depth; + int channels = info_ptr->channels; + printf("color_type %d bit_depth %d channels %d\n", + color_type, bit_depth, channels); + + if (!(bit_depth == 8 && + (channels == 1 && color_type == PNG_COLOR_TYPE_GRAY))) { + return -7; + printf("exiting -7\n"); + goto exit; + } + + unsigned char* row = malloc(width); + int y; + for (y = 0; y < height; ++y) { + png_read_row(png_ptr, row, NULL); + int w = (row[1] << 8) | row[0]; + int h = (row[3] << 8) | row[2]; + int len = row[4]; + char* loc = row+5; + + printf("row %d: %s %d x %d\n", y, loc, w, h); + + if (y+1+h >= height || matches_locale(loc)) { + printf(" taking this one\n"); + + surface = malloc(sizeof(GGLSurface)); + if (surface == NULL) { + result = -8; + goto exit; + } + unsigned char* pData = malloc(w*h); + + surface->version = sizeof(GGLSurface); + surface->width = w; + surface->height = h; + surface->stride = w; /* Yes, pixels, not bytes */ + surface->data = pData; + surface->format = GGL_PIXEL_FORMAT_A_8; + + int i; + for (i = 0; i < h; ++i, ++y) { + png_read_row(png_ptr, row, NULL); + memcpy(pData + i*w, row, w); + } + + *pSurface = (gr_surface) surface; + break; + } else { + printf(" skipping\n"); + int i; + for (i = 0; i < h; ++i, ++y) { + png_read_row(png_ptr, row, NULL); + } + } + } + +exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + if (fp != NULL) { + fclose(fp); + } + if (result < 0) { + if (surface) { + free(surface); + } + } + return result; +} + void res_free_surface(gr_surface surface) { GGLSurface* pSurface = (GGLSurface*) surface; if (pSurface) { diff --git a/recovery.cpp b/recovery.cpp index ce4358a53..e374c7d5a 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -54,6 +54,7 @@ static const struct option OPTIONS[] = { { "wipe_cache", no_argument, NULL, 'c' }, { "show_text", no_argument, NULL, 't' }, { "just_exit", no_argument, NULL, 'x' }, + { "locale", required_argument, NULL, 'l' }, { NULL, 0, NULL, 0 }, }; @@ -62,6 +63,7 @@ static const char *INTENT_FILE = "/cache/recovery/intent"; static const char *LOG_FILE = "/cache/recovery/log"; static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; +static const char *LOCALE_FILE = "/cache/recovery/locale"; static const char *CACHE_ROOT = "/cache"; static const char *SDCARD_ROOT = "/sdcard"; static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; @@ -69,6 +71,7 @@ static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload"; RecoveryUI* ui = NULL; +char* locale = NULL; /* * The recovery tool communicates with the main system through /cache files. @@ -283,6 +286,15 @@ finish_recovery(const char *send_intent) { chmod(LAST_LOG_FILE, 0640); chmod(LAST_INSTALL_FILE, 0644); + // Save the locale to cache, so if recovery is next started up + // without a --locale argument (eg, directly from the bootloader) + // it will use the last-known locale. + if (locale != NULL) { + FILE* fp = fopen(LOCALE_FILE, "w"); + fwrite(locale, 1, strlen(locale), fp); + fclose(fp); + } + // Reset to normal system boot so recovery won't cycle indefinitely. struct bootloader_message boot; memset(&boot, 0, sizeof(boot)); @@ -300,7 +312,7 @@ finish_recovery(const char *send_intent) { static int erase_volume(const char *volume) { - ui->SetBackground(RecoveryUI::INSTALLING); + ui->SetBackground(RecoveryUI::ERASING); ui->SetProgressType(RecoveryUI::INDETERMINATE); ui->Print("Formatting %s...\n", volume); @@ -658,6 +670,7 @@ prompt_and_wait(Device* device) { for (;;) { finish_recovery(NULL); + ui->SetBackground(RecoveryUI::NO_COMMAND); ui->SetProgressType(RecoveryUI::EMPTY); int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device); @@ -679,6 +692,7 @@ prompt_and_wait(Device* device) { break; case Device::WIPE_CACHE: + ui->ShowText(false); ui->Print("\n-- Wiping cache...\n"); erase_volume("/cache"); ui->Print("Cache wipe complete.\n"); @@ -757,6 +771,24 @@ print_property(const char *key, const char *name, void *cookie) { printf("%s=%s\n", key, name); } +static void +load_locale_from_cache() { + FILE* fp = fopen_path(LOCALE_FILE, "r"); + char buffer[80]; + if (fp != NULL) { + fgets(buffer, sizeof(buffer), fp); + int j = 0; + int i; + for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) { + if (!isspace(buffer[i])) { + buffer[j++] = buffer[i]; + } + } + buffer[j] = 0; + locale = strdup(buffer); + } +} + int main(int argc, char **argv) { time_t start = time(NULL); @@ -779,18 +811,13 @@ main(int argc, char **argv) { printf("Starting recovery on %s", ctime(&start)); - Device* device = make_device(); - ui = device->GetUI(); - - ui->Init(); - ui->SetBackground(RecoveryUI::NONE); load_volume_table(); get_args(&argc, &argv); int previous_runs = 0; const char *send_intent = NULL; const char *update_package = NULL; - int wipe_data = 0, wipe_cache = 0; + int wipe_data = 0, wipe_cache = 0, show_text = 0; bool just_exit = false; int arg; @@ -801,14 +828,27 @@ main(int argc, char **argv) { case 'u': update_package = optarg; break; case 'w': wipe_data = wipe_cache = 1; break; case 'c': wipe_cache = 1; break; - case 't': ui->ShowText(true); break; + case 't': show_text = 1; break; case 'x': just_exit = true; break; + case 'l': locale = optarg; break; case '?': LOGE("Invalid command argument\n"); continue; } } + if (locale == NULL) { + load_locale_from_cache(); + } + printf("locale is [%s]\n", locale); + + Device* device = make_device(); + ui = device->GetUI(); + + ui->Init(); + ui->SetBackground(RecoveryUI::NONE); + if (show_text) ui->ShowText(true); + #ifdef HAVE_SELINUX struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/file_contexts" } @@ -868,10 +908,13 @@ main(int argc, char **argv) { if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n"); } else if (!just_exit) { - status = INSTALL_ERROR; // No command specified + status = INSTALL_NONE; // No command specified + ui->SetBackground(RecoveryUI::NO_COMMAND); } - if (status != INSTALL_SUCCESS) ui->SetBackground(RecoveryUI::ERROR); + if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) { + ui->SetBackground(RecoveryUI::ERROR); + } if (status != INSTALL_SUCCESS || ui->IsTextVisible()) { prompt_and_wait(device); } diff --git a/res/images/erasing_text.png b/res/images/erasing_text.png new file mode 100644 index 000000000..f92119772 Binary files /dev/null and b/res/images/erasing_text.png differ diff --git a/res/images/error_text.png b/res/images/error_text.png new file mode 100644 index 000000000..581347094 Binary files /dev/null and b/res/images/error_text.png differ diff --git a/res/images/installing_text.png b/res/images/installing_text.png new file mode 100644 index 000000000..c48a45295 Binary files /dev/null and b/res/images/installing_text.png differ diff --git a/res/images/no_command_text.png b/res/images/no_command_text.png new file mode 100644 index 000000000..1d6a5b7cf Binary files /dev/null and b/res/images/no_command_text.png differ diff --git a/screen_ui.cpp b/screen_ui.cpp index 3c6c3ae25..bb879dfac 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -94,7 +94,7 @@ void ScreenRecoveryUI::draw_install_overlay_locked(int frame) { int iconWidth = gr_get_width(surface); int iconHeight = gr_get_height(surface); gr_blit(surface, 0, 0, iconWidth, iconHeight, - install_overlay_offset_x, install_overlay_offset_y); + overlay_offset_x, overlay_offset_y); } // Clear the screen and draw the currently selected background icon (if any). @@ -107,14 +107,26 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon) if (icon) { gr_surface surface = backgroundIcon[icon]; + gr_surface text_surface = backgroundText[icon]; + int iconWidth = gr_get_width(surface); int iconHeight = gr_get_height(surface); + int textWidth = gr_get_width(text_surface); + int textHeight = gr_get_height(text_surface); + int iconX = (gr_fb_width() - iconWidth) / 2; - int iconY = (gr_fb_height() - iconHeight) / 2; + int iconY = (gr_fb_height() - (iconHeight+textHeight+40)) / 2; + + int textX = (gr_fb_width() - textWidth) / 2; + int textY = ((gr_fb_height() - (iconHeight+textHeight+40)) / 2) + iconHeight + 40; + gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY); - if (icon == INSTALLING) { + if (icon == INSTALLING_UPDATE || icon == ERASING) { draw_install_overlay_locked(installingFrame); } + + gr_color(255, 255, 255, 255); + gr_texticon(textX, textY, text_surface); } } @@ -124,12 +136,12 @@ void ScreenRecoveryUI::draw_progress_locked() { if (currentIcon == ERROR) return; - if (currentIcon == INSTALLING) { + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { draw_install_overlay_locked(installingFrame); } if (progressBarType != EMPTY) { - int iconHeight = gr_get_height(backgroundIcon[INSTALLING]); + int iconHeight = gr_get_height(backgroundIcon[INSTALLING_UPDATE]); int width = gr_get_width(progressBarEmpty); int height = gr_get_height(progressBarEmpty); @@ -242,7 +254,8 @@ void ScreenRecoveryUI::progress_loop() { // update the installation animation, if active // skip this if we have a text overlay (too expensive to update) - if (currentIcon == INSTALLING && installing_frames > 0 && !show_text) { + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && + installing_frames > 0 && !show_text) { installingFrame = (installingFrame + 1) % installing_frames; redraw = 1; } @@ -283,6 +296,13 @@ void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) { } } +void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) { + int result = res_create_localized_surface(filename, surface); + if (result < 0) { + LOGE("missing bitmap %s\n(Code %d)\n", filename, result); + } +} + void ScreenRecoveryUI::Init() { gr_init(); @@ -295,11 +315,19 @@ void ScreenRecoveryUI::Init() text_cols = gr_fb_width() / CHAR_WIDTH; if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; - LoadBitmap("icon_installing", &backgroundIcon[INSTALLING]); + LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); + backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; LoadBitmap("icon_error", &backgroundIcon[ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); + LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); + LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); + LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); + LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); + int i; progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames * @@ -321,14 +349,6 @@ void ScreenRecoveryUI::Init() sprintf(filename, "icon_installing_overlay%02d", i+1); LoadBitmap(filename, installationOverlay+i); } - - // Adjust the offset to account for the positioning of the - // base image on the screen. - if (backgroundIcon[INSTALLING] != NULL) { - gr_surface bg = backgroundIcon[INSTALLING]; - install_overlay_offset_x += (gr_fb_width() - gr_get_width(bg)) / 2; - install_overlay_offset_y += (gr_fb_height() - gr_get_height(bg)) / 2; - } } else { installationOverlay = NULL; } @@ -343,6 +363,17 @@ void ScreenRecoveryUI::SetBackground(Icon icon) pthread_mutex_lock(&updateMutex); currentIcon = icon; update_screen_locked(); + + // Adjust the offset to account for the positioning of the + // base image on the screen. + if (backgroundIcon[icon] != NULL) { + gr_surface bg = backgroundIcon[icon]; + gr_surface text = backgroundText[icon]; + overlay_offset_x = install_overlay_offset_x + (gr_fb_width() - gr_get_width(bg)) / 2; + overlay_offset_y = install_overlay_offset_y + + (gr_fb_height() - (gr_get_height(bg) + gr_get_height(text) + 40)) / 2; + } + pthread_mutex_unlock(&updateMutex); } diff --git a/screen_ui.h b/screen_ui.h index 34929ee1a..16ee741b7 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -57,7 +57,8 @@ class ScreenRecoveryUI : public RecoveryUI { int installingFrame; pthread_mutex_t updateMutex; - gr_surface backgroundIcon[3]; + gr_surface backgroundIcon[5]; + gr_surface backgroundText[5]; gr_surface *installationOverlay; gr_surface *progressBarIndeterminate; gr_surface progressBarEmpty; @@ -92,6 +93,7 @@ class ScreenRecoveryUI : public RecoveryUI { int indeterminate_frames; int installing_frames; int install_overlay_offset_x, install_overlay_offset_y; + int overlay_offset_x, overlay_offset_y; void draw_install_overlay_locked(int frame); void draw_background_locked(Icon icon); @@ -104,7 +106,7 @@ class ScreenRecoveryUI : public RecoveryUI { void progress_loop(); void LoadBitmap(const char* filename, gr_surface* surface); - + void LoadLocalizedBitmap(const char* filename, gr_surface* surface); }; #endif // RECOVERY_UI_H diff --git a/ui.h b/ui.h index 0d3b7bb98..ccbb466ac 100644 --- a/ui.h +++ b/ui.h @@ -31,7 +31,7 @@ class RecoveryUI { virtual void Init(); // Set the overall recovery state ("background image"). - enum Icon { NONE, INSTALLING, ERROR }; + enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; virtual void SetBackground(Icon icon) = 0; // --- progress indicator --- -- cgit v1.2.3