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
diff --git a/install.cpp b/install.cpp
index 819650e..b8f4781 100644
--- a/install.cpp
+++ b/install.cpp
@@ -277,7 +277,7 @@
 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 1943f02..2ada529 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 81f13ad..88572a8 100644
--- a/minui/graphics.c
+++ b/minui/graphics.c
@@ -244,6 +244,22 @@
     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 74da4e9..767ffcb 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -38,6 +38,7 @@
 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 @@
 
 // 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 b437a87..af8720a 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 @@
     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 ce4358a..e374c7d 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -54,6 +54,7 @@
   { "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 *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 *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 @@
     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 @@
 
 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 @@
 
     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 @@
                 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 @@
     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 @@
 
     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 @@
         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 @@
         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 0000000..f921197
--- /dev/null
+++ b/res/images/erasing_text.png
Binary files differ
diff --git a/res/images/error_text.png b/res/images/error_text.png
new file mode 100644
index 0000000..5813470
--- /dev/null
+++ b/res/images/error_text.png
Binary files differ
diff --git a/res/images/installing_text.png b/res/images/installing_text.png
new file mode 100644
index 0000000..c48a452
--- /dev/null
+++ b/res/images/installing_text.png
Binary files differ
diff --git a/res/images/no_command_text.png b/res/images/no_command_text.png
new file mode 100644
index 0000000..1d6a5b7
--- /dev/null
+++ b/res/images/no_command_text.png
Binary files differ
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 3c6c3ae..bb879df 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -94,7 +94,7 @@
     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 @@
 
     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 @@
 {
     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 @@
 
         // 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::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 @@
     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 @@
             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 @@
     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 34929ee..16ee741 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -57,7 +57,8 @@
     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 @@
     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 @@
     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 0d3b7bb..ccbb466 100644
--- a/ui.h
+++ b/ui.h
@@ -31,7 +31,7 @@
     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 ---