Merge "Add a new option in recovery menu to test the background texts"
diff --git a/device.cpp b/device.cpp
index 6150186..f881daf 100644
--- a/device.cpp
+++ b/device.cpp
@@ -17,34 +17,36 @@
 #include "device.h"
 
 static const char* MENU_ITEMS[] = {
-    "Reboot system now",
-    "Reboot to bootloader",
-    "Apply update from ADB",
-    "Apply update from SD card",
-    "Wipe data/factory reset",
+  "Reboot system now",
+  "Reboot to bootloader",
+  "Apply update from ADB",
+  "Apply update from SD card",
+  "Wipe data/factory reset",
 #ifndef AB_OTA_UPDATER
-    "Wipe cache partition",
+  "Wipe cache partition",
 #endif  // !AB_OTA_UPDATER
-    "Mount /system",
-    "View recovery logs",
-    "Run graphics test",
-    "Power off",
-    NULL,
+  "Mount /system",
+  "View recovery logs",
+  "Run graphics test",
+  "Run locale test",
+  "Power off",
+  nullptr,
 };
 
 static const Device::BuiltinAction MENU_ACTIONS[] = {
-    Device::REBOOT,
-    Device::REBOOT_BOOTLOADER,
-    Device::APPLY_ADB_SIDELOAD,
-    Device::APPLY_SDCARD,
-    Device::WIPE_DATA,
+  Device::REBOOT,
+  Device::REBOOT_BOOTLOADER,
+  Device::APPLY_ADB_SIDELOAD,
+  Device::APPLY_SDCARD,
+  Device::WIPE_DATA,
 #ifndef AB_OTA_UPDATER
-    Device::WIPE_CACHE,
+  Device::WIPE_CACHE,
 #endif  // !AB_OTA_UPDATER
-    Device::MOUNT_SYSTEM,
-    Device::VIEW_RECOVERY_LOGS,
-    Device::RUN_GRAPHICS_TEST,
-    Device::SHUTDOWN,
+  Device::MOUNT_SYSTEM,
+  Device::VIEW_RECOVERY_LOGS,
+  Device::RUN_GRAPHICS_TEST,
+  Device::RUN_LOCALE_TEST,
+  Device::SHUTDOWN,
 };
 
 static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) ==
diff --git a/device.h b/device.h
index 639e2bf..74745b3 100644
--- a/device.h
+++ b/device.h
@@ -66,6 +66,7 @@
     VIEW_RECOVERY_LOGS = 9,
     MOUNT_SYSTEM = 10,
     RUN_GRAPHICS_TEST = 11,
+    RUN_LOCALE_TEST = 12,
   };
 
   // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 017ddde..27e6031 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -21,6 +21,7 @@
 
 #include <functional>
 #include <string>
+#include <vector>
 
 //
 // Graphics.
@@ -129,6 +130,9 @@
 int res_create_localized_alpha_surface(const char* name, const char* locale,
                                        GRSurface** pSurface);
 
+// Return a list of locale strings embedded in |png_name|. Return a empty list in case of failure.
+std::vector<std::string> get_locales_in_png(const std::string& png_name);
+
 // Free a surface allocated by any of the res_create_*_surface()
 // functions.
 void res_free_surface(GRSurface* surface);
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 8f8d36d..756f29d 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -396,6 +396,41 @@
   return std::regex_match(locale, loc_regex);
 }
 
+std::vector<std::string> get_locales_in_png(const std::string& png_name) {
+  png_structp png_ptr = nullptr;
+  png_infop info_ptr = nullptr;
+  png_uint_32 width, height;
+  png_byte channels;
+
+  int status = open_png(png_name.c_str(), &png_ptr, &info_ptr, &width, &height, &channels);
+  if (status < 0) {
+    printf("Failed to open %s\n", png_name.c_str());
+    return {};
+  }
+  if (channels != 1) {
+    printf("Expect input png to have 1 data channel, this file has %d\n", channels);
+    png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+    return {};
+  }
+
+  std::vector<std::string> result;
+  std::vector<unsigned char> row(width);
+  for (png_uint_32 y = 0; y < height; ++y) {
+    png_read_row(png_ptr, row.data(), nullptr);
+    int h = (row[3] << 8) | row[2];
+    std::string loc(reinterpret_cast<char*>(&row[5]));
+    if (!loc.empty()) {
+      result.push_back(loc);
+    }
+    for (int i = 0; i < h; ++i, ++y) {
+      png_read_row(png_ptr, row.data(), NULL);
+    }
+  }
+
+  png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+  return result;
+}
+
 int res_create_localized_alpha_surface(const char* name,
                                        const char* locale,
                                        GRSurface** pSurface) {
diff --git a/recovery.cpp b/recovery.cpp
index d037b79..076b449 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -1191,6 +1191,11 @@
         run_graphics_test();
         break;
 
+      case Device::RUN_LOCALE_TEST: {
+        ScreenRecoveryUI* screen_ui = static_cast<ScreenRecoveryUI*>(ui);
+        screen_ui->CheckBackgroundTextImages(locale);
+        break;
+      }
       case Device::MOUNT_SYSTEM:
         // For a system image built with the root directory (i.e. system_root_image == "true"), we
         // mount it to /system_root, and symlink /system to /system_root/system to make adb shell
diff --git a/screen_ui.cpp b/screen_ui.cpp
index d65d656..bc5c5c3 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -31,7 +31,9 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/logging.h>
@@ -258,6 +260,81 @@
   }
 }
 
+void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries,
+                                                   size_t sel) {
+  SetLocale(locales_entries[sel]);
+  std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text",
+                                         "installing_security_text", "no_command_text" };
+  std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces;
+  for (const auto& name : text_name) {
+    GRSurface* text_image = nullptr;
+    LoadLocalizedBitmap(name.c_str(), &text_image);
+    if (!text_image) {
+      Print("Failed to load %s\n", name.c_str());
+      return;
+    }
+    surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free));
+  }
+
+  pthread_mutex_lock(&updateMutex);
+  gr_color(0, 0, 0, 255);
+  gr_clear();
+
+  int text_y = kMarginHeight;
+  int text_x = kMarginWidth;
+  int line_spacing = gr_sys_font()->char_height;  // Put some extra space between images.
+  // Write the header and descriptive texts.
+  SetColor(INFO);
+  std::string header = "Show background text image";
+  text_y += DrawTextLine(text_x, text_y, header.c_str(), true);
+  std::string locale_selection = android::base::StringPrintf(
+      "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel, locales_entries.size());
+  const char* instruction[] = { locale_selection.c_str(),
+                                "Use volume up/down to switch locales and power to exit.",
+                                nullptr };
+  text_y += DrawWrappedTextLines(text_x, text_y, instruction);
+
+  // Iterate through the text images and display them in order for the current locale.
+  for (const auto& p : surfaces) {
+    text_y += line_spacing;
+    SetColor(LOG);
+    text_y += DrawTextLine(text_x, text_y, p.first.c_str(), false);
+    gr_color(255, 255, 255, 255);
+    gr_texticon(text_x, text_y, p.second.get());
+    text_y += gr_get_height(p.second.get());
+  }
+  // Update the whole screen.
+  gr_flip();
+  pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::CheckBackgroundTextImages(const std::string& saved_locale) {
+  // Load a list of locales embedded in one of the resource files.
+  std::vector<std::string> locales_entries = get_locales_in_png("installing_text");
+  if (locales_entries.empty()) {
+    Print("Failed to load locales from the resource files\n");
+    return;
+  }
+  size_t selected = 0;
+  SelectAndShowBackgroundText(locales_entries, selected);
+
+  FlushKeys();
+  while (true) {
+    int key = WaitKey();
+    if (key == KEY_POWER || key == KEY_ENTER) {
+      break;
+    } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+      selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
+      SelectAndShowBackgroundText(locales_entries, selected);
+    } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) {
+      selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
+      SelectAndShowBackgroundText(locales_entries, selected);
+    }
+  }
+
+  SetLocale(saved_locale);
+}
+
 int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
   gr_fill(0, y + 4, gr_fb_width(), y + 6);
   return 8;
diff --git a/screen_ui.h b/screen_ui.h
index eaac2a6..3a28a09 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -80,6 +80,10 @@
 
   void SetColor(UIElement e) const;
 
+  // Check the background text image. Use volume up/down button to cycle through the locales
+  // embedded in the png file, and power button to go back to recovery main menu.
+  void CheckBackgroundTextImages(const std::string& saved_locale);
+
  protected:
   // The margin that we don't want to use for showing texts (e.g. round screen, or screen with
   // rounded corners).
@@ -199,6 +203,10 @@
 
  private:
   void SetLocale(const std::string&);
+
+  // Display the background texts for "erasing", "error", "no_command" and "installing" for the
+  // selected locale.
+  void SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries, size_t sel);
 };
 
 #endif  // RECOVERY_UI_H