[automerger skipped] DO NOT MERGE: Initialize the ZipArchive to zero before parsing skipped: e830a51351 skipped: 244aba4412 skipped: 9216bccd16 skipped: 98acdb9fce skipped: bc42a6b0eb am: 35332d7c62
am: 6b13f89f15

Change-Id: Iecb9ede3a76664e15fc2b533b20571c8052ed4ba
diff --git a/Android.mk b/Android.mk
index 95c8823..776e6ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -59,7 +59,8 @@
     libvintf_recovery \
     libcrypto_utils \
     libcrypto \
-    libbase
+    libbase \
+    libziparchive \
 
 include $(BUILD_STATIC_LIBRARY)
 
@@ -78,7 +79,6 @@
     ui.cpp \
     vr_ui.cpp \
     wear_ui.cpp \
-    wear_touch.cpp \
 
 LOCAL_MODULE := recovery
 
@@ -107,6 +107,36 @@
 LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=0
 endif
 
+ifneq ($(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD),)
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=50
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD),)
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=90
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE),)
+LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=$(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=259
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_ANIMATION_FPS),)
+LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=$(TARGET_RECOVERY_UI_ANIMATION_FPS)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=30
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS),)
+LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=$(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=9
+endif
+
 ifneq ($(TARGET_RECOVERY_UI_VR_STEREO_OFFSET),)
 LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=$(TARGET_RECOVERY_UI_VR_STEREO_OFFSET)
 else
@@ -203,6 +233,16 @@
 LOCAL_CFLAGS := -Werror
 include $(BUILD_STATIC_LIBRARY)
 
+# Wear default device
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := wear_device.cpp
+
+# Should match TARGET_RECOVERY_UI_LIB in BoardConfig.mk.
+LOCAL_MODULE := librecovery_ui_wear
+
+include $(BUILD_STATIC_LIBRARY)
+
 # vr headset default device
 # ===============================
 include $(CLEAR_VARS)
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..09754c6
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+enh+aosp-gerrit@google.com
+tbao@google.com
+xunchang@google.com
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 41d73ab..fc24064 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -693,6 +693,20 @@
         continue;
       }
 
+      // The footer contains the size of the uncompressed data.  Double-check to make sure that it
+      // matches the size of the data we got when we actually did the decompression.
+      size_t footer_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4;
+      if (sz - footer_index < 4) {
+        printf("Warning: invalid footer position; treating as a nomal chunk\n");
+        continue;
+      }
+      size_t footer_size = get_unaligned<uint32_t>(img->data() + footer_index);
+      if (footer_size != uncompressed_len) {
+        printf("Warning: footer size %zu != decompressed size %zu; treating as a nomal chunk\n",
+               footer_size, uncompressed_len);
+        continue;
+      }
+
       ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len);
       uncompressed_data.resize(uncompressed_len);
       body.SetUncompressedData(std::move(uncompressed_data));
@@ -704,17 +718,6 @@
       chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN);
 
       pos += GZIP_FOOTER_LEN;
-
-      // The footer (that we just skipped over) contains the size of
-      // the uncompressed data.  Double-check to make sure that it
-      // matches the size of the data we got when we actually did
-      // the decompression.
-      size_t footer_size = get_unaligned<uint32_t>(img->data() + pos - 4);
-      if (footer_size != body.DataLengthForPatch()) {
-        printf("Error: footer size %zu != decompressed size %zu\n", footer_size,
-               body.GetRawDataLength());
-        return false;
-      }
     } else {
       // Use a normal chunk to take all the contents until the next gzip chunk (or EOF); we expect
       // the number of chunks to be small (5 for typical boot and recovery images).
diff --git a/install.cpp b/install.cpp
index a1f2e4f..7fbf5c0 100644
--- a/install.cpp
+++ b/install.cpp
@@ -27,6 +27,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <atomic>
 #include <chrono>
 #include <condition_variable>
 #include <functional>
@@ -65,18 +66,14 @@
 static std::condition_variable finish_log_temperature;
 
 // This function parses and returns the build.version.incremental
-static int parse_build_number(const std::string& str) {
+static std::string parse_build_number(const std::string& str) {
     size_t pos = str.find('=');
     if (pos != std::string::npos) {
-        std::string num_string = android::base::Trim(str.substr(pos+1));
-        int build_number;
-        if (android::base::ParseInt(num_string.c_str(), &build_number, 0)) {
-            return build_number;
-        }
+        return android::base::Trim(str.substr(pos+1));
     }
 
     LOG(ERROR) << "Failed to parse build number in " << str;
-    return -1;
+    return "";
 }
 
 bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
@@ -113,14 +110,14 @@
   for (const std::string& line : lines) {
     std::string str = android::base::Trim(line);
     if (android::base::StartsWith(str, "pre-build-incremental")) {
-      int source_build = parse_build_number(str);
-      if (source_build != -1) {
-        log_buffer->push_back(android::base::StringPrintf("source_build: %d", source_build));
+      std::string source_build = parse_build_number(str);
+      if (!source_build.empty()) {
+        log_buffer->push_back("source_build: " + source_build);
       }
     } else if (android::base::StartsWith(str, "post-build-incremental")) {
-      int target_build = parse_build_number(str);
-      if (target_build != -1) {
-        log_buffer->push_back(android::base::StringPrintf("target_build: %d", target_build));
+      std::string target_build = parse_build_number(str);
+      if (!target_build.empty()) {
+        log_buffer->push_back("target_build: " + target_build);
       }
     }
   }
@@ -268,7 +265,7 @@
   }
 
   unlink(binary_path.c_str());
-  int fd = creat(binary_path.c_str(), 0755);
+  int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755);
   if (fd == -1) {
     PLOG(ERROR) << "Failed to create " << binary_path;
     return INSTALL_ERROR;
@@ -294,11 +291,12 @@
 }
 #endif  // !AB_OTA_UPDATER
 
-static void log_max_temperature(int* max_temperature) {
+static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
   CHECK(max_temperature != nullptr);
   std::mutex mtx;
   std::unique_lock<std::mutex> lck(mtx);
-  while (finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) {
+  while (!logger_finished.load() &&
+         finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) {
     *max_temperature = std::max(*max_temperature, GetMaxValueFromThermalZone());
   }
 }
@@ -403,7 +401,8 @@
   }
   close(pipefd[1]);
 
-  std::thread temperature_logger(log_max_temperature, max_temperature);
+  std::atomic<bool> logger_finished(false);
+  std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished));
 
   *wipe_cache = false;
   bool retry_update = false;
@@ -467,6 +466,7 @@
   int status;
   waitpid(pid, &status, 0);
 
+  logger_finished.store(true);
   finish_log_temperature.notify_one();
   temperature_logger.join();
 
diff --git a/minui/Android.mk b/minui/Android.mk
index 4dfc65f..6522fcf 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_WHOLE_STATIC_LIBRARIES := \
     libadf \
-    libdrm \
+    libdrm_platform \
     libsync_recovery
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/minui/events.cpp b/minui/events.cpp
index 0e1fd44..24c2a82 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -53,36 +53,37 @@
     return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
 }
 
-int ev_init(ev_callback input_cb) {
-  bool epollctlfail = false;
-
+int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
   g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
   if (g_epoll_fd == -1) {
     return -1;
   }
 
+  bool epollctlfail = false;
   DIR* dir = opendir("/dev/input");
-  if (dir != NULL) {
+  if (dir != nullptr) {
     dirent* de;
     while ((de = readdir(dir))) {
-      // Use unsigned long to match ioctl's parameter type.
-      unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
-
-      //            fprintf(stderr,"/dev/input/%s\n", de->d_name);
       if (strncmp(de->d_name, "event", 5)) continue;
       int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
       if (fd == -1) continue;
 
+      // Use unsigned long to match ioctl's parameter type.
+      unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
+
       // Read the evbits of the input device.
       if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
         close(fd);
         continue;
       }
 
-      // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
+      // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
+      // allowed if allow_touch_inputs is set.
       if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
-        close(fd);
-        continue;
+        if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
+          close(fd);
+          continue;
+        }
       }
 
       epoll_event ev;
@@ -231,3 +232,27 @@
         }
     }
 }
+
+void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
+  for (size_t i = 0; i < ev_dev_count; ++i) {
+    // Use unsigned long to match ioctl's parameter type.
+    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {};  // NOLINT
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+      continue;
+    }
+    if (!test_bit(EV_ABS, ev_bits)) {
+      continue;
+    }
+
+    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)] = {};  // NOLINT
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_ABS, KEY_MAX), key_bits) == -1) {
+      continue;
+    }
+
+    for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
+      if (test_bit(key_code, key_bits)) {
+        action(key_code);
+      }
+    }
+  }
+}
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 78dd4cb..017ddde 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -74,10 +74,11 @@
 using ev_callback = std::function<int(int fd, uint32_t epevents)>;
 using ev_set_key_callback = std::function<int(int code, int value)>;
 
-int ev_init(ev_callback input_cb);
+int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
 void ev_exit();
 int ev_add_fd(int fd, ev_callback cb);
 void ev_iterate_available_keys(const std::function<void(int)>& f);
+void ev_iterate_touch_inputs(const std::function<void(int)>& action);
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
 
 // 'timeout' has the same semantics as poll(2).
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 86c731b..8f8d36d 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -56,7 +56,7 @@
 
     snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
     resPath[sizeof(resPath)-1] = '\0';
-    FILE* fp = fopen(resPath, "rb");
+    FILE* fp = fopen(resPath, "rbe");
     if (fp == NULL) {
         result = -1;
         goto exit;
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index d706cca..dbce7ff 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -59,21 +59,21 @@
 }
 
 static void copy_file(const char* source, const char* destination) {
-    FILE* dest_fp = fopen(destination, "w");
-    if (dest_fp == nullptr) {
-        PLOG(ERROR) << "Can't open " << destination;
-    } else {
-        FILE* source_fp = fopen(source, "r");
-        if (source_fp != nullptr) {
-            char buf[4096];
-            size_t bytes;
-            while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
-                fwrite(buf, 1, bytes, dest_fp);
-            }
-            check_and_fclose(source_fp, source);
-        }
-        check_and_fclose(dest_fp, destination);
+  FILE* dest_fp = fopen(destination, "we");
+  if (dest_fp == nullptr) {
+    PLOG(ERROR) << "Can't open " << destination;
+  } else {
+    FILE* source_fp = fopen(source, "re");
+    if (source_fp != nullptr) {
+      char buf[4096];
+      size_t bytes;
+      while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
+        fwrite(buf, 1, bytes, dest_fp);
+      }
+      check_and_fclose(source_fp, source);
     }
+    check_and_fclose(dest_fp, destination);
+  }
 }
 
 static bool rotated = false;
@@ -120,7 +120,7 @@
      */
     bool has_cache = false;
     static const char mounts_file[] = "/proc/mounts";
-    FILE *fp = fopen(mounts_file, "r");
+    FILE* fp = fopen(mounts_file, "re");
     if (!fp) {
         PLOG(ERROR) << "failed to open " << mounts_file;
     } else {
diff --git a/recovery.cpp b/recovery.cpp
index 852f1e8..07bd7b9 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -250,7 +250,7 @@
         auto start = std::chrono::steady_clock::now();
 
         // Child logger to actually write to the log file.
-        FILE* log_fp = fopen(filename, "a");
+        FILE* log_fp = fopen(filename, "ae");
         if (log_fp == nullptr) {
             PLOG(ERROR) << "fopen \"" << filename << "\" failed";
             close(pipefd[0]);
@@ -419,27 +419,27 @@
 static off_t tmplog_offset = 0;
 
 static void copy_log_file(const char* source, const char* destination, bool append) {
-    FILE* dest_fp = fopen_path(destination, append ? "a" : "w");
-    if (dest_fp == nullptr) {
-        PLOG(ERROR) << "Can't open " << destination;
-    } else {
-        FILE* source_fp = fopen(source, "r");
-        if (source_fp != nullptr) {
-            if (append) {
-                fseeko(source_fp, tmplog_offset, SEEK_SET);  // Since last write
-            }
-            char buf[4096];
-            size_t bytes;
-            while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
-                fwrite(buf, 1, bytes, dest_fp);
-            }
-            if (append) {
-                tmplog_offset = ftello(source_fp);
-            }
-            check_and_fclose(source_fp, source);
-        }
-        check_and_fclose(dest_fp, destination);
+  FILE* dest_fp = fopen_path(destination, append ? "ae" : "we");
+  if (dest_fp == nullptr) {
+    PLOG(ERROR) << "Can't open " << destination;
+  } else {
+    FILE* source_fp = fopen(source, "re");
+    if (source_fp != nullptr) {
+      if (append) {
+        fseeko(source_fp, tmplog_offset, SEEK_SET);  // Since last write
+      }
+      char buf[4096];
+      size_t bytes;
+      while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
+        fwrite(buf, 1, bytes, dest_fp);
+      }
+      if (append) {
+        tmplog_offset = ftello(source_fp);
+      }
+      check_and_fclose(source_fp, source);
     }
+    check_and_fclose(dest_fp, destination);
+  }
 }
 
 static void copy_logs() {
@@ -478,40 +478,38 @@
     sync();
 }
 
-// clear the recovery command and prepare to boot a (hopefully working) system,
+// Clear the recovery command and prepare to boot a (hopefully working) system,
 // copy our log file to cache as well (for the system to read). This function is
 // idempotent: call it as many times as you like.
 static void finish_recovery() {
-    // 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.empty() && has_cache) {
-        LOG(INFO) << "Saving locale \"" << locale << "\"";
-
-        FILE* fp = fopen_path(LOCALE_FILE, "w");
-        if (!android::base::WriteStringToFd(locale, fileno(fp))) {
-            PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
-        }
-        check_and_fclose(fp, LOCALE_FILE);
+  // Save the locale to cache, so if recovery is next started up without a '--locale' argument
+  // (e.g., directly from the bootloader) it will use the last-known locale.
+  if (!locale.empty() && has_cache) {
+    LOG(INFO) << "Saving locale \"" << locale << "\"";
+    if (ensure_path_mounted(LOCALE_FILE) != 0) {
+      LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
+    } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
+      PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
     }
+  }
 
-    copy_logs();
+  copy_logs();
 
-    // Reset to normal system boot so recovery won't cycle indefinitely.
-    std::string err;
-    if (!clear_bootloader_message(&err)) {
-        LOG(ERROR) << "Failed to clear BCB message: " << err;
+  // Reset to normal system boot so recovery won't cycle indefinitely.
+  std::string err;
+  if (!clear_bootloader_message(&err)) {
+    LOG(ERROR) << "Failed to clear BCB message: " << err;
+  }
+
+  // Remove the command file, so recovery won't repeat indefinitely.
+  if (has_cache) {
+    if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
+      LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
     }
+    ensure_path_unmounted(CACHE_ROOT);
+  }
 
-    // Remove the command file, so recovery won't repeat indefinitely.
-    if (has_cache) {
-        if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
-            LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
-        }
-        ensure_path_unmounted(CACHE_ROOT);
-    }
-
-    sync();  // For good measure.
+  sync();  // For good measure.
 }
 
 struct saved_log_file {
@@ -552,7 +550,7 @@
             }
 
             std::string data(sb.st_size, '\0');
-            FILE* f = fopen(path.c_str(), "rb");
+            FILE* f = fopen(path.c_str(), "rbe");
             fread(&data[0], 1, data.size(), f);
             fclose(f);
 
@@ -580,7 +578,7 @@
       ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
       return true;
     }
-    FILE* f = fopen(CONVERT_FBE_FILE, "wb");
+    FILE* f = fopen(CONVERT_FBE_FILE, "wbe");
     if (!f) {
       ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
       return true;
@@ -760,12 +758,13 @@
 }
 
 static bool prompt_and_wipe_data(Device* device) {
+  // Use a single string and let ScreenRecoveryUI handles the wrapping.
   const char* const headers[] = {
-    "Can't load Android system. Your data may be corrupt.",
-    "If you continue to get this message, you may need to",
-    "perform a factory data reset and erase all user data",
+    "Can't load Android system. Your data may be corrupt. "
+    "If you continue to get this message, you may need to "
+    "perform a factory data reset and erase all user data "
     "stored on this device.",
-    NULL
+    nullptr
   };
   const char* const items[] = {
     "Try again",
@@ -1593,15 +1592,14 @@
             ui->Print("Rebooting automatically.\n");
         }
     } else if (!just_exit) {
-        status = INSTALL_NONE;  // No command specified
-        ui->SetBackground(RecoveryUI::NO_COMMAND);
-
-        // http://b/17489952
-        // If this is an eng or userdebug build, automatically turn on the
-        // text display if no command is specified.
-        if (is_ro_debuggable()) {
-            ui->ShowText(true);
-        }
+      // If this is an eng or userdebug build, automatically turn on the text display if no command
+      // is specified. Note that this should be called before setting the background to avoid
+      // flickering the background image.
+      if (is_ro_debuggable()) {
+        ui->ShowText(true);
+      }
+      status = INSTALL_NONE;  // No command specified
+      ui->SetBackground(RecoveryUI::NO_COMMAND);
     }
 
     if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
diff --git a/roots.cpp b/roots.cpp
index 49f002f..29f55b9 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -287,8 +287,6 @@
           if (result == 0 && directory != nullptr) {
             const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static",
                                              "-e",
-                                             "-S",
-                                             "/file_contexts",
                                              "-f",
                                              directory,
                                              "-a",
@@ -297,7 +295,7 @@
                                              nullptr };
 
             result = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
-            }
+          }
         } else {   /* Has to be f2fs because we checked earlier. */
             char *num_sectors = nullptr;
             if (length >= 512 && asprintf(&num_sectors, "%zd", length / 512) <= 0) {
diff --git a/screen_ui.cpp b/screen_ui.cpp
index a7d9c9f..b8f6ea2 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -45,14 +45,15 @@
 
 // Return the current time as a double (including fractions of a second).
 static double now() {
-    struct timeval tv;
-    gettimeofday(&tv, nullptr);
-    return tv.tv_sec + tv.tv_usec / 1000000.0;
+  struct timeval tv;
+  gettimeofday(&tv, nullptr);
+  return tv.tv_sec + tv.tv_usec / 1000000.0;
 }
 
 ScreenRecoveryUI::ScreenRecoveryUI()
     : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH),
       kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT),
+      kAnimationFps(RECOVERY_UI_ANIMATION_FPS),
       density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
       currentIcon(NONE),
       progressBarType(EMPTY),
@@ -68,7 +69,7 @@
       text_top_(0),
       show_text(false),
       show_text_ever(false),
-      menu_(nullptr),
+      menu_headers_(nullptr),
       show_menu(false),
       menu_items(0),
       menu_sel(0),
@@ -77,30 +78,34 @@
       loop_frames(0),
       current_frame(0),
       intro_done(false),
-      animation_fps(30),  // TODO: there's currently no way to infer this.
       stage(-1),
       max_stage(-1),
       updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
 
-GRSurface* ScreenRecoveryUI::GetCurrentFrame() {
-    if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-        return intro_done ? loopFrames[current_frame] : introFrames[current_frame];
-    }
-    return error_icon;
+GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
+  if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
+    return intro_done ? loopFrames[current_frame] : introFrames[current_frame];
+  }
+  return error_icon;
 }
 
-GRSurface* ScreenRecoveryUI::GetCurrentText() {
-    switch (currentIcon) {
-        case ERASING: return erasing_text;
-        case ERROR: return error_text;
-        case INSTALLING_UPDATE: return installing_text;
-        case NO_COMMAND: return no_command_text;
-        case NONE: abort();
-    }
+GRSurface* ScreenRecoveryUI::GetCurrentText() const {
+  switch (currentIcon) {
+    case ERASING:
+      return erasing_text;
+    case ERROR:
+      return error_text;
+    case INSTALLING_UPDATE:
+      return installing_text;
+    case NO_COMMAND:
+      return no_command_text;
+    case NONE:
+      abort();
+  }
 }
 
 int ScreenRecoveryUI::PixelsFromDp(int dp) const {
-    return dp * density_;
+  return dp * density_;
 }
 
 // Here's the intended layout:
@@ -127,17 +132,16 @@
   { 52, 112, },  // LANDSCAPE_LARGE
 };
 
-int ScreenRecoveryUI::GetAnimationBaseline() {
-    return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
-            gr_get_height(loopFrames[0]);
+int ScreenRecoveryUI::GetAnimationBaseline() const {
+  return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]);
 }
 
-int ScreenRecoveryUI::GetTextBaseline() {
-    return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
-            gr_get_height(installing_text);
+int ScreenRecoveryUI::GetTextBaseline() const {
+  return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
+         gr_get_height(installing_text);
 }
 
-int ScreenRecoveryUI::GetProgressBaseline() {
+int ScreenRecoveryUI::GetProgressBaseline() const {
   int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) +
                      gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) +
                      gr_get_height(progressBarFill);
@@ -148,34 +152,33 @@
 // Clear the screen and draw the currently selected background icon (if any).
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_background_locked() {
-    pagesIdentical = false;
-    gr_color(0, 0, 0, 255);
-    gr_clear();
+  pagesIdentical = false;
+  gr_color(0, 0, 0, 255);
+  gr_clear();
 
-    if (currentIcon != NONE) {
-        if (max_stage != -1) {
-            int stage_height = gr_get_height(stageMarkerEmpty);
-            int stage_width = gr_get_width(stageMarkerEmpty);
-            int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
-            int y = gr_fb_height() - stage_height;
-            for (int i = 0; i < max_stage; ++i) {
-                GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
-                gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y);
-                x += stage_width;
-            }
-        }
-
-        GRSurface* text_surface = GetCurrentText();
-        int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2;
-        int text_y = GetTextBaseline();
-        gr_color(255, 255, 255, 255);
-        gr_texticon(text_x, text_y, text_surface);
+  if (currentIcon != NONE) {
+    if (max_stage != -1) {
+      int stage_height = gr_get_height(stageMarkerEmpty);
+      int stage_width = gr_get_width(stageMarkerEmpty);
+      int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
+      int y = gr_fb_height() - stage_height;
+      for (int i = 0; i < max_stage; ++i) {
+        GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
+        gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y);
+        x += stage_width;
+      }
     }
+
+    GRSurface* text_surface = GetCurrentText();
+    int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2;
+    int text_y = GetTextBaseline();
+    gr_color(255, 255, 255, 255);
+    gr_texticon(text_x, text_y, text_surface);
+  }
 }
 
-// Draws the animation and progress bar (if any) on the screen.
-// Does not flip pages.
-// Should only be called with updateMutex locked.
+// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
+// called with updateMutex locked.
 void ScreenRecoveryUI::draw_foreground_locked() {
   if (currentIcon != NONE) {
     GRSurface* frame = GetCurrentFrame();
@@ -223,67 +226,95 @@
   }
 }
 
-void ScreenRecoveryUI::SetColor(UIElement e) {
-    switch (e) {
-        case INFO:
-            gr_color(249, 194, 0, 255);
-            break;
-        case HEADER:
-            gr_color(247, 0, 6, 255);
-            break;
-        case MENU:
-        case MENU_SEL_BG:
-            gr_color(0, 106, 157, 255);
-            break;
-        case MENU_SEL_BG_ACTIVE:
-            gr_color(0, 156, 100, 255);
-            break;
-        case MENU_SEL_FG:
-            gr_color(255, 255, 255, 255);
-            break;
-        case LOG:
-            gr_color(196, 196, 196, 255);
-            break;
-        case TEXT_FILL:
-            gr_color(0, 0, 0, 160);
-            break;
-        default:
-            gr_color(255, 255, 255, 255);
-            break;
-    }
+void ScreenRecoveryUI::SetColor(UIElement e) const {
+  switch (e) {
+    case INFO:
+      gr_color(249, 194, 0, 255);
+      break;
+    case HEADER:
+      gr_color(247, 0, 6, 255);
+      break;
+    case MENU:
+    case MENU_SEL_BG:
+      gr_color(0, 106, 157, 255);
+      break;
+    case MENU_SEL_BG_ACTIVE:
+      gr_color(0, 156, 100, 255);
+      break;
+    case MENU_SEL_FG:
+      gr_color(255, 255, 255, 255);
+      break;
+    case LOG:
+      gr_color(196, 196, 196, 255);
+      break;
+    case TEXT_FILL:
+      gr_color(0, 0, 0, 160);
+      break;
+    default:
+      gr_color(255, 255, 255, 255);
+      break;
+  }
 }
 
-void ScreenRecoveryUI::DrawHorizontalRule(int* y) {
-    SetColor(MENU);
-    *y += 4;
-    gr_fill(0, *y, gr_fb_width(), *y + 2);
-    *y += 4;
+int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
+  gr_fill(0, y + 4, gr_fb_width(), y + 6);
+  return 8;
 }
 
 void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
-    gr_fill(x, y, x + width, y + height);
+  gr_fill(x, y, x + width, y + height);
 }
 
-void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const {
-    gr_text(gr_sys_font(), x, *y, line, bold);
-    *y += char_height_ + 4;
+int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
+  gr_text(gr_sys_font(), x, y, line, bold);
+  return char_height_ + 4;
 }
 
-void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const {
-    for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
-        DrawTextLine(x, y, lines[i], false);
+int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) const {
+  int offset = 0;
+  for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
+    offset += DrawTextLine(x, y + offset, lines[i], false);
+  }
+  return offset;
+}
+
+int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const {
+  int offset = 0;
+  for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
+    // The line will be wrapped if it exceeds text_cols_.
+    std::string line(lines[i]);
+    size_t next_start = 0;
+    while (next_start < line.size()) {
+      std::string sub = line.substr(next_start, text_cols_ + 1);
+      if (sub.size() <= text_cols_) {
+        next_start += sub.size();
+      } else {
+        // Line too long and must be wrapped to text_cols_ columns.
+        size_t last_space = sub.find_last_of(" \t\n");
+        if (last_space == std::string::npos) {
+          // No space found, just draw as much as we can
+          sub.resize(text_cols_);
+          next_start += text_cols_;
+        } else {
+          sub.resize(last_space);
+          next_start += last_space + 1;
+        }
+      }
+      offset += DrawTextLine(x, y + offset, sub.c_str(), false);
     }
+  }
+  return offset;
 }
 
 static const char* REGULAR_HELP[] = {
-    "Use volume up/down and power.",
-    NULL
+  "Use volume up/down and power.",
+  NULL
 };
 
 static const char* LONG_PRESS_HELP[] = {
-    "Any button cycles highlight.",
-    "Long-press activates.",
-    NULL
+  "Any button cycles highlight.",
+  "Long-press activates.",
+  NULL
 };
 
 // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
@@ -294,29 +325,30 @@
     draw_foreground_locked();
     return;
   }
+
   gr_color(0, 0, 0, 255);
   gr_clear();
 
-  static constexpr int TEXT_INDENT = 4;
-  int x = TEXT_INDENT + kMarginWidth;
   int y = kMarginHeight;
   if (show_menu) {
-    std::string recovery_fingerprint =
-        android::base::GetProperty("ro.bootimage.build.fingerprint", "");
+    static constexpr int kMenuIndent = 4;
+    int x = kMarginWidth + kMenuIndent;
 
     SetColor(INFO);
-    DrawTextLine(x, &y, "Android Recovery", true);
+    y += DrawTextLine(x, y, "Android Recovery", true);
+    std::string recovery_fingerprint =
+        android::base::GetProperty("ro.bootimage.build.fingerprint", "");
     for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) {
-      DrawTextLine(x, &y, chunk.c_str(), false);
+      y += DrawTextLine(x, y, chunk.c_str(), false);
     }
-    DrawTextLines(x, &y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
+    y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
 
     SetColor(HEADER);
-    DrawTextLines(x, &y, menu_headers_);
+    // Ignore kMenuIndent, which is not taken into account by text_cols_.
+    y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_);
 
     SetColor(MENU);
-    DrawHorizontalRule(&y);
-    y += 4;
+    y += DrawHorizontalRule(y) + 4;
     for (int i = 0; i < menu_items; ++i) {
       if (i == menu_sel) {
         // Draw the highlight bar.
@@ -324,13 +356,13 @@
         DrawHighlightBar(0, y - 2, gr_fb_width(), char_height_ + 4);
         // Bold white text for the selected item.
         SetColor(MENU_SEL_FG);
-        DrawTextLine(x, &y, menu_[i], true);
+        y += DrawTextLine(x, y, menu_[i].c_str(), true);
         SetColor(MENU);
       } else {
-        DrawTextLine(x, &y, menu_[i], false);
+        y += DrawTextLine(x, y, menu_[i].c_str(), false);
       }
     }
-    DrawHorizontalRule(&y);
+    y += DrawHorizontalRule(y);
   }
 
   // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
@@ -338,10 +370,9 @@
   SetColor(LOG);
   int row = (text_top_ + text_rows_ - 1) % text_rows_;
   size_t count = 0;
-  for (int ty = gr_fb_height() - kMarginHeight - char_height_;
-       ty >= y && count < text_rows_; ty -= char_height_, ++count) {
-    int temp_y = ty;
-    DrawTextLine(x, &temp_y, text_[row], false);
+  for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
+       ty -= char_height_, ++count) {
+    DrawTextLine(kMarginWidth, ty, text_[row], false);
     --row;
     if (row < 0) row = text_rows_ - 1;
   }
@@ -350,81 +381,81 @@
 // Redraw everything on the screen and flip the screen (make it visible).
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::update_screen_locked() {
-    draw_screen_locked();
-    gr_flip();
+  draw_screen_locked();
+  gr_flip();
 }
 
 // Updates only the progress bar, if possible, otherwise redraws the screen.
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::update_progress_locked() {
-    if (show_text || !pagesIdentical) {
-        draw_screen_locked();    // Must redraw the whole screen
-        pagesIdentical = true;
-    } else {
-        draw_foreground_locked();  // Draw only the progress bar and overlays
-    }
-    gr_flip();
+  if (show_text || !pagesIdentical) {
+    draw_screen_locked();  // Must redraw the whole screen
+    pagesIdentical = true;
+  } else {
+    draw_foreground_locked();  // Draw only the progress bar and overlays
+  }
+  gr_flip();
 }
 
 // Keeps the progress bar updated, even when the process is otherwise busy.
 void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) {
-    reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop();
-    return nullptr;
+  reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop();
+  return nullptr;
 }
 
 void ScreenRecoveryUI::ProgressThreadLoop() {
-    double interval = 1.0 / animation_fps;
-    while (true) {
-        double start = now();
-        pthread_mutex_lock(&updateMutex);
+  double interval = 1.0 / kAnimationFps;
+  while (true) {
+    double start = now();
+    pthread_mutex_lock(&updateMutex);
 
-        bool redraw = false;
+    bool redraw = false;
 
-        // update the installation animation, if active
-        // skip this if we have a text overlay (too expensive to update)
-        if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) {
-            if (!intro_done) {
-                if (current_frame == intro_frames - 1) {
-                    intro_done = true;
-                    current_frame = 0;
-                } else {
-                    ++current_frame;
-                }
-            } else {
-                current_frame = (current_frame + 1) % loop_frames;
-            }
-
-            redraw = true;
+    // update the installation animation, if active
+    // skip this if we have a text overlay (too expensive to update)
+    if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) {
+      if (!intro_done) {
+        if (current_frame == intro_frames - 1) {
+          intro_done = true;
+          current_frame = 0;
+        } else {
+          ++current_frame;
         }
+      } else {
+        current_frame = (current_frame + 1) % loop_frames;
+      }
 
-        // move the progress bar forward on timed intervals, if configured
-        int duration = progressScopeDuration;
-        if (progressBarType == DETERMINATE && duration > 0) {
-            double elapsed = now() - progressScopeTime;
-            float p = 1.0 * elapsed / duration;
-            if (p > 1.0) p = 1.0;
-            if (p > progress) {
-                progress = p;
-                redraw = true;
-            }
-        }
-
-        if (redraw) update_progress_locked();
-
-        pthread_mutex_unlock(&updateMutex);
-        double end = now();
-        // minimum of 20ms delay between frames
-        double delay = interval - (end-start);
-        if (delay < 0.02) delay = 0.02;
-        usleep(static_cast<useconds_t>(delay * 1000000));
+      redraw = true;
     }
+
+    // move the progress bar forward on timed intervals, if configured
+    int duration = progressScopeDuration;
+    if (progressBarType == DETERMINATE && duration > 0) {
+      double elapsed = now() - progressScopeTime;
+      float p = 1.0 * elapsed / duration;
+      if (p > 1.0) p = 1.0;
+      if (p > progress) {
+        progress = p;
+        redraw = true;
+      }
+    }
+
+    if (redraw) update_progress_locked();
+
+    pthread_mutex_unlock(&updateMutex);
+    double end = now();
+    // minimum of 20ms delay between frames
+    double delay = interval - (end - start);
+    if (delay < 0.02) delay = 0.02;
+    usleep(static_cast<useconds_t>(delay * 1000000));
+  }
 }
 
 void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
-    int result = res_create_display_surface(filename, surface);
-    if (result < 0) {
-        LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
-    }
+  int result = res_create_display_surface(filename, surface);
+  if (result < 0) {
+    LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
+  }
 }
 
 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
@@ -435,22 +466,22 @@
 }
 
 static char** Alloc2d(size_t rows, size_t cols) {
-    char** result = new char*[rows];
-    for (size_t i = 0; i < rows; ++i) {
-        result[i] = new char[cols];
-        memset(result[i], 0, cols);
-    }
-    return result;
+  char** result = new char*[rows];
+  for (size_t i = 0; i < rows; ++i) {
+    result[i] = new char[cols];
+    memset(result[i], 0, cols);
+  }
+  return result;
 }
 
 // Choose the right background string to display during update.
 void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
-    if (security_update) {
-        LoadLocalizedBitmap("installing_security_text", &installing_text);
-    } else {
-        LoadLocalizedBitmap("installing_text", &installing_text);
-    }
-    Redraw();
+  if (security_update) {
+    LoadLocalizedBitmap("installing_security_text", &installing_text);
+  } else {
+    LoadLocalizedBitmap("installing_text", &installing_text);
+  }
+  Redraw();
 }
 
 bool ScreenRecoveryUI::InitTextParams() {
@@ -477,7 +508,6 @@
 
   text_ = Alloc2d(text_rows_, text_cols_ + 1);
   file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
-  menu_ = Alloc2d(text_rows_, text_cols_ + 1);
 
   text_col_ = text_row_ = 0;
   text_top_ = 1;
@@ -506,309 +536,308 @@
 }
 
 void ScreenRecoveryUI::LoadAnimation() {
-    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir);
-    dirent* de;
-    std::vector<std::string> intro_frame_names;
-    std::vector<std::string> loop_frame_names;
+  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir);
+  dirent* de;
+  std::vector<std::string> intro_frame_names;
+  std::vector<std::string> loop_frame_names;
 
-    while ((de = readdir(dir.get())) != nullptr) {
-        int value, num_chars;
-        if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
-            intro_frame_names.emplace_back(de->d_name, num_chars);
-        } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
-            loop_frame_names.emplace_back(de->d_name, num_chars);
-        }
+  while ((de = readdir(dir.get())) != nullptr) {
+    int value, num_chars;
+    if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
+      intro_frame_names.emplace_back(de->d_name, num_chars);
+    } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
+      loop_frame_names.emplace_back(de->d_name, num_chars);
     }
+  }
 
-    intro_frames = intro_frame_names.size();
-    loop_frames = loop_frame_names.size();
+  intro_frames = intro_frame_names.size();
+  loop_frames = loop_frame_names.size();
 
-    // It's okay to not have an intro.
-    if (intro_frames == 0) intro_done = true;
-    // But you must have an animation.
-    if (loop_frames == 0) abort();
+  // It's okay to not have an intro.
+  if (intro_frames == 0) intro_done = true;
+  // But you must have an animation.
+  if (loop_frames == 0) abort();
 
-    std::sort(intro_frame_names.begin(), intro_frame_names.end());
-    std::sort(loop_frame_names.begin(), loop_frame_names.end());
+  std::sort(intro_frame_names.begin(), intro_frame_names.end());
+  std::sort(loop_frame_names.begin(), loop_frame_names.end());
 
-    introFrames = new GRSurface*[intro_frames];
-    for (size_t i = 0; i < intro_frames; i++) {
-        LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]);
-    }
+  introFrames = new GRSurface*[intro_frames];
+  for (size_t i = 0; i < intro_frames; i++) {
+    LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]);
+  }
 
-    loopFrames = new GRSurface*[loop_frames];
-    for (size_t i = 0; i < loop_frames; i++) {
-        LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]);
-    }
+  loopFrames = new GRSurface*[loop_frames];
+  for (size_t i = 0; i < loop_frames; i++) {
+    LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]);
+  }
 }
 
 void ScreenRecoveryUI::SetBackground(Icon icon) {
-    pthread_mutex_lock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
 
-    currentIcon = icon;
-    update_screen_locked();
+  currentIcon = icon;
+  update_screen_locked();
 
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::SetProgressType(ProgressType type) {
-    pthread_mutex_lock(&updateMutex);
-    if (progressBarType != type) {
-        progressBarType = type;
-    }
-    progressScopeStart = 0;
-    progressScopeSize = 0;
-    progress = 0;
-    update_progress_locked();
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  if (progressBarType != type) {
+    progressBarType = type;
+  }
+  progressScopeStart = 0;
+  progressScopeSize = 0;
+  progress = 0;
+  update_progress_locked();
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::ShowProgress(float portion, float seconds) {
-    pthread_mutex_lock(&updateMutex);
-    progressBarType = DETERMINATE;
-    progressScopeStart += progressScopeSize;
-    progressScopeSize = portion;
-    progressScopeTime = now();
-    progressScopeDuration = seconds;
-    progress = 0;
-    update_progress_locked();
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  progressBarType = DETERMINATE;
+  progressScopeStart += progressScopeSize;
+  progressScopeSize = portion;
+  progressScopeTime = now();
+  progressScopeDuration = seconds;
+  progress = 0;
+  update_progress_locked();
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::SetProgress(float fraction) {
-    pthread_mutex_lock(&updateMutex);
-    if (fraction < 0.0) fraction = 0.0;
-    if (fraction > 1.0) fraction = 1.0;
-    if (progressBarType == DETERMINATE && fraction > progress) {
-        // Skip updates that aren't visibly different.
-        int width = gr_get_width(progressBarEmpty);
-        float scale = width * progressScopeSize;
-        if ((int) (progress * scale) != (int) (fraction * scale)) {
-            progress = fraction;
-            update_progress_locked();
-        }
+  pthread_mutex_lock(&updateMutex);
+  if (fraction < 0.0) fraction = 0.0;
+  if (fraction > 1.0) fraction = 1.0;
+  if (progressBarType == DETERMINATE && fraction > progress) {
+    // Skip updates that aren't visibly different.
+    int width = gr_get_width(progressBarEmpty);
+    float scale = width * progressScopeSize;
+    if ((int)(progress * scale) != (int)(fraction * scale)) {
+      progress = fraction;
+      update_progress_locked();
     }
-    pthread_mutex_unlock(&updateMutex);
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::SetStage(int current, int max) {
-    pthread_mutex_lock(&updateMutex);
-    stage = current;
-    max_stage = max;
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  stage = current;
+  max_stage = max;
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
-    std::string str;
-    android::base::StringAppendV(&str, fmt, ap);
+  std::string str;
+  android::base::StringAppendV(&str, fmt, ap);
 
-    if (copy_to_stdout) {
-        fputs(str.c_str(), stdout);
-    }
+  if (copy_to_stdout) {
+    fputs(str.c_str(), stdout);
+  }
 
-    pthread_mutex_lock(&updateMutex);
-    if (text_rows_ > 0 && text_cols_ > 0) {
-        for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
-            if (*ptr == '\n' || text_col_ >= text_cols_) {
-                text_[text_row_][text_col_] = '\0';
-                text_col_ = 0;
-                text_row_ = (text_row_ + 1) % text_rows_;
-                if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
-            }
-            if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
-        }
+  pthread_mutex_lock(&updateMutex);
+  if (text_rows_ > 0 && text_cols_ > 0) {
+    for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
+      if (*ptr == '\n' || text_col_ >= text_cols_) {
         text_[text_row_][text_col_] = '\0';
-        update_screen_locked();
+        text_col_ = 0;
+        text_row_ = (text_row_ + 1) % text_rows_;
+        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
+      }
+      if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
     }
-    pthread_mutex_unlock(&updateMutex);
+    text_[text_row_][text_col_] = '\0';
+    update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::Print(const char* fmt, ...) {
-    va_list ap;
-    va_start(ap, fmt);
-    PrintV(fmt, true, ap);
-    va_end(ap);
+  va_list ap;
+  va_start(ap, fmt);
+  PrintV(fmt, true, ap);
+  va_end(ap);
 }
 
 void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
-    va_list ap;
-    va_start(ap, fmt);
-    PrintV(fmt, false, ap);
-    va_end(ap);
+  va_list ap;
+  va_start(ap, fmt);
+  PrintV(fmt, false, ap);
+  va_end(ap);
 }
 
 void ScreenRecoveryUI::PutChar(char ch) {
-    pthread_mutex_lock(&updateMutex);
-    if (ch != '\n') text_[text_row_][text_col_++] = ch;
-    if (ch == '\n' || text_col_ >= text_cols_) {
-        text_col_ = 0;
-        ++text_row_;
+  pthread_mutex_lock(&updateMutex);
+  if (ch != '\n') text_[text_row_][text_col_++] = ch;
+  if (ch == '\n' || text_col_ >= text_cols_) {
+    text_col_ = 0;
+    ++text_row_;
 
-        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
-    }
-    pthread_mutex_unlock(&updateMutex);
+    if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::ClearText() {
-    pthread_mutex_lock(&updateMutex);
-    text_col_ = 0;
-    text_row_ = 0;
-    text_top_ = 1;
-    for (size_t i = 0; i < text_rows_; ++i) {
-        memset(text_[i], 0, text_cols_ + 1);
-    }
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  text_col_ = 0;
+  text_row_ = 0;
+  text_top_ = 1;
+  for (size_t i = 0; i < text_rows_; ++i) {
+    memset(text_[i], 0, text_cols_ + 1);
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::ShowFile(FILE* fp) {
-    std::vector<off_t> offsets;
-    offsets.push_back(ftello(fp));
-    ClearText();
+  std::vector<off_t> offsets;
+  offsets.push_back(ftello(fp));
+  ClearText();
 
-    struct stat sb;
-    fstat(fileno(fp), &sb);
+  struct stat sb;
+  fstat(fileno(fp), &sb);
 
-    bool show_prompt = false;
-    while (true) {
-        if (show_prompt) {
-            PrintOnScreenOnly("--(%d%% of %d bytes)--",
-                  static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
-                  static_cast<int>(sb.st_size));
-            Redraw();
-            while (show_prompt) {
-                show_prompt = false;
-                int key = WaitKey();
-                if (key == KEY_POWER || key == KEY_ENTER) {
-                    return;
-                } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
-                    if (offsets.size() <= 1) {
-                        show_prompt = true;
-                    } else {
-                        offsets.pop_back();
-                        fseek(fp, offsets.back(), SEEK_SET);
-                    }
-                } else {
-                    if (feof(fp)) {
-                        return;
-                    }
-                    offsets.push_back(ftello(fp));
-                }
-            }
-            ClearText();
-        }
-
-        int ch = getc(fp);
-        if (ch == EOF) {
-            while (text_row_ < text_rows_ - 1) PutChar('\n');
+  bool show_prompt = false;
+  while (true) {
+    if (show_prompt) {
+      PrintOnScreenOnly("--(%d%% of %d bytes)--",
+                        static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
+                        static_cast<int>(sb.st_size));
+      Redraw();
+      while (show_prompt) {
+        show_prompt = false;
+        int key = WaitKey();
+        if (key == KEY_POWER || key == KEY_ENTER) {
+          return;
+        } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+          if (offsets.size() <= 1) {
             show_prompt = true;
+          } else {
+            offsets.pop_back();
+            fseek(fp, offsets.back(), SEEK_SET);
+          }
         } else {
-            PutChar(ch);
-            if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
-                show_prompt = true;
-            }
+          if (feof(fp)) {
+            return;
+          }
+          offsets.push_back(ftello(fp));
         }
+      }
+      ClearText();
     }
+
+    int ch = getc(fp);
+    if (ch == EOF) {
+      while (text_row_ < text_rows_ - 1) PutChar('\n');
+      show_prompt = true;
+    } else {
+      PutChar(ch);
+      if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
+        show_prompt = true;
+      }
+    }
+  }
 }
 
 void ScreenRecoveryUI::ShowFile(const char* filename) {
-    FILE* fp = fopen_path(filename, "re");
-    if (fp == nullptr) {
-        Print("  Unable to open %s: %s\n", filename, strerror(errno));
-        return;
-    }
+  FILE* fp = fopen_path(filename, "re");
+  if (fp == nullptr) {
+    Print("  Unable to open %s: %s\n", filename, strerror(errno));
+    return;
+  }
 
-    char** old_text = text_;
-    size_t old_text_col = text_col_;
-    size_t old_text_row = text_row_;
-    size_t old_text_top = text_top_;
+  char** old_text = text_;
+  size_t old_text_col = text_col_;
+  size_t old_text_row = text_row_;
+  size_t old_text_top = text_top_;
 
-    // Swap in the alternate screen and clear it.
-    text_ = file_viewer_text_;
-    ClearText();
+  // Swap in the alternate screen and clear it.
+  text_ = file_viewer_text_;
+  ClearText();
 
-    ShowFile(fp);
-    fclose(fp);
+  ShowFile(fp);
+  fclose(fp);
 
-    text_ = old_text;
-    text_col_ = old_text_col;
-    text_row_ = old_text_row;
-    text_top_ = old_text_top;
+  text_ = old_text;
+  text_col_ = old_text_col;
+  text_row_ = old_text_row;
+  text_top_ = old_text_top;
 }
 
-void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
+void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
                                  int initial_selection) {
-    pthread_mutex_lock(&updateMutex);
-    if (text_rows_ > 0 && text_cols_ > 0) {
-        menu_headers_ = headers;
-        size_t i = 0;
-        for (; i < text_rows_ && items[i] != nullptr; ++i) {
-            strncpy(menu_[i], items[i], text_cols_ - 1);
-            menu_[i][text_cols_ - 1] = '\0';
-        }
-        menu_items = i;
-        show_menu = true;
-        menu_sel = initial_selection;
-        update_screen_locked();
+  pthread_mutex_lock(&updateMutex);
+  if (text_rows_ > 0 && text_cols_ > 0) {
+    menu_headers_ = headers;
+    menu_.clear();
+    for (size_t i = 0; i < text_rows_ && items[i] != nullptr; ++i) {
+      menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1)));
     }
-    pthread_mutex_unlock(&updateMutex);
+    menu_items = static_cast<int>(menu_.size());
+    show_menu = true;
+    menu_sel = initial_selection;
+    update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 int ScreenRecoveryUI::SelectMenu(int sel) {
-    pthread_mutex_lock(&updateMutex);
-    if (show_menu) {
-        int old_sel = menu_sel;
-        menu_sel = sel;
+  pthread_mutex_lock(&updateMutex);
+  if (show_menu) {
+    int old_sel = menu_sel;
+    menu_sel = sel;
 
-        // Wrap at top and bottom.
-        if (menu_sel < 0) menu_sel = menu_items - 1;
-        if (menu_sel >= menu_items) menu_sel = 0;
+    // Wrap at top and bottom.
+    if (menu_sel < 0) menu_sel = menu_items - 1;
+    if (menu_sel >= menu_items) menu_sel = 0;
 
-        sel = menu_sel;
-        if (menu_sel != old_sel) update_screen_locked();
-    }
-    pthread_mutex_unlock(&updateMutex);
-    return sel;
+    sel = menu_sel;
+    if (menu_sel != old_sel) update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
+  return sel;
 }
 
 void ScreenRecoveryUI::EndMenu() {
-    pthread_mutex_lock(&updateMutex);
-    if (show_menu && text_rows_ > 0 && text_cols_ > 0) {
-        show_menu = false;
-        update_screen_locked();
-    }
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  if (show_menu && text_rows_ > 0 && text_cols_ > 0) {
+    show_menu = false;
+    update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 bool ScreenRecoveryUI::IsTextVisible() {
-    pthread_mutex_lock(&updateMutex);
-    int visible = show_text;
-    pthread_mutex_unlock(&updateMutex);
-    return visible;
+  pthread_mutex_lock(&updateMutex);
+  int visible = show_text;
+  pthread_mutex_unlock(&updateMutex);
+  return visible;
 }
 
 bool ScreenRecoveryUI::WasTextEverVisible() {
-    pthread_mutex_lock(&updateMutex);
-    int ever_visible = show_text_ever;
-    pthread_mutex_unlock(&updateMutex);
-    return ever_visible;
+  pthread_mutex_lock(&updateMutex);
+  int ever_visible = show_text_ever;
+  pthread_mutex_unlock(&updateMutex);
+  return ever_visible;
 }
 
 void ScreenRecoveryUI::ShowText(bool visible) {
-    pthread_mutex_lock(&updateMutex);
-    show_text = visible;
-    if (show_text) show_text_ever = true;
-    update_screen_locked();
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  show_text = visible;
+  if (show_text) show_text_ever = true;
+  update_screen_locked();
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::Redraw() {
-    pthread_mutex_lock(&updateMutex);
-    update_screen_locked();
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  update_screen_locked();
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void ScreenRecoveryUI::KeyLongPress(int) {
-    // Redraw so that if we're in the menu, the highlight
-    // will change color to indicate a successful long press.
-    Redraw();
+  // Redraw so that if we're in the menu, the highlight
+  // will change color to indicate a successful long press.
+  Redraw();
 }
diff --git a/screen_ui.h b/screen_ui.h
index 2500575..8231a2b 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -21,6 +21,7 @@
 #include <stdio.h>
 
 #include <string>
+#include <vector>
 
 #include "ui.h"
 
@@ -30,152 +31,166 @@
 // Implementation of RecoveryUI appropriate for devices with a screen
 // (shows an icon + a progress bar, text logging, menu, etc.)
 class ScreenRecoveryUI : public RecoveryUI {
-  public:
-    ScreenRecoveryUI();
+ public:
+  ScreenRecoveryUI();
 
-    bool Init(const std::string& locale) override;
+  bool Init(const std::string& locale) override;
 
-    // overall recovery state ("background image")
-    void SetBackground(Icon icon);
-    void SetSystemUpdateText(bool security_update);
+  // overall recovery state ("background image")
+  void SetBackground(Icon icon) override;
+  void SetSystemUpdateText(bool security_update) override;
 
-    // progress indicator
-    void SetProgressType(ProgressType type) override;
-    void ShowProgress(float portion, float seconds) override;
-    void SetProgress(float fraction) override;
+  // progress indicator
+  void SetProgressType(ProgressType type) override;
+  void ShowProgress(float portion, float seconds) override;
+  void SetProgress(float fraction) override;
 
-    void SetStage(int current, int max) override;
+  void SetStage(int current, int max) override;
 
-    // text log
-    void ShowText(bool visible) override;
-    bool IsTextVisible() override;
-    bool WasTextEverVisible() override;
+  // text log
+  void ShowText(bool visible) override;
+  bool IsTextVisible() override;
+  bool WasTextEverVisible() override;
 
-    // printing messages
-    void Print(const char* fmt, ...) __printflike(2, 3);
-    void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3);
-    void ShowFile(const char* filename);
+  // printing messages
+  void Print(const char* fmt, ...) override __printflike(2, 3);
+  void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3);
+  void ShowFile(const char* filename) override;
 
-    // menu display
-    void StartMenu(const char* const * headers, const char* const * items,
-                   int initial_selection);
-    int SelectMenu(int sel);
-    void EndMenu();
+  // menu display
+  void StartMenu(const char* const* headers, const char* const* items,
+                 int initial_selection) override;
+  int SelectMenu(int sel) override;
+  void EndMenu() override;
 
-    void KeyLongPress(int);
+  void KeyLongPress(int) override;
 
-    void Redraw();
+  void Redraw();
 
-    enum UIElement {
-        HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO
-    };
-    void SetColor(UIElement e);
+  enum UIElement {
+    HEADER,
+    MENU,
+    MENU_SEL_BG,
+    MENU_SEL_BG_ACTIVE,
+    MENU_SEL_FG,
+    LOG,
+    TEXT_FILL,
+    INFO
+  };
+  void SetColor(UIElement e) const;
 
-  protected:
-    // The margin that we don't want to use for showing texts (e.g. round screen, or screen with
-    // rounded corners).
-    const int kMarginWidth;
-    const int kMarginHeight;
+ protected:
+  // The margin that we don't want to use for showing texts (e.g. round screen, or screen with
+  // rounded corners).
+  const int kMarginWidth;
+  const int kMarginHeight;
 
-    // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
-    const float density_;
+  // Number of frames per sec (default: 30) for both parts of the animation.
+  const int kAnimationFps;
 
-    Icon currentIcon;
+  // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
+  const float density_;
 
-    // The layout to use.
-    int layout_;
+  Icon currentIcon;
 
-    GRSurface* error_icon;
+  // The layout to use.
+  int layout_;
 
-    GRSurface* erasing_text;
-    GRSurface* error_text;
-    GRSurface* installing_text;
-    GRSurface* no_command_text;
+  GRSurface* error_icon;
 
-    GRSurface** introFrames;
-    GRSurface** loopFrames;
+  GRSurface* erasing_text;
+  GRSurface* error_text;
+  GRSurface* installing_text;
+  GRSurface* no_command_text;
 
-    GRSurface* progressBarEmpty;
-    GRSurface* progressBarFill;
-    GRSurface* stageMarkerEmpty;
-    GRSurface* stageMarkerFill;
+  GRSurface** introFrames;
+  GRSurface** loopFrames;
 
-    ProgressType progressBarType;
+  GRSurface* progressBarEmpty;
+  GRSurface* progressBarFill;
+  GRSurface* stageMarkerEmpty;
+  GRSurface* stageMarkerFill;
 
-    float progressScopeStart, progressScopeSize, progress;
-    double progressScopeTime, progressScopeDuration;
+  ProgressType progressBarType;
 
-    // true when both graphics pages are the same (except for the progress bar).
-    bool pagesIdentical;
+  float progressScopeStart, progressScopeSize, progress;
+  double progressScopeTime, progressScopeDuration;
 
-    size_t text_cols_, text_rows_;
+  // true when both graphics pages are the same (except for the progress bar).
+  bool pagesIdentical;
 
-    // Log text overlay, displayed when a magic key is pressed.
-    char** text_;
-    size_t text_col_, text_row_, text_top_;
+  size_t text_cols_, text_rows_;
 
-    bool show_text;
-    bool show_text_ever;   // has show_text ever been true?
+  // Log text overlay, displayed when a magic key is pressed.
+  char** text_;
+  size_t text_col_, text_row_, text_top_;
 
-    char** menu_;
-    const char* const* menu_headers_;
-    bool show_menu;
-    int menu_items, menu_sel;
+  bool show_text;
+  bool show_text_ever;  // has show_text ever been true?
 
-    // An alternate text screen, swapped with 'text_' when we're viewing a log file.
-    char** file_viewer_text_;
+  std::vector<std::string> menu_;
+  const char* const* menu_headers_;
+  bool show_menu;
+  int menu_items, menu_sel;
 
-    pthread_t progress_thread_;
+  // An alternate text screen, swapped with 'text_' when we're viewing a log file.
+  char** file_viewer_text_;
 
-    // Number of intro frames and loop frames in the animation.
-    size_t intro_frames;
-    size_t loop_frames;
+  pthread_t progress_thread_;
 
-    size_t current_frame;
-    bool intro_done;
+  // Number of intro frames and loop frames in the animation.
+  size_t intro_frames;
+  size_t loop_frames;
 
-    // Number of frames per sec (default: 30) for both parts of the animation.
-    int animation_fps;
+  size_t current_frame;
+  bool intro_done;
 
-    int stage, max_stage;
+  int stage, max_stage;
 
-    int char_width_;
-    int char_height_;
+  int char_width_;
+  int char_height_;
 
-    pthread_mutex_t updateMutex;
+  pthread_mutex_t updateMutex;
 
-    virtual bool InitTextParams();
+  virtual bool InitTextParams();
 
-    virtual void draw_background_locked();
-    virtual void draw_foreground_locked();
-    virtual void draw_screen_locked();
-    virtual void update_screen_locked();
-    virtual void update_progress_locked();
+  virtual void draw_background_locked();
+  virtual void draw_foreground_locked();
+  virtual void draw_screen_locked();
+  virtual void update_screen_locked();
+  virtual void update_progress_locked();
 
-    GRSurface* GetCurrentFrame();
-    GRSurface* GetCurrentText();
+  GRSurface* GetCurrentFrame() const;
+  GRSurface* GetCurrentText() const;
 
-    static void* ProgressThreadStartRoutine(void* data);
-    void ProgressThreadLoop();
+  static void* ProgressThreadStartRoutine(void* data);
+  void ProgressThreadLoop();
 
-    virtual void ShowFile(FILE*);
-    virtual void PrintV(const char*, bool, va_list);
-    void PutChar(char);
-    void ClearText();
+  virtual void ShowFile(FILE*);
+  virtual void PrintV(const char*, bool, va_list);
+  void PutChar(char);
+  void ClearText();
 
-    void LoadAnimation();
-    void LoadBitmap(const char* filename, GRSurface** surface);
-    void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
+  void LoadAnimation();
+  void LoadBitmap(const char* filename, GRSurface** surface);
+  void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
 
-    int PixelsFromDp(int dp) const;
-    virtual int GetAnimationBaseline();
-    virtual int GetProgressBaseline();
-    virtual int GetTextBaseline();
+  int PixelsFromDp(int dp) const;
+  virtual int GetAnimationBaseline() const;
+  virtual int GetProgressBaseline() const;
+  virtual int GetTextBaseline() const;
 
-    virtual void DrawHorizontalRule(int* y);
-    virtual void DrawHighlightBar(int x, int y, int width, int height) const;
-    virtual void DrawTextLine(int x, int* y, const char* line, bool bold) const;
-    void DrawTextLines(int x, int* y, const char* const* lines) const;
+  // Draws a highlight bar at (x, y) - (x + width, y + height).
+  virtual void DrawHighlightBar(int x, int y, int width, int height) const;
+  // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
+  virtual int DrawHorizontalRule(int y) const;
+  // Draws a line of text. Returns the offset it should be moving along Y-axis.
+  virtual int DrawTextLine(int x, int y, const char* line, bool bold) const;
+  // Draws multiple text lines. Returns the offset it should be moving along Y-axis.
+  int DrawTextLines(int x, int y, const char* const* lines) const;
+  // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines.
+  // Returns the offset it should be moving along Y-axis.
+  int DrawWrappedTextLines(int x, int y, const char* const* lines) const;
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 346873d..f2497b8 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -111,7 +111,8 @@
     component/update_verifier_test.cpp \
     component/verifier_test.cpp
 
-LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_SHARED_LIBRARIES := \
+    libhidlbase
 
 tune2fs_static_libraries := \
     libext2_com_err \
@@ -160,6 +161,7 @@
     libfec_rs \
     libsquashfs_utils \
     libcutils \
+    libbrotli \
     $(tune2fs_static_libraries)
 
 testdata_files := $(call find-subdir-files, testdata/*)
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index 6f5960b..bf25aeb 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -328,6 +328,39 @@
   verify_patched_image(src, patch, tgt);
 }
 
+TEST(ImgdiffTest, image_mode_bad_gzip) {
+  // Modify the uncompressed length in the gzip footer.
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+                                       '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+                                       '\xff', '\xff', '\xff' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // Modify the uncompressed length in the gzip footer.
+  const std::vector<char> tgt_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z',    '\x1f', '\x8b',
+    '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+    '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\xff', '\xff', '\xff'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+  verify_patched_image(src, patch, tgt);
+}
+
 TEST(ImgdiffTest, image_mode_different_num_chunks) {
   // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test".
   const std::vector<char> src_data = {
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
index 5fc7ef6..b04e118 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/component/update_verifier_test.cpp
@@ -81,3 +81,16 @@
   ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
   ASSERT_FALSE(verify_image(temp_file.path));
 }
+
+TEST_F(UpdateVerifierTest, verify_image_legacy_care_map) {
+  // This test relies on dm-verity support.
+  if (!verity_supported) {
+    GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+    return;
+  }
+
+  TemporaryFile temp_file;
+  std::string content = "/dev/block/bootdevice/by-name/system\n2,1,0";
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+  ASSERT_TRUE(verify_image(temp_file.path));
+}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 35e87fd..6c341c1 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -15,10 +15,12 @@
  */
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <vector>
@@ -29,6 +31,7 @@
 #include <android-base/strings.h>
 #include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
+#include <brotli/encode.h>
 #include <bsdiff.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_archive.h>
@@ -224,102 +227,6 @@
     expect("", script6.c_str(), kNoCause);
 }
 
-TEST_F(UpdaterTest, package_extract_dir) {
-  // package_extract_dir expects 2 arguments.
-  expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
-  expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
-  expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
-
-  std::string zip_path = from_testdata_base("ziptest_valid.zip");
-  ZipArchiveHandle handle;
-  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
-
-  // Need to set up the ziphandle.
-  UpdaterInfo updater_info;
-  updater_info.package_zip = handle;
-
-  // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
-  TemporaryDir td;
-  std::string temp_dir(td.path);
-  std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
-  expect("t", script.c_str(), kNoCause, &updater_info);
-
-  // Verify.
-  std::string data;
-  std::string file_c = temp_dir + "/c.txt";
-  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
-  ASSERT_EQ(kCTxtContents, data);
-
-  std::string file_d = temp_dir + "/d.txt";
-  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
-  ASSERT_EQ(kDTxtContents, data);
-
-  // Modify the contents in order to retry. It's expected to be overwritten.
-  ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
-  ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
-
-  // Extract again and verify.
-  expect("t", script.c_str(), kNoCause, &updater_info);
-
-  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
-  ASSERT_EQ(kCTxtContents, data);
-  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
-  ASSERT_EQ(kDTxtContents, data);
-
-  // Clean up the temp files under td.
-  ASSERT_EQ(0, unlink(file_c.c_str()));
-  ASSERT_EQ(0, unlink(file_d.c_str()));
-
-  // Extracting "b/" (with slash) should give the same result.
-  script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
-  expect("t", script.c_str(), kNoCause, &updater_info);
-
-  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
-  ASSERT_EQ(kCTxtContents, data);
-  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
-  ASSERT_EQ(kDTxtContents, data);
-
-  ASSERT_EQ(0, unlink(file_c.c_str()));
-  ASSERT_EQ(0, unlink(file_d.c_str()));
-
-  // Extracting "" is allowed. The entries will carry the path name.
-  script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
-  expect("t", script.c_str(), kNoCause, &updater_info);
-
-  std::string file_a = temp_dir + "/a.txt";
-  ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
-  ASSERT_EQ(kATxtContents, data);
-  std::string file_b = temp_dir + "/b.txt";
-  ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
-  ASSERT_EQ(kBTxtContents, data);
-  std::string file_b_c = temp_dir + "/b/c.txt";
-  ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
-  ASSERT_EQ(kCTxtContents, data);
-  std::string file_b_d = temp_dir + "/b/d.txt";
-  ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
-  ASSERT_EQ(kDTxtContents, data);
-
-  ASSERT_EQ(0, unlink(file_a.c_str()));
-  ASSERT_EQ(0, unlink(file_b.c_str()));
-  ASSERT_EQ(0, unlink(file_b_c.c_str()));
-  ASSERT_EQ(0, unlink(file_b_d.c_str()));
-  ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
-
-  // Extracting non-existent entry should still give "t".
-  script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
-  expect("t", script.c_str(), kNoCause, &updater_info);
-
-  // Only relative zip_path is allowed.
-  script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
-  expect("", script.c_str(), kNoCause, &updater_info);
-
-  // Only absolute dest_path is allowed.
-  script = "package_extract_dir(\"b\", \"path\")";
-  expect("", script.c_str(), kNoCause, &updater_info);
-
-  CloseArchive(handle);
-}
-
 // TODO: Test extracting to block device.
 TEST_F(UpdaterTest, package_extract_file) {
   // package_extract_file expects 1 or 2 arguments.
@@ -578,7 +485,7 @@
   UpdaterInfo updater_info;
   updater_info.package_zip = handle;
   TemporaryFile temp_pipe;
-  updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+  updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
   updater_info.package_zip_addr = map.addr;
   updater_info.package_zip_len = map.length;
 
@@ -654,7 +561,7 @@
   UpdaterInfo updater_info;
   updater_info.package_zip = handle;
   TemporaryFile temp_pipe;
-  updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+  updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
   updater_info.package_zip_addr = map.addr;
   updater_info.package_zip_len = map.length;
 
@@ -672,4 +579,79 @@
   std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
       R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
   expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, brotli_new_data) {
+  // Create a zip file with new_data.
+  TemporaryFile zip_file;
+  FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
+  ZipWriter zip_writer(zip_file_ptr);
+
+  // Add a brotli compressed new data entry.
+  ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0));
+
+  auto generator = []() { return rand() % 128; };
+  // Generate 100 blocks of random data.
+  std::string brotli_new_data;
+  brotli_new_data.reserve(4096 * 100);
+  generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
+
+  size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
+  std::vector<uint8_t> encoded_data(encoded_size);
+  ASSERT_TRUE(BrotliEncoderCompress(
+      BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
+      reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, encoded_data.data()));
+
+  ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+  // Add a dummy patch data.
+  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  // Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
+  // This helps us to catch potential short writes.
+  std::vector<std::string> transfer_list = {
+    "4",
+    "100",
+    "0",
+    "0",
+    "new 2,0,1",
+    "new 2,1,2",
+    "new 4,2,50,50,97",
+    "new 2,97,98",
+    "new 2,98,99",
+    "new 2,99,100",
+  };
+  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
+  std::string commands = android::base::Join(transfer_list, '\n');
+  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+  ASSERT_EQ(0, zip_writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  // Check if we can decompress the new data correctly.
+  TemporaryFile update_file;
+  std::string script_new_data =
+      "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list"), "new.dat.br", "patch_data"))";
+  expect("t", script_new_data.c_str(), kNoCause, &updater_info);
+
+  std::string updated_content;
+  ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
+  ASSERT_EQ(brotli_new_data, updated_content);
+  CloseArchive(handle);
 }
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
index d36dd33..92c6ef2 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -141,7 +141,7 @@
   // under recovery.
   void SetUp() override {
     std::string file_path = GetParam();
-    fp = fopen(file_path.c_str(), "rb");
+    fp = fopen(file_path.c_str(), "rbe");
     ASSERT_NE(nullptr, fp);
 
     unsigned char header[8];
diff --git a/tools/recovery_l10n/res/values-en-rCA/strings.xml b/tools/recovery_l10n/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..dc75c23
--- /dev/null
+++ b/tools/recovery_l10n/res/values-en-rCA/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="recovery_installing" msgid="2013591905463558223">"Installing system update"</string>
+    <string name="recovery_erasing" msgid="7334826894904037088">"Erasing"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"No command"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"Error!"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"Installing security update"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-en-rXC/strings.xml b/tools/recovery_l10n/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..2d528b3
--- /dev/null
+++ b/tools/recovery_l10n/res/values-en-rXC/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="recovery_installing" msgid="2013591905463558223">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎Installing system update‎‏‎‎‏‎"</string>
+    <string name="recovery_erasing" msgid="7334826894904037088">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎Erasing‎‏‎‎‏‎"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml
index a8a876e..65d0033 100644
--- a/tools/recovery_l10n/res/values-hi/strings.xml
+++ b/tools/recovery_l10n/res/values-hi/strings.xml
@@ -3,7 +3,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अपडेट इंस्टॉल किया जा रहा है"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"मिटाया जा रहा है"</string>
-    <string name="recovery_no_command" msgid="4465476568623024327">"कोई आदेश नहीं"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"कोई निर्देश नहीं मिला"</string>
     <string name="recovery_error" msgid="5748178989622716736">"गड़बड़ी!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल किया जा रहा है"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-mr/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml
index 8cf86f7..5f82033 100644
--- a/tools/recovery_l10n/res/values-mr/strings.xml
+++ b/tools/recovery_l10n/res/values-mr/strings.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अद्यतन स्थापित करीत आहे"</string>
+    <string name="recovery_installing" msgid="2013591905463558223">"सिस्टम अपडेट इंस्टॉल करत आहे"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"मिटवत आहे"</string>
-    <string name="recovery_no_command" msgid="4465476568623024327">"कोणताही आदेश नाही"</string>
-    <string name="recovery_error" msgid="5748178989622716736">"त्रुटी!"</string>
-    <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अद्यतन स्थापित करीत आहे"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"कोणतीही कमांड नाही"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"एरर!"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल करत आहे"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-pa/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml
index 8564c9c..27972d1 100644
--- a/tools/recovery_l10n/res/values-pa/strings.xml
+++ b/tools/recovery_l10n/res/values-pa/strings.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="recovery_installing" msgid="2013591905463558223">"ਸਿਸਟਮ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="recovery_installing" msgid="2013591905463558223">"ਸਿਸਟਮ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"ਮਿਟਾਈ ਜਾ ਰਹੀ ਹੈ"</string>
-    <string name="recovery_no_command" msgid="4465476568623024327">"ਕੋਈ ਕਮਾਂਡ ਨਹੀਂ"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"ਕੋਈ ਆਦੇਸ਼ ਨਹੀਂ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ਅਸ਼ੁੱਧੀ!"</string>
-    <string name="recovery_installing_security" msgid="9184031299717114342">"ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml
index cfb02c9..e35c82b 100644
--- a/tools/recovery_l10n/res/values-te/strings.xml
+++ b/tools/recovery_l10n/res/values-te/strings.xml
@@ -4,6 +4,6 @@
     <string name="recovery_installing" msgid="2013591905463558223">"సిస్టమ్ నవీకరణను ఇన్‍స్టాల్ చేస్తోంది"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"డేటాను తొలగిస్తోంది"</string>
     <string name="recovery_no_command" msgid="4465476568623024327">"ఆదేశం లేదు"</string>
-    <string name="recovery_error" msgid="5748178989622716736">"లోపం సంభవించింది!"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"ఎర్రర్ సంభవించింది!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"భద్రతా నవీకరణను ఇన్‌స్టాల్ చేస్తోంది"</string>
 </resources>
diff --git a/ui.cpp b/ui.cpp
index cad7449..e80d7ed 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -54,6 +54,9 @@
       rtl_locale_(false),
       brightness_normal_(50),
       brightness_dimmed_(25),
+      touch_screen_allowed_(false),
+      kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
+      kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
       key_queue_len(0),
       key_last_down(-1),
       key_long_press(false),
@@ -64,6 +67,9 @@
       has_power_key(false),
       has_up_key(false),
       has_down_key(false),
+      has_touch_screen(false),
+      touch_slot_(0),
+      is_bootreason_recovery_ui_(false),
       screensaver_state_(ScreensaverState::DISABLED) {
   pthread_mutex_init(&key_queue_mutex, nullptr);
   pthread_cond_init(&key_queue_cond, nullptr);
@@ -71,23 +77,25 @@
 }
 
 void RecoveryUI::OnKeyDetected(int key_code) {
-    if (key_code == KEY_POWER) {
-        has_power_key = true;
-    } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) {
-        has_down_key = true;
-    } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
-        has_up_key = true;
-    }
+  if (key_code == KEY_POWER) {
+    has_power_key = true;
+  } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) {
+    has_down_key = true;
+  } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
+    has_up_key = true;
+  } else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) {
+    has_touch_screen = true;
+  }
 }
 
 // Reads input events, handles special hot keys, and adds to the key queue.
 static void* InputThreadLoop(void*) {
-    while (true) {
-        if (!ev_wait(-1)) {
-            ev_dispatch();
-        }
+  while (true) {
+    if (!ev_wait(-1)) {
+      ev_dispatch();
     }
-    return nullptr;
+  }
+  return nullptr;
 }
 
 bool RecoveryUI::InitScreensaver() {
@@ -128,10 +136,28 @@
   // Set up the locale info.
   SetLocale(locale);
 
-  ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
+  ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),
+          touch_screen_allowed_);
 
   ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
 
+  if (touch_screen_allowed_) {
+    ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+
+    // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of
+    // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way
+    // to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or
+    // with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text
+    // mode will be turned on automatically on debuggable builds, even without a swipe.
+    std::string cmdline;
+    if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
+      is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos;
+    } else {
+      // Non-fatal, and won't affect Init() result.
+      PLOG(WARNING) << "Failed to read /proc/cmdline";
+    }
+  }
+
   if (!InitScreensaver()) {
     LOG(INFO) << "Screensaver disabled";
   }
@@ -140,40 +166,157 @@
   return true;
 }
 
+void RecoveryUI::OnTouchDetected(int dx, int dy) {
+  enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction;
+
+  // We only consider a valid swipe if:
+  // - the delta along one axis is below kTouchLowThreshold;
+  // - and the delta along the other axis is beyond kTouchHighThreshold.
+  if (abs(dy) < kTouchLowThreshold && abs(dx) > kTouchHighThreshold) {
+    direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT;
+  } else if (abs(dx) < kTouchLowThreshold && abs(dy) > kTouchHighThreshold) {
+    direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
+  } else {
+    LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << kTouchLowThreshold
+               << ", high: " << kTouchHighThreshold << ")";
+    return;
+  }
+
+  // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui.
+  if (is_bootreason_recovery_ui_ && !IsTextVisible()) {
+    ShowText(true);
+    return;
+  }
+
+  LOG(DEBUG) << "Swipe direction=" << direction;
+  switch (direction) {
+    case SwipeDirection::UP:
+      ProcessKey(KEY_UP, 1);  // press up key
+      ProcessKey(KEY_UP, 0);  // and release it
+      break;
+
+    case SwipeDirection::DOWN:
+      ProcessKey(KEY_DOWN, 1);  // press down key
+      ProcessKey(KEY_DOWN, 0);  // and release it
+      break;
+
+    case SwipeDirection::LEFT:
+    case SwipeDirection::RIGHT:
+      ProcessKey(KEY_POWER, 1);  // press power key
+      ProcessKey(KEY_POWER, 0);  // and release it
+      break;
+  };
+}
+
 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
-    struct input_event ev;
-    if (ev_get_input(fd, epevents, &ev) == -1) {
-        return -1;
-    }
+  struct input_event ev;
+  if (ev_get_input(fd, epevents, &ev) == -1) {
+    return -1;
+  }
 
-    if (ev.type == EV_SYN) {
-        return 0;
-    } else if (ev.type == EV_REL) {
-        if (ev.code == REL_Y) {
-            // accumulate the up or down motion reported by
-            // the trackball.  When it exceeds a threshold
-            // (positive or negative), fake an up/down
-            // key event.
-            rel_sum += ev.value;
-            if (rel_sum > 3) {
-                ProcessKey(KEY_DOWN, 1);   // press down key
-                ProcessKey(KEY_DOWN, 0);   // and release it
-                rel_sum = 0;
-            } else if (rel_sum < -3) {
-                ProcessKey(KEY_UP, 1);     // press up key
-                ProcessKey(KEY_UP, 0);     // and release it
-                rel_sum = 0;
-            }
-        }
-    } else {
-        rel_sum = 0;
-    }
+  // Touch inputs handling.
+  //
+  // We handle the touch inputs by tracking the position changes between initial contacting and
+  // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon
+  // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes.
+  //
+  // Per the doc Multi-touch Protocol at below, there are two protocols.
+  // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
+  //
+  // The main difference between the stateless type A protocol and the stateful type B slot protocol
+  // lies in the usage of identifiable contacts to reduce the amount of data sent to userspace. The
+  // slot protocol (i.e. type B) sends ABS_MT_TRACKING_ID with a unique id on initial contact, and
+  // sends ABS_MT_TRACKING_ID -1 upon lifting the contact. Protocol A doesn't send
+  // ABS_MT_TRACKING_ID -1 on lifting, but the driver may additionally report BTN_TOUCH event.
+  //
+  // For protocol A, we rely on BTN_TOUCH to recognize lifting, while for protocol B we look for
+  // ABS_MT_TRACKING_ID being -1.
+  //
+  // Touch input events will only be available if touch_screen_allowed_ is set.
 
-    if (ev.type == EV_KEY && ev.code <= KEY_MAX) {
-        ProcessKey(ev.code, ev.value);
+  if (ev.type == EV_SYN) {
+    if (touch_screen_allowed_ && ev.code == SYN_REPORT) {
+      // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the
+      // contact.
+      if (touch_finger_down_ && !touch_swiping_) {
+        touch_start_X_ = touch_X_;
+        touch_start_Y_ = touch_Y_;
+        touch_swiping_ = true;
+      } else if (!touch_finger_down_ && touch_swiping_) {
+        touch_swiping_ = false;
+        OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_);
+      }
     }
-
     return 0;
+  }
+
+  if (ev.type == EV_REL) {
+    if (ev.code == REL_Y) {
+      // accumulate the up or down motion reported by
+      // the trackball.  When it exceeds a threshold
+      // (positive or negative), fake an up/down
+      // key event.
+      rel_sum += ev.value;
+      if (rel_sum > 3) {
+        ProcessKey(KEY_DOWN, 1);  // press down key
+        ProcessKey(KEY_DOWN, 0);  // and release it
+        rel_sum = 0;
+      } else if (rel_sum < -3) {
+        ProcessKey(KEY_UP, 1);  // press up key
+        ProcessKey(KEY_UP, 0);  // and release it
+        rel_sum = 0;
+      }
+    }
+  } else {
+    rel_sum = 0;
+  }
+
+  if (touch_screen_allowed_ && ev.type == EV_ABS) {
+    if (ev.code == ABS_MT_SLOT) {
+      touch_slot_ = ev.value;
+    }
+    // Ignore other fingers.
+    if (touch_slot_ > 0) return 0;
+
+    switch (ev.code) {
+      case ABS_MT_POSITION_X:
+        touch_X_ = ev.value;
+        touch_finger_down_ = true;
+        break;
+
+      case ABS_MT_POSITION_Y:
+        touch_Y_ = ev.value;
+        touch_finger_down_ = true;
+        break;
+
+      case ABS_MT_TRACKING_ID:
+        // Protocol B: -1 marks lifting the contact.
+        if (ev.value < 0) touch_finger_down_ = false;
+        break;
+    }
+    return 0;
+  }
+
+  if (ev.type == EV_KEY && ev.code <= KEY_MAX) {
+    if (touch_screen_allowed_) {
+      if (ev.code == BTN_TOUCH) {
+        // A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means
+        // lifting the contact.
+        touch_finger_down_ = (ev.value == 1);
+      }
+
+      // Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger
+      // additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than
+      // KEY_POWER and KEY_UP as KEY_DOWN).
+      if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) {
+        return 0;
+      }
+    }
+
+    ProcessKey(ev.code, ev.value);
+  }
+
+  return 0;
 }
 
 // Process a key-up or -down event.  A key is "registered" when it is
@@ -189,82 +332,84 @@
 //
 // updown == 1 for key down events; 0 for key up events
 void RecoveryUI::ProcessKey(int key_code, int updown) {
-    bool register_key = false;
-    bool long_press = false;
-    bool reboot_enabled;
+  bool register_key = false;
+  bool long_press = false;
+  bool reboot_enabled;
 
-    pthread_mutex_lock(&key_queue_mutex);
-    key_pressed[key_code] = updown;
-    if (updown) {
-        ++key_down_count;
-        key_last_down = key_code;
-        key_long_press = false;
-        key_timer_t* info = new key_timer_t;
-        info->ui = this;
-        info->key_code = key_code;
-        info->count = key_down_count;
-        pthread_t thread;
-        pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info);
-        pthread_detach(thread);
-    } else {
-        if (key_last_down == key_code) {
-            long_press = key_long_press;
-            register_key = true;
-        }
-        key_last_down = -1;
+  pthread_mutex_lock(&key_queue_mutex);
+  key_pressed[key_code] = updown;
+  if (updown) {
+    ++key_down_count;
+    key_last_down = key_code;
+    key_long_press = false;
+    key_timer_t* info = new key_timer_t;
+    info->ui = this;
+    info->key_code = key_code;
+    info->count = key_down_count;
+    pthread_t thread;
+    pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info);
+    pthread_detach(thread);
+  } else {
+    if (key_last_down == key_code) {
+      long_press = key_long_press;
+      register_key = true;
     }
-    reboot_enabled = enable_reboot;
-    pthread_mutex_unlock(&key_queue_mutex);
+    key_last_down = -1;
+  }
+  reboot_enabled = enable_reboot;
+  pthread_mutex_unlock(&key_queue_mutex);
 
-    if (register_key) {
-        switch (CheckKey(key_code, long_press)) {
-          case RecoveryUI::IGNORE:
-            break;
+  if (register_key) {
+    switch (CheckKey(key_code, long_press)) {
+      case RecoveryUI::IGNORE:
+        break;
 
-          case RecoveryUI::TOGGLE:
-            ShowText(!IsTextVisible());
-            break;
+      case RecoveryUI::TOGGLE:
+        ShowText(!IsTextVisible());
+        break;
 
-          case RecoveryUI::REBOOT:
-            if (reboot_enabled) {
-                reboot("reboot,");
-                while (true) { pause(); }
-            }
-            break;
-
-          case RecoveryUI::ENQUEUE:
-            EnqueueKey(key_code);
-            break;
+      case RecoveryUI::REBOOT:
+        if (reboot_enabled) {
+          reboot("reboot,");
+          while (true) {
+            pause();
+          }
         }
+        break;
+
+      case RecoveryUI::ENQUEUE:
+        EnqueueKey(key_code);
+        break;
     }
+  }
 }
 
 void* RecoveryUI::time_key_helper(void* cookie) {
-    key_timer_t* info = static_cast<key_timer_t*>(cookie);
-    info->ui->time_key(info->key_code, info->count);
-    delete info;
-    return nullptr;
+  key_timer_t* info = static_cast<key_timer_t*>(cookie);
+  info->ui->time_key(info->key_code, info->count);
+  delete info;
+  return nullptr;
 }
 
 void RecoveryUI::time_key(int key_code, int count) {
-    usleep(750000);  // 750 ms == "long"
-    bool long_press = false;
-    pthread_mutex_lock(&key_queue_mutex);
-    if (key_last_down == key_code && key_down_count == count) {
-        long_press = key_long_press = true;
-    }
-    pthread_mutex_unlock(&key_queue_mutex);
-    if (long_press) KeyLongPress(key_code);
+  usleep(750000);  // 750 ms == "long"
+  bool long_press = false;
+  pthread_mutex_lock(&key_queue_mutex);
+  if (key_last_down == key_code && key_down_count == count) {
+    long_press = key_long_press = true;
+  }
+  pthread_mutex_unlock(&key_queue_mutex);
+  if (long_press) KeyLongPress(key_code);
 }
 
 void RecoveryUI::EnqueueKey(int key_code) {
-    pthread_mutex_lock(&key_queue_mutex);
-    const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
-    if (key_queue_len < queue_max) {
-        key_queue[key_queue_len++] = key_code;
-        pthread_cond_signal(&key_queue_cond);
-    }
-    pthread_mutex_unlock(&key_queue_mutex);
+  pthread_mutex_lock(&key_queue_mutex);
+  const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
+  if (key_queue_len < queue_max) {
+    key_queue[key_queue_len++] = key_code;
+    pthread_cond_signal(&key_queue_cond);
+  }
+  pthread_mutex_unlock(&key_queue_mutex);
 }
 
 int RecoveryUI::WaitKey() {
@@ -330,98 +475,104 @@
 }
 
 bool RecoveryUI::IsUsbConnected() {
-    int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
-    if (fd < 0) {
-        printf("failed to open /sys/class/android_usb/android0/state: %s\n",
-               strerror(errno));
-        return 0;
-    }
+  int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
+  if (fd < 0) {
+    printf("failed to open /sys/class/android_usb/android0/state: %s\n", strerror(errno));
+    return 0;
+  }
 
-    char buf;
-    // USB is connected if android_usb state is CONNECTED or CONFIGURED.
-    int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C');
-    if (close(fd) < 0) {
-        printf("failed to close /sys/class/android_usb/android0/state: %s\n",
-               strerror(errno));
-    }
-    return connected;
+  char buf;
+  // USB is connected if android_usb state is CONNECTED or CONFIGURED.
+  int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C');
+  if (close(fd) < 0) {
+    printf("failed to close /sys/class/android_usb/android0/state: %s\n", strerror(errno));
+  }
+  return connected;
 }
 
 bool RecoveryUI::IsKeyPressed(int key) {
-    pthread_mutex_lock(&key_queue_mutex);
-    int pressed = key_pressed[key];
-    pthread_mutex_unlock(&key_queue_mutex);
-    return pressed;
+  pthread_mutex_lock(&key_queue_mutex);
+  int pressed = key_pressed[key];
+  pthread_mutex_unlock(&key_queue_mutex);
+  return pressed;
 }
 
 bool RecoveryUI::IsLongPress() {
-    pthread_mutex_lock(&key_queue_mutex);
-    bool result = key_long_press;
-    pthread_mutex_unlock(&key_queue_mutex);
-    return result;
+  pthread_mutex_lock(&key_queue_mutex);
+  bool result = key_long_press;
+  pthread_mutex_unlock(&key_queue_mutex);
+  return result;
 }
 
 bool RecoveryUI::HasThreeButtons() {
-    return has_power_key && has_up_key && has_down_key;
+  return has_power_key && has_up_key && has_down_key;
+}
+
+bool RecoveryUI::HasPowerKey() const {
+  return has_power_key;
+}
+
+bool RecoveryUI::HasTouchScreen() const {
+  return has_touch_screen;
 }
 
 void RecoveryUI::FlushKeys() {
-    pthread_mutex_lock(&key_queue_mutex);
-    key_queue_len = 0;
-    pthread_mutex_unlock(&key_queue_mutex);
+  pthread_mutex_lock(&key_queue_mutex);
+  key_queue_len = 0;
+  pthread_mutex_unlock(&key_queue_mutex);
 }
 
 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
+  pthread_mutex_lock(&key_queue_mutex);
+  key_long_press = false;
+  pthread_mutex_unlock(&key_queue_mutex);
+
+  // If we have power and volume up keys, that chord is the signal to toggle the text display.
+  if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) {
+    if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) {
+      return TOGGLE;
+    }
+  } else {
+    // Otherwise long press of any button toggles to the text display,
+    // and there's no way to toggle back (but that's pretty useless anyway).
+    if (is_long_press && !IsTextVisible()) {
+      return TOGGLE;
+    }
+
+    // Also, for button-limited devices, a long press is translated to KEY_ENTER.
+    if (is_long_press && IsTextVisible()) {
+      EnqueueKey(KEY_ENTER);
+      return IGNORE;
+    }
+  }
+
+  // Press power seven times in a row to reboot.
+  if (key == KEY_POWER) {
     pthread_mutex_lock(&key_queue_mutex);
-    key_long_press = false;
+    bool reboot_enabled = enable_reboot;
     pthread_mutex_unlock(&key_queue_mutex);
 
-    // If we have power and volume up keys, that chord is the signal to toggle the text display.
-    if (HasThreeButtons()) {
-        if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) {
-            return TOGGLE;
-        }
-    } else {
-        // Otherwise long press of any button toggles to the text display,
-        // and there's no way to toggle back (but that's pretty useless anyway).
-        if (is_long_press && !IsTextVisible()) {
-            return TOGGLE;
-        }
-
-        // Also, for button-limited devices, a long press is translated to KEY_ENTER.
-        if (is_long_press && IsTextVisible()) {
-            EnqueueKey(KEY_ENTER);
-            return IGNORE;
-        }
+    if (reboot_enabled) {
+      ++consecutive_power_keys;
+      if (consecutive_power_keys >= 7) {
+        return REBOOT;
+      }
     }
+  } else {
+    consecutive_power_keys = 0;
+  }
 
-    // Press power seven times in a row to reboot.
-    if (key == KEY_POWER) {
-        pthread_mutex_lock(&key_queue_mutex);
-        bool reboot_enabled = enable_reboot;
-        pthread_mutex_unlock(&key_queue_mutex);
-
-        if (reboot_enabled) {
-            ++consecutive_power_keys;
-            if (consecutive_power_keys >= 7) {
-                return REBOOT;
-            }
-        }
-    } else {
-        consecutive_power_keys = 0;
-    }
-
-    last_key = key;
-    return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
+  last_key = key;
+  return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
 }
 
 void RecoveryUI::KeyLongPress(int) {
 }
 
 void RecoveryUI::SetEnableReboot(bool enabled) {
-    pthread_mutex_lock(&key_queue_mutex);
-    enable_reboot = enabled;
-    pthread_mutex_unlock(&key_queue_mutex);
+  pthread_mutex_lock(&key_queue_mutex);
+  enable_reboot = enabled;
+  pthread_mutex_unlock(&key_queue_mutex);
 }
 
 void RecoveryUI::SetLocale(const std::string& new_locale) {
diff --git a/ui.h b/ui.h
index 823eb65..3d9afec 100644
--- a/ui.h
+++ b/ui.h
@@ -25,163 +25,180 @@
 
 // Abstract class for controlling the user interface during recovery.
 class RecoveryUI {
-  public:
-    RecoveryUI();
+ public:
+  RecoveryUI();
 
-    virtual ~RecoveryUI() { }
+  virtual ~RecoveryUI() {}
 
-    // Initialize the object; called before anything else. UI texts will be
-    // initialized according to the given locale. Returns true on success.
-    virtual bool Init(const std::string& locale);
+  // Initializes the object; called before anything else. UI texts will be initialized according to
+  // the given locale. Returns true on success.
+  virtual bool Init(const std::string& locale);
 
-    // Show a stage indicator.  Call immediately after Init().
-    virtual void SetStage(int current, int max) = 0;
+  // Shows a stage indicator. Called immediately after Init().
+  virtual void SetStage(int current, int max) = 0;
 
-    // Set the overall recovery state ("background image").
-    enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
-    virtual void SetBackground(Icon icon) = 0;
-    virtual void SetSystemUpdateText(bool security_update) = 0;
+  // Sets the overall recovery state ("background image").
+  enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
+  virtual void SetBackground(Icon icon) = 0;
+  virtual void SetSystemUpdateText(bool security_update) = 0;
 
-    // --- progress indicator ---
-    enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE };
-    virtual void SetProgressType(ProgressType determinate) = 0;
+  // --- progress indicator ---
+  enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE };
+  virtual void SetProgressType(ProgressType determinate) = 0;
 
-    // Show a progress bar and define the scope of the next operation:
-    //   portion - fraction of the progress bar the next operation will use
-    //   seconds - expected time interval (progress bar moves at this minimum rate)
-    virtual void ShowProgress(float portion, float seconds) = 0;
+  // Shows a progress bar and define the scope of the next operation:
+  //   portion - fraction of the progress bar the next operation will use
+  //   seconds - expected time interval (progress bar moves at this minimum rate)
+  virtual void ShowProgress(float portion, float seconds) = 0;
 
-    // Set progress bar position (0.0 - 1.0 within the scope defined
-    // by the last call to ShowProgress).
-    virtual void SetProgress(float fraction) = 0;
+  // Sets progress bar position (0.0 - 1.0 within the scope defined by the last call to
+  // ShowProgress).
+  virtual void SetProgress(float fraction) = 0;
 
-    // --- text log ---
+  // --- text log ---
 
-    virtual void ShowText(bool visible) = 0;
+  virtual void ShowText(bool visible) = 0;
 
-    virtual bool IsTextVisible() = 0;
+  virtual bool IsTextVisible() = 0;
 
-    virtual bool WasTextEverVisible() = 0;
+  virtual bool WasTextEverVisible() = 0;
 
-    // Write a message to the on-screen log (shown if the user has
-    // toggled on the text display). Print() will also dump the message
-    // to stdout / log file, while PrintOnScreenOnly() not.
-    virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0;
-    virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0;
+  // Writes a message to the on-screen log (shown if the user has toggled on the text display).
+  // Print() will also dump the message to stdout / log file, while PrintOnScreenOnly() not.
+  virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0;
+  virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0;
 
-    virtual void ShowFile(const char* filename) = 0;
+  virtual void ShowFile(const char* filename) = 0;
 
-    // --- key handling ---
+  // --- key handling ---
 
-    // Wait for a key and return it.  May return -1 after timeout.
-    virtual int WaitKey();
+  // Waits for a key and return it. May return -1 after timeout.
+  virtual int WaitKey();
 
-    virtual bool IsKeyPressed(int key);
-    virtual bool IsLongPress();
+  virtual bool IsKeyPressed(int key);
+  virtual bool IsLongPress();
 
-    // Returns true if you have the volume up/down and power trio typical
-    // of phones and tablets, false otherwise.
-    virtual bool HasThreeButtons();
+  // Returns true if you have the volume up/down and power trio typical of phones and tablets, false
+  // otherwise.
+  virtual bool HasThreeButtons();
 
-    // Erase any queued-up keys.
-    virtual void FlushKeys();
+  // Returns true if it has a power key.
+  virtual bool HasPowerKey() const;
 
-    // Called on each key press, even while operations are in progress.
-    // Return value indicates whether an immediate operation should be
-    // triggered (toggling the display, rebooting the device), or if
-    // the key should be enqueued for use by the main thread.
-    enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
-    virtual KeyAction CheckKey(int key, bool is_long_press);
+  // Returns true if it supports touch inputs.
+  virtual bool HasTouchScreen() const;
 
-    // Called when a key is held down long enough to have been a
-    // long-press (but before the key is released).  This means that
-    // if the key is eventually registered (released without any other
-    // keys being pressed in the meantime), CheckKey will be called with
-    // 'is_long_press' true.
-    virtual void KeyLongPress(int key);
+  // Erases any queued-up keys.
+  virtual void FlushKeys();
 
-    // Normally in recovery there's a key sequence that triggers
-    // immediate reboot of the device, regardless of what recovery is
-    // doing (with the default CheckKey implementation, it's pressing
-    // the power button 7 times in row).  Call this to enable or
-    // disable that feature.  It is enabled by default.
-    virtual void SetEnableReboot(bool enabled);
+  // Called on each key press, even while operations are in progress. Return value indicates whether
+  // an immediate operation should be triggered (toggling the display, rebooting the device), or if
+  // the key should be enqueued for use by the main thread.
+  enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
+  virtual KeyAction CheckKey(int key, bool is_long_press);
 
-    // --- menu display ---
+  // Called when a key is held down long enough to have been a long-press (but before the key is
+  // released). This means that if the key is eventually registered (released without any other keys
+  // being pressed in the meantime), CheckKey will be called with 'is_long_press' true.
+  virtual void KeyLongPress(int key);
 
-    // Display some header text followed by a menu of items, which appears
-    // at the top of the screen (in place of any scrolling ui_print()
-    // output, if necessary).
-    virtual void StartMenu(const char* const * headers, const char* const * items,
-                           int initial_selection) = 0;
+  // Normally in recovery there's a key sequence that triggers immediate reboot of the device,
+  // regardless of what recovery is doing (with the default CheckKey implementation, it's pressing
+  // the power button 7 times in row). Call this to enable or disable that feature. It is enabled by
+  // default.
+  virtual void SetEnableReboot(bool enabled);
 
-    // Set the menu highlight to the given index, wrapping if necessary.
-    // Returns the actual item selected.
-    virtual int SelectMenu(int sel) = 0;
+  // --- menu display ---
 
-    // End menu mode, resetting the text overlay so that ui_print()
-    // statements will be displayed.
-    virtual void EndMenu() = 0;
+  // Display some header text followed by a menu of items, which appears at the top of the screen
+  // (in place of any scrolling ui_print() output, if necessary).
+  virtual void StartMenu(const char* const* headers, const char* const* items,
+                         int initial_selection) = 0;
 
-  protected:
-    void EnqueueKey(int key_code);
+  // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item
+  // selected.
+  virtual int SelectMenu(int sel) = 0;
 
-    // The locale that's used to show the rendered texts.
-    std::string locale_;
-    bool rtl_locale_;
+  // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed.
+  virtual void EndMenu() = 0;
 
-    // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25%
-    // of the max_brightness). Because the absolute values may vary across devices. These two
-    // values can be configured via subclassing. Setting brightness_normal_ to 0 to disable
-    // screensaver.
-    unsigned int brightness_normal_;
-    unsigned int brightness_dimmed_;
+ protected:
+  void EnqueueKey(int key_code);
 
-  private:
-    // Key event input queue
-    pthread_mutex_t key_queue_mutex;
-    pthread_cond_t key_queue_cond;
-    int key_queue[256], key_queue_len;
-    char key_pressed[KEY_MAX + 1];     // under key_queue_mutex
-    int key_last_down;                 // under key_queue_mutex
-    bool key_long_press;               // under key_queue_mutex
-    int key_down_count;                // under key_queue_mutex
-    bool enable_reboot;                // under key_queue_mutex
-    int rel_sum;
+  // The locale that's used to show the rendered texts.
+  std::string locale_;
+  bool rtl_locale_;
 
-    int consecutive_power_keys;
-    int last_key;
+  // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of
+  // the max_brightness). Because the absolute values may vary across devices. These two values can
+  // be configured via subclassing. Setting brightness_normal_ to 0 to disable screensaver.
+  unsigned int brightness_normal_;
+  unsigned int brightness_dimmed_;
 
-    bool has_power_key;
-    bool has_up_key;
-    bool has_down_key;
+  // Whether we should listen for touch inputs (default: false).
+  bool touch_screen_allowed_;
 
-    struct key_timer_t {
-        RecoveryUI* ui;
-        int key_code;
-        int count;
-    };
+ private:
+  // The sensitivity when detecting a swipe.
+  const int kTouchLowThreshold;
+  const int kTouchHighThreshold;
 
-    pthread_t input_thread_;
+  // Key event input queue
+  pthread_mutex_t key_queue_mutex;
+  pthread_cond_t key_queue_cond;
+  int key_queue[256], key_queue_len;
+  char key_pressed[KEY_MAX + 1];  // under key_queue_mutex
+  int key_last_down;              // under key_queue_mutex
+  bool key_long_press;            // under key_queue_mutex
+  int key_down_count;             // under key_queue_mutex
+  bool enable_reboot;             // under key_queue_mutex
+  int rel_sum;
 
-    void OnKeyDetected(int key_code);
-    int OnInputEvent(int fd, uint32_t epevents);
-    void ProcessKey(int key_code, int updown);
+  int consecutive_power_keys;
+  int last_key;
 
-    bool IsUsbConnected();
+  bool has_power_key;
+  bool has_up_key;
+  bool has_down_key;
+  bool has_touch_screen;
 
-    static void* time_key_helper(void* cookie);
-    void time_key(int key_code, int count);
+  // Touch event related variables. See the comments in RecoveryUI::OnInputEvent().
+  int touch_slot_;
+  int touch_X_;
+  int touch_Y_;
+  int touch_start_X_;
+  int touch_start_Y_;
+  bool touch_finger_down_;
+  bool touch_swiping_;
+  bool is_bootreason_recovery_ui_;
 
-    void SetLocale(const std::string&);
+  struct key_timer_t {
+    RecoveryUI* ui;
+    int key_code;
+    int count;
+  };
 
-    enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF };
-    ScreensaverState screensaver_state_;
-    // The following two contain the absolute values computed from brightness_normal_ and
-    // brightness_dimmed_ respectively.
-    unsigned int brightness_normal_value_;
-    unsigned int brightness_dimmed_value_;
-    bool InitScreensaver();
+  pthread_t input_thread_;
+
+  void OnKeyDetected(int key_code);
+  void OnTouchDetected(int dx, int dy);
+  int OnInputEvent(int fd, uint32_t epevents);
+  void ProcessKey(int key_code, int updown);
+
+  bool IsUsbConnected();
+
+  static void* time_key_helper(void* cookie);
+  void time_key(int key_code, int count);
+
+  void SetLocale(const std::string&);
+
+  enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF };
+  ScreensaverState screensaver_state_;
+  // The following two contain the absolute values computed from brightness_normal_ and
+  // brightness_dimmed_ respectively.
+  unsigned int brightness_normal_value_;
+  unsigned int brightness_dimmed_value_;
+  bool InitScreensaver();
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index 48242a5..ba7b7ae 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -45,6 +45,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <future>
 #include <string>
 #include <vector>
 
@@ -123,11 +124,6 @@
     LOG(ERROR) << "Failed to find dm block device for " << partition;
     return false;
   }
-  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
-  if (fd.get() == -1) {
-    PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
-    return false;
-  }
 
   // For block range string, first integer 'count' equals 2 * total number of valid ranges,
   // followed by 'count' number comma separated integers. Every two integers reprensent a
@@ -141,74 +137,113 @@
     LOG(ERROR) << "Error in parsing range string.";
     return false;
   }
+  range_count /= 2;
 
-  size_t blk_count = 0;
-  for (size_t i = 1; i < ranges.size(); i += 2) {
-    unsigned int range_start, range_end;
-    bool parse_status = android::base::ParseUint(ranges[i], &range_start);
-    parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
-    if (!parse_status || range_start >= range_end) {
-      LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
-      return false;
-    }
+  std::vector<std::future<bool>> threads;
+  size_t thread_num = std::thread::hardware_concurrency() ?: 4;
+  thread_num = std::min(thread_num, range_count);
+  size_t group_range_count = (range_count + thread_num - 1) / thread_num;
 
-    static constexpr size_t BLOCKSIZE = 4096;
-    if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
-      PLOG(ERROR) << "lseek to " << range_start << " failed";
-      return false;
-    }
-
-    size_t remain = (range_end - range_start) * BLOCKSIZE;
-    while (remain > 0) {
-      size_t to_read = std::min(remain, 1024 * BLOCKSIZE);
-      std::vector<uint8_t> buf(to_read);
-      if (!android::base::ReadFully(fd.get(), buf.data(), to_read)) {
-        PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
+  for (size_t t = 0; t < thread_num; t++) {
+    auto thread_func = [t, group_range_count, &dm_block_device, &ranges, &partition]() {
+      size_t blk_count = 0;
+      static constexpr size_t kBlockSize = 4096;
+      std::vector<uint8_t> buf(1024 * kBlockSize);
+      android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+      if (fd.get() == -1) {
+        PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
         return false;
       }
-      remain -= to_read;
-    }
-    blk_count += (range_end - range_start);
+
+      for (size_t i = group_range_count * 2 * t + 1;
+           i < std::min(group_range_count * 2 * (t + 1) + 1, ranges.size()); i += 2) {
+        unsigned int range_start, range_end;
+        bool parse_status = android::base::ParseUint(ranges[i], &range_start);
+        parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
+        if (!parse_status || range_start >= range_end) {
+          LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
+          return false;
+        }
+
+        if (lseek64(fd.get(), static_cast<off64_t>(range_start) * kBlockSize, SEEK_SET) == -1) {
+          PLOG(ERROR) << "lseek to " << range_start << " failed";
+          return false;
+        }
+
+        size_t remain = (range_end - range_start) * kBlockSize;
+        while (remain > 0) {
+          size_t to_read = std::min(remain, 1024 * kBlockSize);
+          if (!android::base::ReadFully(fd.get(), buf.data(), to_read)) {
+            PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
+            return false;
+          }
+          remain -= to_read;
+        }
+        blk_count += (range_end - range_start);
+      }
+      LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
+      return true;
+    };
+
+    threads.emplace_back(std::async(std::launch::async, thread_func));
   }
 
-  LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
-  return true;
+  bool ret = true;
+  for (auto& t : threads) {
+    ret = t.get() && ret;
+  }
+  LOG(INFO) << "Finished reading blocks on " << dm_block_device << " with " << thread_num
+            << " threads.";
+  return ret;
 }
 
+// Returns true to indicate a passing verification (or the error should be ignored); Otherwise
+// returns false on fatal errors, where we should reject the current boot and trigger a fallback.
+// Note that update_verifier should be backward compatible to not reject care_map.txt from old
+// releases, which could otherwise fail to boot into the new release. For example, we've changed
+// the care_map format between N and O. An O update_verifier would fail to work with N
+// care_map.txt. This could be a result of sideloading an O OTA while the device having a pending N
+// update.
 bool verify_image(const std::string& care_map_name) {
-    android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY)));
-    // If the device is flashed before the current boot, it may not have care_map.txt
-    // in /data/ota_package. To allow the device to continue booting in this situation,
-    // we should print a warning and skip the block verification.
-    if (care_map_fd.get() == -1) {
-        PLOG(WARNING) << "Failed to open " << care_map_name;
-        return true;
-    }
-    // Care map file has four lines (two lines if vendor partition is not present):
-    // First line has the block partition name (system/vendor).
-    // Second line holds all ranges of blocks to verify.
-    // The next two lines have the same format but for vendor partition.
-    std::string file_content;
-    if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
-        LOG(ERROR) << "Error reading care map contents to string.";
-        return false;
-    }
-
-    std::vector<std::string> lines;
-    lines = android::base::Split(android::base::Trim(file_content), "\n");
-    if (lines.size() != 2 && lines.size() != 4) {
-        LOG(ERROR) << "Invalid lines in care_map: found " << lines.size()
-                   << " lines, expecting 2 or 4 lines.";
-        return false;
-    }
-
-    for (size_t i = 0; i < lines.size(); i += 2) {
-        if (!read_blocks(lines[i], lines[i+1])) {
-            return false;
-        }
-    }
-
+  android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY)));
+  // If the device is flashed before the current boot, it may not have care_map.txt
+  // in /data/ota_package. To allow the device to continue booting in this situation,
+  // we should print a warning and skip the block verification.
+  if (care_map_fd.get() == -1) {
+    PLOG(WARNING) << "Failed to open " << care_map_name;
     return true;
+  }
+  // Care map file has four lines (two lines if vendor partition is not present):
+  // First line has the block partition name (system/vendor).
+  // Second line holds all ranges of blocks to verify.
+  // The next two lines have the same format but for vendor partition.
+  std::string file_content;
+  if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
+    LOG(ERROR) << "Error reading care map contents to string.";
+    return false;
+  }
+
+  std::vector<std::string> lines;
+  lines = android::base::Split(android::base::Trim(file_content), "\n");
+  if (lines.size() != 2 && lines.size() != 4) {
+    LOG(ERROR) << "Invalid lines in care_map: found " << lines.size()
+               << " lines, expecting 2 or 4 lines.";
+    return false;
+  }
+
+  for (size_t i = 0; i < lines.size(); i += 2) {
+    // We're seeing an N care_map.txt. Skip the verification since it's not compatible with O
+    // update_verifier (the last few metadata blocks can't be read via device mapper).
+    if (android::base::StartsWith(lines[i], "/dev/block/")) {
+      LOG(WARNING) << "Found legacy care_map.txt; skipped.";
+      return true;
+    }
+    if (!read_blocks(lines[i], lines[i+1])) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 static int reboot_device() {
diff --git a/updater/Android.mk b/updater/Android.mk
index a113fe8..86dc48e 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -47,6 +47,7 @@
     libcrypto_utils \
     libcutils \
     libtune2fs \
+    libbrotli \
     $(tune2fs_static_libraries)
 
 # libupdater (static library)
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index df366b0..a0b9ad2 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -44,6 +44,7 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <applypatch/applypatch.h>
+#include <brotli/decode.h>
 #include <openssl/sha.h>
 #include <private/android_filesystem_config.h>
 #include <ziparchive/zip_archive.h>
@@ -149,7 +150,11 @@
 class RangeSinkWriter {
  public:
   RangeSinkWriter(int fd, const RangeSet& tgt)
-      : fd_(fd), tgt_(tgt), next_range_(0), current_range_left_(0), bytes_written_(0) {
+      : fd_(fd),
+        tgt_(tgt),
+        next_range_(0),
+        current_range_left_(0),
+        bytes_written_(0) {
     CHECK_NE(tgt.size(), static_cast<size_t>(0));
   };
 
@@ -157,6 +162,11 @@
     return next_range_ == tgt_.size() && current_range_left_ == 0;
   }
 
+  size_t AvailableSpace() const {
+    return tgt_.blocks() * BLOCKSIZE - bytes_written_;
+  }
+
+  // Return number of bytes written; and 0 indicates a writing failure.
   size_t Write(const uint8_t* data, size_t size) {
     if (Finished()) {
       LOG(ERROR) << "range sink write overrun; can't write " << size << " bytes";
@@ -166,23 +176,8 @@
     size_t written = 0;
     while (size > 0) {
       // Move to the next range as needed.
-      if (current_range_left_ == 0) {
-        if (next_range_ < tgt_.size()) {
-          const Range& range = tgt_[next_range_];
-          off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE;
-          current_range_left_ = (range.second - range.first) * BLOCKSIZE;
-          next_range_++;
-          if (!discard_blocks(fd_, offset, current_range_left_)) {
-            break;
-          }
-
-          if (!check_lseek(fd_, offset, SEEK_SET)) {
-            break;
-          }
-        } else {
-          // We can't write any more; return how many bytes have been written so far.
-          break;
-        }
+      if (!SeekToOutputRange()) {
+        break;
       }
 
       size_t write_now = size;
@@ -210,9 +205,35 @@
   }
 
  private:
-  // The input data.
+  // Set up the output cursor, move to next range if needed.
+  bool SeekToOutputRange() {
+    // We haven't finished the current range yet.
+    if (current_range_left_ != 0) {
+      return true;
+    }
+    // We can't write any more; let the write function return how many bytes have been written
+    // so far.
+    if (next_range_ >= tgt_.size()) {
+      return false;
+    }
+
+    const Range& range = tgt_[next_range_];
+    off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE;
+    current_range_left_ = (range.second - range.first) * BLOCKSIZE;
+    next_range_++;
+
+    if (!discard_blocks(fd_, offset, current_range_left_)) {
+      return false;
+    }
+    if (!check_lseek(fd_, offset, SEEK_SET)) {
+      return false;
+    }
+    return true;
+  }
+
+  // The output file descriptor.
   int fd_;
-  // The destination for the data.
+  // The destination ranges for the data.
   const RangeSet& tgt_;
   // The next range that we should write to.
   size_t next_range_;
@@ -243,8 +264,10 @@
 struct NewThreadInfo {
   ZipArchiveHandle za;
   ZipEntry entry;
+  bool brotli_compressed;
 
-  RangeSinkWriter* writer;
+  std::unique_ptr<RangeSinkWriter> writer;
+  BrotliDecoderState* brotli_decoder_state;
   bool receiver_available;
 
   pthread_mutex_t mu;
@@ -264,9 +287,73 @@
 
     // At this point nti->writer is set, and we own it. The main thread is waiting for it to
     // disappear from nti.
-    size_t written = nti->writer->Write(data, size);
-    data += written;
-    size -= written;
+    size_t write_now = std::min(size, nti->writer->AvailableSpace());
+    if (nti->writer->Write(data, write_now) != write_now) {
+      LOG(ERROR) << "Failed to write " << write_now << " bytes.";
+      return false;
+    }
+
+    data += write_now;
+    size -= write_now;
+
+    if (nti->writer->Finished()) {
+      // We have written all the bytes desired by this writer.
+
+      pthread_mutex_lock(&nti->mu);
+      nti->writer = nullptr;
+      pthread_cond_broadcast(&nti->cv);
+      pthread_mutex_unlock(&nti->mu);
+    }
+  }
+
+  return true;
+}
+
+static bool receive_brotli_new_data(const uint8_t* data, size_t size, void* cookie) {
+  NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
+
+  while (size > 0 || BrotliDecoderHasMoreOutput(nti->brotli_decoder_state)) {
+    // Wait for nti->writer to be non-null, indicating some of this data is wanted.
+    pthread_mutex_lock(&nti->mu);
+    while (nti->writer == nullptr) {
+      pthread_cond_wait(&nti->cv, &nti->mu);
+    }
+    pthread_mutex_unlock(&nti->mu);
+
+    // At this point nti->writer is set, and we own it. The main thread is waiting for it to
+    // disappear from nti.
+
+    size_t buffer_size = std::min<size_t>(32768, nti->writer->AvailableSpace());
+    if (buffer_size == 0) {
+      LOG(ERROR) << "No space left in output range";
+      return false;
+    }
+    uint8_t buffer[buffer_size];
+    size_t available_in = size;
+    size_t available_out = buffer_size;
+    uint8_t* next_out = buffer;
+
+    // The brotli decoder will update |data|, |available_in|, |next_out| and |available_out|.
+    BrotliDecoderResult result = BrotliDecoderDecompressStream(
+        nti->brotli_decoder_state, &available_in, &data, &available_out, &next_out, nullptr);
+
+    if (result == BROTLI_DECODER_RESULT_ERROR) {
+      LOG(ERROR) << "Decompression failed with "
+                 << BrotliDecoderErrorString(BrotliDecoderGetErrorCode(nti->brotli_decoder_state));
+      return false;
+    }
+
+    LOG(DEBUG) << "bytes to write: " << buffer_size - available_out << ", bytes consumed "
+               << size - available_in << ", decoder status " << result;
+
+    size_t write_now = buffer_size - available_out;
+    if (nti->writer->Write(buffer, write_now) != write_now) {
+      LOG(ERROR) << "Failed to write " << write_now << " bytes.";
+      return false;
+    }
+
+    // Update the remaining size. The input data ptr is already updated by brotli decoder function.
+    size = available_in;
 
     if (nti->writer->Finished()) {
       // We have written all the bytes desired by this writer.
@@ -283,8 +370,11 @@
 
 static void* unzip_new_data(void* cookie) {
   NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
-  ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
-
+  if (nti->brotli_compressed) {
+    ProcessZipEntryContents(nti->za, &nti->entry, receive_brotli_new_data, nti);
+  } else {
+    ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
+  }
   pthread_mutex_lock(&nti->mu);
   nti->receiver_available = false;
   if (nti->writer != nullptr) {
@@ -1142,9 +1232,8 @@
   if (params.canwrite) {
     LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data";
 
-    RangeSinkWriter writer(params.fd, tgt);
     pthread_mutex_lock(&params.nti.mu);
-    params.nti.writer = &writer;
+    params.nti.writer = std::make_unique<RangeSinkWriter>(params.fd, tgt);
     pthread_cond_broadcast(&params.nti.cv);
 
     while (params.nti.writer != nullptr) {
@@ -1384,6 +1473,11 @@
   if (params.canwrite) {
     params.nti.za = za;
     params.nti.entry = new_entry;
+    params.nti.brotli_compressed = android::base::EndsWith(new_data_fn->data, ".br");
+    if (params.nti.brotli_compressed) {
+      // Initialize brotli decoder state.
+      params.nti.brotli_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+    }
     params.nti.receiver_available = true;
 
     pthread_mutex_init(&params.nti.mu, nullptr);
@@ -1526,6 +1620,10 @@
   }
   // params.fd will be automatically closed because it's a unique_fd.
 
+  if (params.nti.brotli_decoder_state != nullptr) {
+    BrotliDecoderDestroyInstance(params.nti.brotli_decoder_state);
+  }
+
   // Only delete the stash if the update cannot be resumed, or it's a verification run and we
   // created the stash.
   if (params.isunresumable || (!params.canwrite && params.createdstash)) {
diff --git a/updater/install.cpp b/updater/install.cpp
index c9a3a07..bfe91e7 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -322,8 +322,7 @@
       return StringValue(location);
     }
 
-    const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e",   "-S",
-                                     "/file_contexts",         "-a",   mount_point.c_str(),
+    const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e",   "-a", mount_point.c_str(),
                                      location.c_str(),         nullptr };
     status = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
     if (status != 0) {
diff --git a/verifier.cpp b/verifier.cpp
index 2ef9c4c..18437fb 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -474,81 +474,80 @@
 // Otherwise returns false if the file failed to parse, or if it contains zero
 // keys. The contents in certs would be unspecified on failure.
 bool load_keys(const char* filename, std::vector<Certificate>& certs) {
-    std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose);
-    if (!f) {
-        PLOG(ERROR) << "error opening " << filename;
+  std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "re"), fclose);
+  if (!f) {
+    PLOG(ERROR) << "error opening " << filename;
+    return false;
+  }
+
+  while (true) {
+    certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+    Certificate& cert = certs.back();
+    uint32_t exponent = 0;
+
+    char start_char;
+    if (fscanf(f.get(), " %c", &start_char) != 1) return false;
+    if (start_char == '{') {
+      // a version 1 key has no version specifier.
+      cert.key_type = Certificate::KEY_TYPE_RSA;
+      exponent = 3;
+      cert.hash_len = SHA_DIGEST_LENGTH;
+    } else if (start_char == 'v') {
+      int version;
+      if (fscanf(f.get(), "%d {", &version) != 1) return false;
+      switch (version) {
+        case 2:
+          cert.key_type = Certificate::KEY_TYPE_RSA;
+          exponent = 65537;
+          cert.hash_len = SHA_DIGEST_LENGTH;
+          break;
+        case 3:
+          cert.key_type = Certificate::KEY_TYPE_RSA;
+          exponent = 3;
+          cert.hash_len = SHA256_DIGEST_LENGTH;
+          break;
+        case 4:
+          cert.key_type = Certificate::KEY_TYPE_RSA;
+          exponent = 65537;
+          cert.hash_len = SHA256_DIGEST_LENGTH;
+          break;
+        case 5:
+          cert.key_type = Certificate::KEY_TYPE_EC;
+          cert.hash_len = SHA256_DIGEST_LENGTH;
+          break;
+        default:
+          return false;
+      }
+    }
+
+    if (cert.key_type == Certificate::KEY_TYPE_RSA) {
+      cert.rsa = parse_rsa_key(f.get(), exponent);
+      if (!cert.rsa) {
         return false;
+      }
+
+      LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len;
+    } else if (cert.key_type == Certificate::KEY_TYPE_EC) {
+      cert.ec = parse_ec_key(f.get());
+      if (!cert.ec) {
+        return false;
+      }
+    } else {
+      LOG(ERROR) << "Unknown key type " << cert.key_type;
+      return false;
     }
 
-    while (true) {
-        certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
-        Certificate& cert = certs.back();
-        uint32_t exponent = 0;
-
-        char start_char;
-        if (fscanf(f.get(), " %c", &start_char) != 1) return false;
-        if (start_char == '{') {
-            // a version 1 key has no version specifier.
-            cert.key_type = Certificate::KEY_TYPE_RSA;
-            exponent = 3;
-            cert.hash_len = SHA_DIGEST_LENGTH;
-        } else if (start_char == 'v') {
-            int version;
-            if (fscanf(f.get(), "%d {", &version) != 1) return false;
-            switch (version) {
-                case 2:
-                    cert.key_type = Certificate::KEY_TYPE_RSA;
-                    exponent = 65537;
-                    cert.hash_len = SHA_DIGEST_LENGTH;
-                    break;
-                case 3:
-                    cert.key_type = Certificate::KEY_TYPE_RSA;
-                    exponent = 3;
-                    cert.hash_len = SHA256_DIGEST_LENGTH;
-                    break;
-                case 4:
-                    cert.key_type = Certificate::KEY_TYPE_RSA;
-                    exponent = 65537;
-                    cert.hash_len = SHA256_DIGEST_LENGTH;
-                    break;
-                case 5:
-                    cert.key_type = Certificate::KEY_TYPE_EC;
-                    cert.hash_len = SHA256_DIGEST_LENGTH;
-                    break;
-                default:
-                    return false;
-            }
-        }
-
-        if (cert.key_type == Certificate::KEY_TYPE_RSA) {
-            cert.rsa = parse_rsa_key(f.get(), exponent);
-            if (!cert.rsa) {
-              return false;
-            }
-
-            LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len;
-        } else if (cert.key_type == Certificate::KEY_TYPE_EC) {
-            cert.ec = parse_ec_key(f.get());
-            if (!cert.ec) {
-              return false;
-            }
-        } else {
-            LOG(ERROR) << "Unknown key type " << cert.key_type;
-            return false;
-        }
-
-        // if the line ends in a comma, this file has more keys.
-        int ch = fgetc(f.get());
-        if (ch == ',') {
-            // more keys to come.
-            continue;
-        } else if (ch == EOF) {
-            break;
-        } else {
-            LOG(ERROR) << "unexpected character between keys";
-            return false;
-        }
+    // if the line ends in a comma, this file has more keys.
+    int ch = fgetc(f.get());
+    if (ch == ',') {
+      // more keys to come.
+      continue;
+    } else if (ch == EOF) {
+      break;
+    } else {
+      LOG(ERROR) << "unexpected character between keys";
+      return false;
     }
-
-    return true;
+  }
+  return true;
 }
diff --git a/vr_ui.cpp b/vr_ui.cpp
index 8b8261e..1251672 100644
--- a/vr_ui.cpp
+++ b/vr_ui.cpp
@@ -27,9 +27,9 @@
   return true;
 }
 
-void VrRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const {
+int VrRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
   int mid_divide = gr_fb_width() / 2;
-  gr_text(gr_sys_font(), x + kStereoOffset, *y, line, bold);
-  gr_text(gr_sys_font(), x - kStereoOffset + mid_divide, *y, line, bold);
-  *y += char_height_ + 4;
+  gr_text(gr_sys_font(), x + kStereoOffset, y, line, bold);
+  gr_text(gr_sys_font(), x - kStereoOffset + mid_divide, y, line, bold);
+  return char_height_ + 4;
 }
diff --git a/vr_ui.h b/vr_ui.h
index 31ca4a6..d996c14 100644
--- a/vr_ui.h
+++ b/vr_ui.h
@@ -20,17 +20,17 @@
 #include "screen_ui.h"
 
 class VrRecoveryUI : public ScreenRecoveryUI {
-  public:
-    VrRecoveryUI();
+ public:
+  VrRecoveryUI();
 
-  protected:
-    // Pixel offsets to move drawing functions to visible range.
-    // Can vary per device depending on screen size and lens distortion.
-    const int kStereoOffset;
+ protected:
+  // Pixel offsets to move drawing functions to visible range.
+  // Can vary per device depending on screen size and lens distortion.
+  const int kStereoOffset;
 
-    bool InitTextParams() override;
+  bool InitTextParams() override;
 
-    void DrawTextLine(int x, int* y, const char* line, bool bold) const override;
+  int DrawTextLine(int x, int y, const char* line, bool bold) const override;
 };
 
 #endif  // RECOVERY_VR_UI_H
diff --git a/wear_device.cpp b/wear_device.cpp
new file mode 100644
index 0000000..3268130
--- /dev/null
+++ b/wear_device.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "device.h"
+#include "wear_ui.h"
+
+Device* make_device() {
+  return new Device(new WearRecoveryUI);
+}
+
diff --git a/wear_touch.cpp b/wear_touch.cpp
deleted file mode 100644
index e2ab44d..0000000
--- a/wear_touch.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <dirent.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-
-#include <android-base/logging.h>
-#include <linux/input.h>
-
-#include "wear_touch.h"
-
-#define DEVICE_PATH "/dev/input"
-
-WearSwipeDetector::WearSwipeDetector(int low, int high, OnSwipeCallback callback, void* cookie):
-    mLowThreshold(low),
-    mHighThreshold(high),
-    mCallback(callback),
-    mCookie(cookie),
-    mCurrentSlot(-1) {
-    pthread_create(&mThread, NULL, touch_thread, this);
-}
-
-WearSwipeDetector::~WearSwipeDetector() {
-}
-
-void WearSwipeDetector::detect(int dx, int dy) {
-    enum SwipeDirection direction;
-
-    if (abs(dy) < mLowThreshold && abs(dx) > mHighThreshold) {
-        direction = dx < 0 ? LEFT : RIGHT;
-    } else if (abs(dx) < mLowThreshold && abs(dy) > mHighThreshold) {
-        direction = dy < 0 ? UP : DOWN;
-    } else {
-        LOG(DEBUG) << "Ignore " << dx << " " << dy;
-        return;
-    }
-
-    LOG(DEBUG) << "Swipe direction=" << direction;
-    mCallback(mCookie, direction);
-}
-
-void WearSwipeDetector::process(struct input_event *event) {
-    if (mCurrentSlot < 0) {
-        mCallback(mCookie, UP);
-        mCurrentSlot = 0;
-    }
-
-    if (event->type == EV_ABS) {
-        if (event->code == ABS_MT_SLOT)
-            mCurrentSlot = event->value;
-
-        // Ignore other fingers
-        if (mCurrentSlot > 0) {
-            return;
-        }
-
-        switch (event->code) {
-        case ABS_MT_POSITION_X:
-            mX = event->value;
-            mFingerDown = true;
-            break;
-
-        case ABS_MT_POSITION_Y:
-            mY = event->value;
-            mFingerDown = true;
-            break;
-
-        case ABS_MT_TRACKING_ID:
-            if (event->value < 0)
-                mFingerDown = false;
-            break;
-        }
-    } else if (event->type == EV_SYN) {
-        if (event->code == SYN_REPORT) {
-            if (mFingerDown && !mSwiping) {
-                mStartX = mX;
-                mStartY = mY;
-                mSwiping = true;
-            } else if (!mFingerDown && mSwiping) {
-                mSwiping = false;
-                detect(mX - mStartX, mY - mStartY);
-            }
-        }
-    }
-}
-
-void WearSwipeDetector::run() {
-    int fd = findDevice(DEVICE_PATH);
-    if (fd < 0) {
-        LOG(ERROR) << "no input devices found";
-        return;
-    }
-
-    struct input_event event;
-    while (read(fd, &event, sizeof(event)) == sizeof(event)) {
-        process(&event);
-    }
-
-    close(fd);
-}
-
-void* WearSwipeDetector::touch_thread(void* cookie) {
-    (static_cast<WearSwipeDetector*>(cookie))->run();
-    return NULL;
-}
-
-#define test_bit(bit, array)    ((array)[(bit)/8] & (1<<((bit)%8)))
-
-int WearSwipeDetector::openDevice(const char *device) {
-    int fd = open(device, O_RDONLY);
-    if (fd < 0) {
-        PLOG(ERROR) << "could not open " << device;
-        return false;
-    }
-
-    char name[80];
-    name[sizeof(name) - 1] = '\0';
-    if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
-        PLOG(ERROR) << "could not get device name for " << device;
-        name[0] = '\0';
-    }
-
-    uint8_t bits[512];
-    memset(bits, 0, sizeof(bits));
-    int ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits);
-    if (ret > 0) {
-        if (test_bit(ABS_MT_POSITION_X, bits) && test_bit(ABS_MT_POSITION_Y, bits)) {
-            LOG(DEBUG) << "Found " << device << " " << name;
-            return fd;
-        }
-    }
-
-    close(fd);
-    return -1;
-}
-
-int WearSwipeDetector::findDevice(const char* path) {
-    DIR* dir = opendir(path);
-    if (dir == NULL) {
-        PLOG(ERROR) << "Could not open directory " << path;
-        return false;
-    }
-
-    struct dirent* entry;
-    int ret = -1;
-    while (ret < 0 && (entry = readdir(dir)) != NULL) {
-        if (entry->d_name[0] == '.') continue;
-
-        char device[PATH_MAX];
-        device[PATH_MAX-1] = '\0';
-        snprintf(device, PATH_MAX-1, "%s/%s", path, entry->d_name);
-
-        ret = openDevice(device);
-    }
-
-    closedir(dir);
-    return ret;
-}
-
diff --git a/wear_touch.h b/wear_touch.h
deleted file mode 100644
index 9a1d315..0000000
--- a/wear_touch.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __WEAR_TOUCH_H
-#define __WEAR_TOUCH_H
-
-#include <pthread.h>
-
-class WearSwipeDetector {
-
-public:
-    enum SwipeDirection { UP, DOWN, RIGHT, LEFT };
-    typedef void (*OnSwipeCallback)(void* cookie, enum SwipeDirection direction);
-
-    WearSwipeDetector(int low, int high, OnSwipeCallback cb, void* cookie);
-    ~WearSwipeDetector();
-
-private:
-    void run();
-    void process(struct input_event *event);
-    void detect(int dx, int dy);
-
-    pthread_t mThread;
-    static void* touch_thread(void* cookie);
-
-    int findDevice(const char* path);
-    int openDevice(const char* device);
-
-    int mLowThreshold;
-    int mHighThreshold;
-
-    OnSwipeCallback mCallback;
-    void *mCookie;
-
-    int mX;
-    int mY;
-    int mStartX;
-    int mStartY;
-
-    int mCurrentSlot;
-    bool mFingerDown;
-    bool mSwiping;
-};
-
-#endif // __WEAR_TOUCH_H
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 6c02865..624116c 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -45,167 +45,164 @@
 
 // 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;
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  return tv.tv_sec + tv.tv_usec / 1000000.0;
 }
 
-WearRecoveryUI::WearRecoveryUI() :
-    progress_bar_y(259),
-    outer_height(0),
-    outer_width(0),
-    menu_unusable_rows(0) {
-    intro_frames = 22;
-    loop_frames = 60;
-    animation_fps = 30;
+WearRecoveryUI::WearRecoveryUI()
+    : kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE),
+      kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) {
+  // TODO: kMenuUnusableRows should be computed based on the lines in draw_screen_locked().
 
-    for (size_t i = 0; i < 5; i++)
-        backgroundIcon[i] = NULL;
+  // TODO: The following three variables are likely not needed. The first two are detected
+  // automatically in ScreenRecoveryUI::LoadAnimation(), based on the actual files seen on device.
+  intro_frames = 22;
+  loop_frames = 60;
 
-    self = this;
+  touch_screen_allowed_ = true;
+
+  for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL;
+
+  self = this;
 }
 
-int WearRecoveryUI::GetProgressBaseline() {
-    return progress_bar_y;
+int WearRecoveryUI::GetProgressBaseline() const {
+  return kProgressBarBaseline;
 }
 
 // Draw background frame on the screen.  Does not flip pages.
 // Should only be called with updateMutex locked.
 // TODO merge drawing routines with screen_ui
-void WearRecoveryUI::draw_background_locked()
-{
-    pagesIdentical = false;
-    gr_color(0, 0, 0, 255);
-    gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+void WearRecoveryUI::draw_background_locked() {
+  pagesIdentical = false;
+  gr_color(0, 0, 0, 255);
+  gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
-    if (currentIcon != NONE) {
-        GRSurface* surface;
-        if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-            if (!intro_done) {
-                surface = introFrames[current_frame];
-            } else {
-                surface = loopFrames[current_frame];
-            }
-        }
-        else {
-            surface = backgroundIcon[currentIcon];
-        }
-
-        int width = gr_get_width(surface);
-        int height = gr_get_height(surface);
-
-        int x = (gr_fb_width() - width) / 2;
-        int y = (gr_fb_height() - height) / 2;
-
-        gr_blit(surface, 0, 0, width, height, x, y);
+  if (currentIcon != NONE) {
+    GRSurface* surface;
+    if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
+      if (!intro_done) {
+        surface = introFrames[current_frame];
+      } else {
+        surface = loopFrames[current_frame];
+      }
+    } else {
+      surface = backgroundIcon[currentIcon];
     }
+
+    int width = gr_get_width(surface);
+    int height = gr_get_height(surface);
+
+    int x = (gr_fb_width() - width) / 2;
+    int y = (gr_fb_height() - height) / 2;
+
+    gr_blit(surface, 0, 0, width, height, x, y);
+  }
 }
 
-static const char* HEADERS[] = {
-    "Swipe up/down to move.",
-    "Swipe left/right to select.",
-    "",
-    NULL
+static const char* SWIPE_HELP[] = {
+  "Swipe up/down to move.",
+  "Swipe left/right to select.",
+  "",
+  NULL
 };
 
 // TODO merge drawing routines with screen_ui
-void WearRecoveryUI::draw_screen_locked()
-{
-    char cur_selection_str[50];
+void WearRecoveryUI::draw_screen_locked() {
+  char cur_selection_str[50];
 
-    draw_background_locked();
-    if (!show_text) {
-        draw_foreground_locked();
-    } else {
-        SetColor(TEXT_FILL);
-        gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+  draw_background_locked();
+  if (!show_text) {
+    draw_foreground_locked();
+  } else {
+    SetColor(TEXT_FILL);
+    gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
-        int y = outer_height;
-        int x = outer_width;
-        if (show_menu) {
-            std::string recovery_fingerprint =
-                    android::base::GetProperty("ro.bootimage.build.fingerprint", "");
-            SetColor(HEADER);
-            DrawTextLine(x + 4, &y, "Android Recovery", true);
-            for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) {
-                DrawTextLine(x +4, &y, chunk.c_str(), false);
-            }
+    int y = kMarginHeight;
+    int x = kMarginWidth;
+    if (show_menu) {
+      std::string recovery_fingerprint =
+          android::base::GetProperty("ro.bootimage.build.fingerprint", "");
+      SetColor(HEADER);
+      y += DrawTextLine(x + 4, y, "Android Recovery", true);
+      for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) {
+        y += DrawTextLine(x + 4, y, chunk.c_str(), false);
+      }
 
-            // This is actually the help strings.
-            DrawTextLines(x + 4, &y, HEADERS);
-            SetColor(HEADER);
-            DrawTextLines(x + 4, &y, menu_headers_);
+      // This is actually the help strings.
+      y += DrawTextLines(x + 4, y, SWIPE_HELP);
+      SetColor(HEADER);
+      y += DrawTextLines(x + 4, y, menu_headers_);
 
-            // Show the current menu item number in relation to total number if
-            // items don't fit on the screen.
-            if (menu_items > menu_end - menu_start) {
-                sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items);
-                gr_text(gr_sys_font(), x+4, y, cur_selection_str, 1);
-                y += char_height_+4;
-            }
+      // Show the current menu item number in relation to total number if
+      // items don't fit on the screen.
+      if (menu_items > menu_end - menu_start) {
+        sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items);
+        gr_text(gr_sys_font(), x + 4, y, cur_selection_str, 1);
+        y += char_height_ + 4;
+      }
 
-            // Menu begins here
-            SetColor(MENU);
+      // Menu begins here
+      SetColor(MENU);
 
-            for (int i = menu_start; i < menu_end; ++i) {
-
-                if (i == menu_sel) {
-                    // draw the highlight bar
-                    SetColor(MENU_SEL_BG);
-                    gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2);
-                    // white text of selected item
-                    SetColor(MENU_SEL_FG);
-                    if (menu_[i][0]) {
-                        gr_text(gr_sys_font(), x + 4, y, menu_[i], 1);
-                    }
-                    SetColor(MENU);
-                } else if (menu_[i][0]) {
-                    gr_text(gr_sys_font(), x + 4, y, menu_[i], 0);
-                }
-                y += char_height_+4;
-            }
-            SetColor(MENU);
-            y += 4;
-            gr_fill(0, y, gr_fb_width(), y+2);
-            y += 4;
+      for (int i = menu_start; i < menu_end; ++i) {
+        if (i == menu_sel) {
+          // draw the highlight bar
+          SetColor(MENU_SEL_BG);
+          gr_fill(x, y - 2, gr_fb_width() - x, y + char_height_ + 2);
+          // white text of selected item
+          SetColor(MENU_SEL_FG);
+          if (menu_[i][0]) {
+            gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 1);
+          }
+          SetColor(MENU);
+        } else if (menu_[i][0]) {
+          gr_text(gr_sys_font(), x + 4, y, menu_[i].c_str(), 0);
         }
-
-        SetColor(LOG);
-
-        // display from the bottom up, until we hit the top of the
-        // screen, the bottom of the menu, or we've displayed the
-        // entire text buffer.
-        int ty;
-        int row = (text_top_ + text_rows_ - 1) % text_rows_;
-        size_t count = 0;
-        for (int ty = gr_fb_height() - char_height_ - outer_height;
-             ty > y + 2 && count < text_rows_;
-             ty -= char_height_, ++count) {
-            gr_text(gr_sys_font(), x+4, ty, text_[row], 0);
-            --row;
-            if (row < 0) row = text_rows_ - 1;
-        }
+        y += char_height_ + 4;
+      }
+      SetColor(MENU);
+      y += 4;
+      gr_fill(0, y, gr_fb_width(), y + 2);
+      y += 4;
     }
+
+    SetColor(LOG);
+
+    // display from the bottom up, until we hit the top of the
+    // screen, the bottom of the menu, or we've displayed the
+    // entire text buffer.
+    int ty;
+    int row = (text_top_ + text_rows_ - 1) % text_rows_;
+    size_t count = 0;
+    for (int ty = gr_fb_height() - char_height_ - kMarginHeight; ty > y + 2 && count < text_rows_;
+         ty -= char_height_, ++count) {
+      gr_text(gr_sys_font(), x + 4, ty, text_[row], 0);
+      --row;
+      if (row < 0) row = text_rows_ - 1;
+    }
+  }
 }
 
 // TODO merge drawing routines with screen_ui
 void WearRecoveryUI::update_progress_locked() {
-    draw_screen_locked();
-    gr_flip();
+  draw_screen_locked();
+  gr_flip();
 }
 
 bool WearRecoveryUI::InitTextParams() {
-    if (!ScreenRecoveryUI::InitTextParams()) {
-        return false;
-    }
+  if (!ScreenRecoveryUI::InitTextParams()) {
+    return false;
+  }
 
-    text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_;
+  text_cols_ = (gr_fb_width() - (kMarginWidth * 2)) / char_width_;
 
-    if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
-    if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
+  if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
+  if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
 
-    visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
-    return true;
+  visible_text_rows = (gr_fb_height() - (kMarginHeight * 2)) / char_height_;
+  return true;
 }
 
 bool WearRecoveryUI::Init(const std::string& locale) {
@@ -222,7 +219,8 @@
   return true;
 }
 
-void WearRecoveryUI::SetStage(int current, int max) {}
+void WearRecoveryUI::SetStage(int current, int max) {
+}
 
 void WearRecoveryUI::Print(const char* fmt, ...) {
   char buf[256];
@@ -252,165 +250,152 @@
   pthread_mutex_unlock(&updateMutex);
 }
 
-void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
+void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
                                int initial_selection) {
-    pthread_mutex_lock(&updateMutex);
-    if (text_rows_ > 0 && text_cols_ > 0) {
-        menu_headers_ = headers;
-        size_t i = 0;
-        // "i < text_rows_" is removed from the loop termination condition,
-        // which is different from the one in ScreenRecoveryUI::StartMenu().
-        // Because WearRecoveryUI supports scrollable menu, it's fine to have
-        // more entries than text_rows_. The menu may be truncated otherwise.
-        // Bug: 23752519
-        for (; items[i] != nullptr; i++) {
-            strncpy(menu_[i], items[i], text_cols_ - 1);
-            menu_[i][text_cols_ - 1] = '\0';
-        }
-        menu_items = i;
-        show_menu = true;
-        menu_sel = initial_selection;
-        menu_start = 0;
-        menu_end = visible_text_rows - 1 - menu_unusable_rows;
-        if (menu_items <= menu_end)
-          menu_end = menu_items;
-        update_screen_locked();
+  pthread_mutex_lock(&updateMutex);
+  if (text_rows_ > 0 && text_cols_ > 0) {
+    menu_headers_ = headers;
+    menu_.clear();
+    // "i < text_rows_" is removed from the loop termination condition,
+    // which is different from the one in ScreenRecoveryUI::StartMenu().
+    // Because WearRecoveryUI supports scrollable menu, it's fine to have
+    // more entries than text_rows_. The menu may be truncated otherwise.
+    // Bug: 23752519
+    for (size_t i = 0; items[i] != nullptr; i++) {
+      menu_.emplace_back(std::string(items[i], strnlen(items[i], text_cols_ - 1)));
     }
-    pthread_mutex_unlock(&updateMutex);
+    menu_items = static_cast<int>(menu_.size());
+    show_menu = true;
+    menu_sel = initial_selection;
+    menu_start = 0;
+    menu_end = visible_text_rows - 1 - kMenuUnusableRows;
+    if (menu_items <= menu_end) menu_end = menu_items;
+    update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 int WearRecoveryUI::SelectMenu(int sel) {
-    int old_sel;
-    pthread_mutex_lock(&updateMutex);
-    if (show_menu) {
-        old_sel = menu_sel;
-        menu_sel = sel;
-        if (menu_sel < 0) menu_sel = 0;
-        if (menu_sel >= menu_items) menu_sel = menu_items-1;
-        if (menu_sel < menu_start) {
-          menu_start--;
-          menu_end--;
-        } else if (menu_sel >= menu_end && menu_sel < menu_items) {
-          menu_end++;
-          menu_start++;
-        }
-        sel = menu_sel;
-        if (menu_sel != old_sel) update_screen_locked();
+  int old_sel;
+  pthread_mutex_lock(&updateMutex);
+  if (show_menu) {
+    old_sel = menu_sel;
+    menu_sel = sel;
+    if (menu_sel < 0) menu_sel = 0;
+    if (menu_sel >= menu_items) menu_sel = menu_items - 1;
+    if (menu_sel < menu_start) {
+      menu_start--;
+      menu_end--;
+    } else if (menu_sel >= menu_end && menu_sel < menu_items) {
+      menu_end++;
+      menu_start++;
     }
-    pthread_mutex_unlock(&updateMutex);
-    return sel;
+    sel = menu_sel;
+    if (menu_sel != old_sel) update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
+  return sel;
 }
 
 void WearRecoveryUI::ShowFile(FILE* fp) {
-    std::vector<off_t> offsets;
-    offsets.push_back(ftello(fp));
-    ClearText();
+  std::vector<off_t> offsets;
+  offsets.push_back(ftello(fp));
+  ClearText();
 
-    struct stat sb;
-    fstat(fileno(fp), &sb);
+  struct stat sb;
+  fstat(fileno(fp), &sb);
 
-    bool show_prompt = false;
-    while (true) {
-        if (show_prompt) {
-            Print("--(%d%% of %d bytes)--",
-                  static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
-                  static_cast<int>(sb.st_size));
-            Redraw();
-            while (show_prompt) {
-                show_prompt = false;
-                int key = WaitKey();
-                if (key == KEY_POWER || key == KEY_ENTER) {
-                    return;
-                } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
-                    if (offsets.size() <= 1) {
-                        show_prompt = true;
-                    } else {
-                        offsets.pop_back();
-                        fseek(fp, offsets.back(), SEEK_SET);
-                    }
-                } else {
-                    if (feof(fp)) {
-                        return;
-                    }
-                    offsets.push_back(ftello(fp));
-                }
-            }
-            ClearText();
-        }
-
-        int ch = getc(fp);
-        if (ch == EOF) {
-            text_row_ = text_top_ = text_rows_ - 2;
+  bool show_prompt = false;
+  while (true) {
+    if (show_prompt) {
+      Print("--(%d%% of %d bytes)--",
+            static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
+            static_cast<int>(sb.st_size));
+      Redraw();
+      while (show_prompt) {
+        show_prompt = false;
+        int key = WaitKey();
+        if (key == KEY_POWER || key == KEY_ENTER) {
+          return;
+        } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+          if (offsets.size() <= 1) {
             show_prompt = true;
+          } else {
+            offsets.pop_back();
+            fseek(fp, offsets.back(), SEEK_SET);
+          }
         } else {
-            PutChar(ch);
-            if (text_col_ == 0 && text_row_ >= text_rows_ - 2) {
-                text_top_ = text_row_;
-                show_prompt = true;
-            }
+          if (feof(fp)) {
+            return;
+          }
+          offsets.push_back(ftello(fp));
         }
+      }
+      ClearText();
     }
+
+    int ch = getc(fp);
+    if (ch == EOF) {
+      text_row_ = text_top_ = text_rows_ - 2;
+      show_prompt = true;
+    } else {
+      PutChar(ch);
+      if (text_col_ == 0 && text_row_ >= text_rows_ - 2) {
+        text_top_ = text_row_;
+        show_prompt = true;
+      }
+    }
+  }
 }
 
 void WearRecoveryUI::PutChar(char ch) {
-    pthread_mutex_lock(&updateMutex);
-    if (ch != '\n') text_[text_row_][text_col_++] = ch;
-    if (ch == '\n' || text_col_ >= text_cols_) {
-        text_col_ = 0;
-        ++text_row_;
-    }
-    pthread_mutex_unlock(&updateMutex);
+  pthread_mutex_lock(&updateMutex);
+  if (ch != '\n') text_[text_row_][text_col_++] = ch;
+  if (ch == '\n' || text_col_ >= text_cols_) {
+    text_col_ = 0;
+    ++text_row_;
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void WearRecoveryUI::ShowFile(const char* filename) {
-    FILE* fp = fopen_path(filename, "re");
-    if (fp == nullptr) {
-        Print("  Unable to open %s: %s\n", filename, strerror(errno));
-        return;
-    }
-    ShowFile(fp);
-    fclose(fp);
-}
-
-void WearRecoveryUI::ClearText() {
-    pthread_mutex_lock(&updateMutex);
-    text_col_ = 0;
-    text_row_ = 0;
-    text_top_ = 1;
-    for (size_t i = 0; i < text_rows_; ++i) {
-        memset(text_[i], 0, text_cols_ + 1);
-    }
-    pthread_mutex_unlock(&updateMutex);
+  FILE* fp = fopen_path(filename, "re");
+  if (fp == nullptr) {
+    Print("  Unable to open %s: %s\n", filename, strerror(errno));
+    return;
+  }
+  ShowFile(fp);
+  fclose(fp);
 }
 
 void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
-    va_list ap;
-    va_start(ap, fmt);
-    PrintV(fmt, false, ap);
-    va_end(ap);
+  va_list ap;
+  va_start(ap, fmt);
+  PrintV(fmt, false, ap);
+  va_end(ap);
 }
 
 void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
-    std::string str;
-    android::base::StringAppendV(&str, fmt, ap);
+  std::string str;
+  android::base::StringAppendV(&str, fmt, ap);
 
-    if (copy_to_stdout) {
-        fputs(str.c_str(), stdout);
-    }
+  if (copy_to_stdout) {
+    fputs(str.c_str(), stdout);
+  }
 
-    pthread_mutex_lock(&updateMutex);
-    if (text_rows_ > 0 && text_cols_ > 0) {
-        for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
-            if (*ptr == '\n' || text_col_ >= text_cols_) {
-                text_[text_row_][text_col_] = '\0';
-                text_col_ = 0;
-                text_row_ = (text_row_ + 1) % text_rows_;
-                if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
-            }
-            if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
-        }
+  pthread_mutex_lock(&updateMutex);
+  if (text_rows_ > 0 && text_cols_ > 0) {
+    for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
+      if (*ptr == '\n' || text_col_ >= text_cols_) {
         text_[text_row_][text_col_] = '\0';
-        update_screen_locked();
+        text_col_ = 0;
+        text_row_ = (text_row_ + 1) % text_rows_;
+        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
+      }
+      if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
     }
-    pthread_mutex_unlock(&updateMutex);
+    text_[text_row_][text_col_] = '\0';
+    update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
diff --git a/wear_ui.h b/wear_ui.h
index 4cd852f..3bd90b6 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -22,64 +22,58 @@
 #include <string>
 
 class WearRecoveryUI : public ScreenRecoveryUI {
-  public:
-    WearRecoveryUI();
+ public:
+  WearRecoveryUI();
 
-    bool Init(const std::string& locale) override;
+  bool Init(const std::string& locale) override;
 
-    void SetStage(int current, int max) override;
+  void SetStage(int current, int max) override;
 
-    // printing messages
-    void Print(const char* fmt, ...) override;
-    void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3);
-    void ShowFile(const char* filename) override;
-    void ShowFile(FILE* fp) override;
+  // printing messages
+  void Print(const char* fmt, ...) override;
+  void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3);
+  void ShowFile(const char* filename) override;
+  void ShowFile(FILE* fp) override;
 
-    // menu display
-    void StartMenu(const char* const * headers, const char* const * items,
-                   int initial_selection) override;
-    int SelectMenu(int sel) override;
+  // menu display
+  void StartMenu(const char* const* headers, const char* const* items,
+                 int initial_selection) override;
+  int SelectMenu(int sel) override;
 
-  protected:
-    // progress bar vertical position, it's centered horizontally
-    int progress_bar_y;
+ protected:
+  // progress bar vertical position, it's centered horizontally
+  const int kProgressBarBaseline;
 
-    // outer of window
-    int outer_height, outer_width;
+  // Unusable rows when displaying the recovery menu, including the lines for headers (Android
+  // Recovery, build id and etc) and the bottom lines that may otherwise go out of the screen.
+  const int kMenuUnusableRows;
 
-    // Unusable rows when displaying the recovery menu, including the lines
-    // for headers (Android Recovery, build id and etc) and the bottom lines
-    // that may otherwise go out of the screen.
-    int menu_unusable_rows;
+  int GetProgressBaseline() const override;
 
-    int GetProgressBaseline() override;
+  bool InitTextParams() override;
 
-    bool InitTextParams() override;
+  void update_progress_locked() override;
 
-    void update_progress_locked() override;
+  void PrintV(const char*, bool, va_list) override;
 
-    void PrintV(const char*, bool, va_list) override;
+ private:
+  GRSurface* backgroundIcon[5];
 
-  private:
-    GRSurface* backgroundIcon[5];
+  static const int kMaxCols = 96;
+  static const int kMaxRows = 96;
 
-    static const int kMaxCols = 96;
-    static const int kMaxRows = 96;
+  // Number of text rows seen on screen
+  int visible_text_rows;
 
-    // Number of text rows seen on screen
-    int visible_text_rows;
+  const char* const* menu_headers_;
+  int menu_start, menu_end;
 
-    const char* const* menu_headers_;
-    int menu_start, menu_end;
+  pthread_t progress_t;
 
-    pthread_t progress_t;
+  void draw_background_locked() override;
+  void draw_screen_locked() override;
 
-    void draw_background_locked() override;
-    void draw_screen_locked() override;
-    void draw_progress_locked();
-
-    void PutChar(char);
-    void ClearText();
+  void PutChar(char);
 };
 
 #endif  // RECOVERY_WEAR_UI_H