Snap for 4778411 from baa4f0d2af970e014bb7b48f7dc8a5b92eb29bee to qt-release

Change-Id: Iaaa00d815714e27c45516f8d8fd194416bf4ec39
diff --git a/Android.mk b/Android.mk
index dbd2eb7..09feba2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,44 +23,21 @@
 # librecovery_ui_default, which uses ScreenRecoveryUI.
 TARGET_RECOVERY_UI_LIB ?= librecovery_ui_default
 
-# librecovery (static library)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    install.cpp
-
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
-
-ifeq ($(AB_OTA_UPDATER),true)
-    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
-endif
-
-LOCAL_MODULE := librecovery
-
-LOCAL_STATIC_LIBRARIES := \
-    libminui \
-    libotautil \
-    libvintf_recovery \
-    libcrypto_utils \
-    libcrypto \
-    libbase \
-    libziparchive \
-
-include $(BUILD_STATIC_LIBRARY)
+recovery_common_cflags := \
+    -Wall \
+    -Werror \
+    -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 
 # librecovery_ui (static library)
 # ===============================
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := \
+    device.cpp \
     screen_ui.cpp \
     ui.cpp \
     vr_ui.cpp \
     wear_ui.cpp
 
-LOCAL_CFLAGS := -Wall -Werror
-
 LOCAL_MODULE := librecovery_ui
 
 LOCAL_STATIC_LIBRARIES := \
@@ -68,6 +45,8 @@
     libotautil \
     libbase
 
+LOCAL_CFLAGS := $(recovery_common_cflags)
+
 ifneq ($(TARGET_RECOVERY_UI_MARGIN_HEIGHT),)
 LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=$(TARGET_RECOVERY_UI_MARGIN_HEIGHT)
 else
@@ -118,13 +97,38 @@
 
 include $(BUILD_STATIC_LIBRARY)
 
+# librecovery (static library)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    install.cpp
+
+LOCAL_CFLAGS := $(recovery_common_cflags)
+
+ifeq ($(AB_OTA_UPDATER),true)
+    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+endif
+
+LOCAL_MODULE := librecovery
+
+LOCAL_STATIC_LIBRARIES := \
+    libminui \
+    libotautil \
+    libvintf_recovery \
+    libcrypto_utils \
+    libcrypto \
+    libbase \
+    libziparchive \
+
+include $(BUILD_STATIC_LIBRARY)
+
 # recovery (static executable)
 # ===============================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
     adb_install.cpp \
-    device.cpp \
     fuse_sdcard_provider.cpp \
     logging.cpp \
     recovery.cpp \
@@ -135,20 +139,13 @@
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+
 # Cannot link with LLD: undefined symbol: UsbNoPermissionsLongHelpText
 # http://b/77543887, lld does not handle -Wl,--gc-sections as well as ld.
 LOCAL_USE_CLANG_LLD := false
 
-LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf
-
-ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
-ifeq ($(HOST_OS),linux)
-LOCAL_REQUIRED_MODULES += sload.f2fs mkfs.f2fs
-endif
-endif
-
-LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
-LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS := $(recovery_common_cflags)
 
 LOCAL_C_INCLUDES += \
     system/vold \
@@ -169,42 +166,52 @@
 LOCAL_STATIC_LIBRARIES += \
     librecovery \
     $(TARGET_RECOVERY_UI_LIB) \
-    libverifier \
     libbootloader_message \
-    libfs_mgr \
-    libext4_utils \
-    libsparse \
-    libziparchive \
-    libotautil \
-    libminadbd \
-    libasyncio \
     libfusesideload \
+    libminadbd \
     librecovery_ui \
     libminui \
-    libpng \
+    libverifier \
+    libotautil \
+    libasyncio \
+    libbatterymonitor \
     libcrypto_utils \
     libcrypto \
+    libext4_utils \
+    libfs_mgr \
+    libpng \
+    libsparse \
     libvintf_recovery \
     libvintf \
     libhidl-gen-utils \
     libtinyxml2 \
+    libziparchive \
     libbase \
     libutils \
     libcutils \
     liblog \
     libselinux \
-    libz
+    libz \
 
 LOCAL_HAL_STATIC_LIBRARIES := libhealthd
 
-ifeq ($(AB_OTA_UPDATER),true)
-    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+LOCAL_REQUIRED_MODULES := \
+    e2fsdroid_static \
+    mke2fs_static \
+    mke2fs.conf
+
+ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
+ifeq ($(HOST_OS),linux)
+LOCAL_REQUIRED_MODULES += \
+    sload.f2fs \
+    mkfs.f2fs
+endif
 endif
 
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-
 ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),)
-LOCAL_REQUIRED_MODULES += recovery-persist recovery-refresh
+LOCAL_REQUIRED_MODULES += \
+    recovery-persist \
+    recovery-refresh
 endif
 
 include $(BUILD_EXECUTABLE)
diff --git a/device.cpp b/device.cpp
index 5cf9cc2..3c6334e 100644
--- a/device.cpp
+++ b/device.cpp
@@ -16,59 +16,57 @@
 
 #include "device.h"
 
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
 #include <android-base/logging.h>
-#include <android-base/macros.h>
 
 #include "ui.h"
 
-// clang-format off
-static constexpr const char* kItems[]{
-  "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",
-#endif  // !AB_OTA_UPDATER
-  "Mount /system",
-  "View recovery logs",
-  "Run graphics test",
-  "Run locale test",
-  "Power off",
+static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
+  { "Reboot system now", Device::REBOOT },
+  { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
+  { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD },
+  { "Apply update from SD card", Device::APPLY_SDCARD },
+  { "Wipe data/factory reset", Device::WIPE_DATA },
+  { "Wipe cache partition", Device::WIPE_CACHE },
+  { "Mount /system", Device::MOUNT_SYSTEM },
+  { "View recovery logs", Device::VIEW_RECOVERY_LOGS },
+  { "Run graphics test", Device::RUN_GRAPHICS_TEST },
+  { "Run locale test", Device::RUN_LOCALE_TEST },
+  { "Power off", Device::SHUTDOWN },
 };
-// clang-format on
 
-// clang-format off
-static constexpr Device::BuiltinAction kMenuActions[] {
-  Device::REBOOT,
-  Device::REBOOT_BOOTLOADER,
-  Device::APPLY_ADB_SIDELOAD,
-  Device::APPLY_SDCARD,
-  Device::WIPE_DATA,
-#ifndef AB_OTA_UPDATER
-  Device::WIPE_CACHE,
-#endif  // !AB_OTA_UPDATER
-  Device::MOUNT_SYSTEM,
-  Device::VIEW_RECOVERY_LOGS,
-  Device::RUN_GRAPHICS_TEST,
-  Device::RUN_LOCALE_TEST,
-  Device::SHUTDOWN,
-};
-// clang-format on
+static std::vector<std::string> g_menu_items;
 
-static_assert(arraysize(kItems) == arraysize(kMenuActions),
-              "kItems and kMenuActions should have the same length.");
+static void PopulateMenuItems() {
+  g_menu_items.clear();
+  std::transform(g_menu_actions.cbegin(), g_menu_actions.cend(), std::back_inserter(g_menu_items),
+                 [](const auto& entry) { return entry.first; });
+}
 
-static const std::vector<std::string> kMenuItems(kItems, kItems + arraysize(kItems));
+Device::Device(RecoveryUI* ui) : ui_(ui) {
+  PopulateMenuItems();
+}
+
+void Device::RemoveMenuItemForAction(Device::BuiltinAction action) {
+  g_menu_actions.erase(
+      std::remove_if(g_menu_actions.begin(), g_menu_actions.end(),
+                     [action](const auto& entry) { return entry.second == action; }));
+  CHECK(!g_menu_actions.empty());
+
+  // Re-populate the menu items.
+  PopulateMenuItems();
+}
 
 const std::vector<std::string>& Device::GetMenuItems() {
-  return kMenuItems;
+  return g_menu_items;
 }
 
 Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) {
-  // CHECK_LT(menu_position, );
-  return kMenuActions[menu_position];
+  return g_menu_actions[menu_position].second;
 }
 
 int Device::HandleMenuKey(int key, bool visible) {
diff --git a/device.h b/device.h
index bf148f5..9c43371 100644
--- a/device.h
+++ b/device.h
@@ -49,7 +49,7 @@
     RUN_LOCALE_TEST = 12,
   };
 
-  explicit Device(RecoveryUI* ui) : ui_(ui) {}
+  explicit Device(RecoveryUI* ui);
   virtual ~Device() {}
 
   // Returns a raw pointer to the RecoveryUI object.
@@ -96,6 +96,10 @@
   // here and return NO_ACTION.
   virtual BuiltinAction InvokeMenuItem(size_t menu_position);
 
+  // Removes the menu item for the given action. This allows tailoring the menu based on the
+  // runtime info, such as the availability of /cache or /sdcard.
+  virtual void RemoveMenuItemForAction(Device::BuiltinAction action);
+
   // Called before and after we do a wipe data/factory reset operation, either via a reboot from the
   // main system with the --wipe_data flag, or when the user boots into recovery image manually and
   // selects the option from the menu, to perform whatever device-specific wiping actions as needed.
diff --git a/minui/include/private/resources.h b/minui/include/private/resources.h
index 2a83a10..047ebe2 100644
--- a/minui/include/private/resources.h
+++ b/minui/include/private/resources.h
@@ -82,3 +82,6 @@
   // After initialization, we'll keep the file pointer open before destruction of PngHandler.
   std::unique_ptr<FILE, decltype(&fclose)> png_fp_{ nullptr, fclose };
 };
+
+// Overrides the default resource dir, for testing purpose.
+void res_set_resource_dir(const std::string&);
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 9f67cf8..c018d9b 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -32,7 +32,6 @@
 #include <string>
 #include <vector>
 
-#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <png.h>
 
@@ -40,6 +39,8 @@
 
 #define SURFACE_DATA_ALIGNMENT 8
 
+static std::string g_resource_dir{ "/res/images" };
+
 static GRSurface* malloc_surface(size_t data_size) {
     size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT;
     unsigned char* temp = static_cast<unsigned char*>(malloc(size));
@@ -51,7 +52,7 @@
 }
 
 PngHandler::PngHandler(const std::string& name) {
-  std::string res_path = android::base::StringPrintf("/res/images/%s.png", name.c_str());
+  std::string res_path = g_resource_dir + "/" + name + ".png";
   png_fp_.reset(fopen(res_path.c_str(), "rbe"));
   // Try to read from |name| if the resource path does not work.
   if (!png_fp_) {
@@ -340,6 +341,10 @@
   return 0;
 }
 
+void res_set_resource_dir(const std::string& dirname) {
+  g_resource_dir = dirname;
+}
+
 // This function tests if a locale string stored in PNG (prefix) matches
 // the locale string provided by the system (locale).
 bool matches_locale(const std::string& prefix, const std::string& locale) {
diff --git a/otautil/include/otautil/paths.h b/otautil/include/otautil/paths.h
index 788c3de..39088f1 100644
--- a/otautil/include/otautil/paths.h
+++ b/otautil/include/otautil/paths.h
@@ -48,6 +48,13 @@
     last_command_file_ = last_command_file;
   }
 
+  std::string resource_dir() const {
+    return resource_dir_;
+  }
+  void set_resource_dir(const std::string& resource_dir) {
+    resource_dir_ = resource_dir;
+  }
+
   std::string stash_directory_base() const {
     return stash_directory_base_;
   }
@@ -85,6 +92,9 @@
   // Path to the last command file.
   std::string last_command_file_;
 
+  // Path to the resource dir;
+  std::string resource_dir_;
+
   // Path to the base directory to write stashes during update.
   std::string stash_directory_base_;
 
diff --git a/otautil/paths.cpp b/otautil/paths.cpp
index ad9ec11..f08e51c 100644
--- a/otautil/paths.cpp
+++ b/otautil/paths.cpp
@@ -19,6 +19,7 @@
 constexpr const char kDefaultCacheLogDirectory[] = "/cache/recovery";
 constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file";
 constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command";
+constexpr const char kDefaultResourceDirectory[] = "/res/images";
 constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery";
 constexpr const char kDefaultTemporaryInstallFile[] = "/tmp/last_install";
 constexpr const char kDefaultTemporaryLogFile[] = "/tmp/recovery.log";
@@ -32,6 +33,7 @@
     : cache_log_directory_(kDefaultCacheLogDirectory),
       cache_temp_source_(kDefaultCacheTempSource),
       last_command_file_(kDefaultLastCommandFile),
+      resource_dir_(kDefaultResourceDirectory),
       stash_directory_base_(kDefaultStashDirectoryBase),
       temporary_install_file_(kDefaultTemporaryInstallFile),
       temporary_log_file_(kDefaultTemporaryLogFile) {}
diff --git a/recovery.cpp b/recovery.cpp
index 38784b0..f03cec3 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -1202,6 +1202,10 @@
   }
   ui = device->GetUI();
 
+  if (!has_cache) {
+    device->RemoveMenuItemForAction(Device::WIPE_CACHE);
+  }
+
   // Set background string to "installing security update" for security update,
   // otherwise set it to "installing system update".
   ui->SetSystemUpdateText(security_update);
diff --git a/screen_ui.cpp b/screen_ui.cpp
index f5dadf7..fd7a1be 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -41,9 +41,10 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <minui/minui.h>
 
 #include "device.h"
+#include "minui/minui.h"
+#include "otautil/paths.h"
 #include "ui.h"
 
 // Return the current time as a double (including fractions of a second).
@@ -756,7 +757,8 @@
 }
 
 void ScreenRecoveryUI::LoadAnimation() {
-  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir);
+  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(Paths::Get().resource_dir().c_str()),
+                                                closedir);
   dirent* de;
   std::vector<std::string> intro_frame_names;
   std::vector<std::string> loop_frame_names;
@@ -1099,9 +1101,9 @@
   rtl_locale_ = false;
 
   if (!new_locale.empty()) {
-    size_t underscore = new_locale.find('_');
-    // lang has the language prefix prior to '_', or full string if '_' doesn't exist.
-    std::string lang = new_locale.substr(0, underscore);
+    size_t separator = new_locale.find('-');
+    // lang has the language prefix prior to the separator, or full string if none exists.
+    std::string lang = new_locale.substr(0, separator);
 
     // A bit cheesy: keep an explicit list of supported RTL languages.
     if (lang == "ar" ||  // Arabic
diff --git a/tests/Android.mk b/tests/Android.mk
index 538ae63..cdc5b52 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -27,6 +27,7 @@
     libminui \
     libotautil \
     libupdater \
+    libpng \
     libziparchive \
     libutils \
     libz \
@@ -93,6 +94,26 @@
 LOCAL_SHARED_LIBRARIES := \
     libhidlbase
 
+# libapplypatch, libapplypatch_modes, libimgdiff, libimgpatch.
+libapplypatch_static_libraries := \
+    libapplypatch_modes \
+    libapplypatch \
+    libedify \
+    libimgdiff \
+    libimgpatch \
+    libotafault \
+    libotautil \
+    libbsdiff \
+    libbspatch \
+    libdivsufsort \
+    libdivsufsort64 \
+    libutils \
+    libbase \
+    libbz \
+    libcrypto \
+    libz \
+    libziparchive \
+
 tune2fs_static_libraries := \
     libext2_com_err \
     libext2_blkid \
@@ -101,50 +122,73 @@
     libext2_e2p \
     libext2fs
 
-LOCAL_STATIC_LIBRARIES := \
-    libapplypatch_modes \
-    libapplypatch \
-    libedify \
-    libimgdiff \
-    libimgpatch \
-    libbsdiff \
-    libbspatch \
-    libfusesideload \
-    libminui \
-    libotafault \
-    librecovery \
+libupdater_static_libraries := \
     libupdater \
+    libapplypatch \
+    libbspatch \
+    libedify \
+    libziparchive \
+    libotautil \
     libbootloader_message \
+    libutils \
+    libotafault \
+    libext4_utils \
+    libfec \
+    libfec_rs \
+    libfs_mgr \
+    liblog \
+    libselinux \
+    libsparse \
+    libsquashfs_utils \
+    libbz \
+    libz \
+    libbase \
+    libcrypto \
+    libcrypto_utils \
+    libcutils \
+    libtune2fs \
+    libbrotli \
+    $(tune2fs_static_libraries)
+
+librecovery_static_libraries := \
+    librecovery \
+    $(TARGET_RECOVERY_UI_LIB) \
+    libbootloader_message \
+    libfusesideload \
+    libminadbd \
+    librecovery_ui \
+    libminui \
     libverifier \
     libotautil \
-    libupdate_verifier \
-    libdivsufsort \
-    libdivsufsort64 \
+    libasyncio \
+    libbatterymonitor \
+    libcrypto_utils \
+    libcrypto \
+    libext4_utils \
     libfs_mgr \
     libpng \
+    libsparse \
     libvintf_recovery \
     libvintf \
     libhidl-gen-utils \
     libtinyxml2 \
-    libselinux \
-    libext4_utils \
-    libsparse \
-    libcrypto_utils \
-    libcrypto \
-    libbz \
     libziparchive \
-    liblog \
-    libutils \
-    libz \
     libbase \
-    libtune2fs \
-    libfec \
-    libfec_rs \
-    libsquashfs_utils \
     libcutils \
-    libbrotli \
-    libBionicGtestMain \
-    $(tune2fs_static_libraries)
+    libutils \
+    liblog \
+    libselinux \
+    libz \
+
+libupdate_verifier_static_libraries := \
+    libupdate_verifier \
+
+LOCAL_STATIC_LIBRARIES := \
+    $(libapplypatch_static_libraries) \
+    $(librecovery_static_libraries) \
+    $(libupdate_verifier_static_libraries) \
+    $(libupdater_static_libraries) \
+    libBionicGtestMain
 
 LOCAL_TEST_DATA := \
     $(call find-test-data-in-subdirs, $(LOCAL_PATH), "*", testdata) \
diff --git a/tests/testdata/font.png b/tests/testdata/font.png
new file mode 100644
index 0000000..d95408a
--- /dev/null
+++ b/tests/testdata/font.png
Binary files differ
diff --git a/tests/testdata/loop00000.png b/tests/testdata/loop00000.png
new file mode 100644
index 0000000..0e11c01
--- /dev/null
+++ b/tests/testdata/loop00000.png
Binary files differ
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
index e47d705..03e23ca 100644
--- a/tests/unit/screen_ui_test.cpp
+++ b/tests/unit/screen_ui_test.cpp
@@ -16,11 +16,19 @@
 
 #include <stddef.h>
 
+#include <functional>
+#include <map>
+#include <memory>
 #include <string>
 #include <vector>
 
+#include <android-base/logging.h>
 #include <gtest/gtest.h>
 
+#include "common/test_constants.h"
+#include "device.h"
+#include "otautil/paths.h"
+#include "private/resources.h"
 #include "screen_ui.h"
 
 static const std::vector<std::string> HEADERS{ "header" };
@@ -185,3 +193,162 @@
   ASSERT_EQ(0u, menu.MenuStart());
   ASSERT_EQ(3u, menu.MenuEnd());
 }
+
+static constexpr int kMagicAction = 101;
+
+enum class KeyCode : int {
+  TIMEOUT = -1,
+  NO_OP = 0,
+  UP = 1,
+  DOWN = 2,
+  ENTER = 3,
+  MAGIC = 1001,
+  LAST,
+};
+
+static const std::map<KeyCode, int> kKeyMapping{
+  // clang-format off
+  { KeyCode::NO_OP, Device::kNoAction },
+  { KeyCode::UP, Device::kHighlightUp },
+  { KeyCode::DOWN, Device::kHighlightDown },
+  { KeyCode::ENTER, Device::kInvokeItem },
+  { KeyCode::MAGIC, kMagicAction },
+  // clang-format on
+};
+
+class TestableScreenRecoveryUI : public ScreenRecoveryUI {
+ public:
+  int WaitKey() override;
+
+  void SetKeyBuffer(const std::vector<KeyCode>& buffer);
+
+  int KeyHandler(int key, bool visible) const;
+
+  bool GetRtlLocale() const {
+    return rtl_locale_;
+  }
+
+ private:
+  std::vector<KeyCode> key_buffer_;
+  size_t key_buffer_index_;
+};
+
+void TestableScreenRecoveryUI::SetKeyBuffer(const std::vector<KeyCode>& buffer) {
+  key_buffer_ = buffer;
+  key_buffer_index_ = 0;
+}
+
+int TestableScreenRecoveryUI::KeyHandler(int key, bool) const {
+  KeyCode key_code = static_cast<KeyCode>(key);
+  if (kKeyMapping.find(key_code) != kKeyMapping.end()) {
+    return kKeyMapping.at(key_code);
+  }
+  return Device::kNoAction;
+}
+
+int TestableScreenRecoveryUI::WaitKey() {
+  CHECK_LT(key_buffer_index_, key_buffer_.size());
+  return static_cast<int>(key_buffer_[key_buffer_index_++]);
+}
+
+class ScreenRecoveryUITest : public ::testing::Test {
+ protected:
+  const std::string kTestLocale = "en-US";
+  const std::string kTestRtlLocale = "ar";
+  const std::string kTestRtlLocaleWithSuffix = "ar-EG";
+
+  void SetUp() override {
+    ui_ = std::make_unique<TestableScreenRecoveryUI>();
+
+    std::string testdata_dir = from_testdata_base("");
+    Paths::Get().set_resource_dir(testdata_dir);
+    res_set_resource_dir(testdata_dir);
+
+    ASSERT_TRUE(ui_->Init(kTestLocale));
+  }
+
+  std::unique_ptr<TestableScreenRecoveryUI> ui_;
+};
+
+TEST_F(ScreenRecoveryUITest, Init) {
+  ASSERT_EQ(kTestLocale, ui_->GetLocale());
+  ASSERT_FALSE(ui_->GetRtlLocale());
+  ASSERT_FALSE(ui_->IsTextVisible());
+  ASSERT_FALSE(ui_->WasTextEverVisible());
+}
+
+TEST_F(ScreenRecoveryUITest, ShowText) {
+  ASSERT_FALSE(ui_->IsTextVisible());
+  ui_->ShowText(true);
+  ASSERT_TRUE(ui_->IsTextVisible());
+  ASSERT_TRUE(ui_->WasTextEverVisible());
+
+  ui_->ShowText(false);
+  ASSERT_FALSE(ui_->IsTextVisible());
+  ASSERT_TRUE(ui_->WasTextEverVisible());
+}
+
+TEST_F(ScreenRecoveryUITest, RtlLocale) {
+  ASSERT_TRUE(ui_->Init(kTestRtlLocale));
+  ASSERT_TRUE(ui_->GetRtlLocale());
+
+  ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
+  ASSERT_TRUE(ui_->GetRtlLocale());
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu) {
+  ui_->SetKeyBuffer({
+      KeyCode::UP,
+      KeyCode::DOWN,
+      KeyCode::UP,
+      KeyCode::DOWN,
+      KeyCode::ENTER,
+  });
+  ASSERT_EQ(3u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+                              std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+                                        std::placeholders::_1, std::placeholders::_2)));
+
+  ui_->SetKeyBuffer({
+      KeyCode::UP,
+      KeyCode::UP,
+      KeyCode::NO_OP,
+      KeyCode::NO_OP,
+      KeyCode::UP,
+      KeyCode::ENTER,
+  });
+  ASSERT_EQ(2u, ui_->ShowMenu(HEADERS, ITEMS, 0, true,
+                              std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+                                        std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
+  ui_->SetKeyBuffer({
+      KeyCode::MAGIC,
+  });
+  ASSERT_EQ(static_cast<size_t>(kMagicAction),
+            ui_->ShowMenu(HEADERS, ITEMS, 3, false,
+                          std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+                                    std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
+  ui_->SetKeyBuffer({
+      KeyCode::TIMEOUT,
+  });
+  ASSERT_EQ(static_cast<size_t>(-1), ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr));
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
+  ui_->ShowText(true);
+  ui_->ShowText(false);
+  ASSERT_TRUE(ui_->WasTextEverVisible());
+
+  ui_->SetKeyBuffer({
+      KeyCode::TIMEOUT,
+      KeyCode::DOWN,
+      KeyCode::ENTER,
+  });
+  ASSERT_EQ(4u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+                              std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+                                        std::placeholders::_1, std::placeholders::_2)));
+}