Merge "Move libboot_control to boot_control 1.1"
diff --git a/Android.bp b/Android.bp
index a8e032e..4032bcc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -90,7 +90,6 @@
     ],
 
     srcs: [
-        "fsck_unshare_blocks.cpp",
         "recovery.cpp",
     ],
 
@@ -157,7 +156,6 @@
     shared_libs: [
         "libbase",
         "liblog",
-        "libmetricslogger",
     ],
 
     static_libs: [
diff --git a/OWNERS b/OWNERS
index fe1c33d..79dd9f7 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
+elsk@google.com
 enh@google.com
 nhdo@google.com
 xunchang@google.com
diff --git a/edify/Android.bp b/edify/Android.bp
index 73048d2..0ab53d6 100644
--- a/edify/Android.bp
+++ b/edify/Android.bp
@@ -17,6 +17,7 @@
 
     host_supported: true,
     vendor_available: true,
+    recovery_available: true,
 
     srcs: [
         "expr.cpp",
diff --git a/edify/parser.yy b/edify/parser.yy
index 3a63c37..37bcdd0 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -72,7 +72,7 @@
 
 %parse-param {std::unique_ptr<Expr>* root}
 %parse-param {int* error_count}
-%error-verbose
+%define parse.error verbose
 
 /* declarations in increasing order of precedence */
 %left ';'
diff --git a/etc/init.rc b/etc/init.rc
index 395e627..3ec45db 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -4,8 +4,8 @@
     # Set the security context of /postinstall if present.
     restorecon /postinstall
 
-    # Generate ld.config.txt for recovery processes
-    exec u:r:linkerconfig:s0 -- /system/bin/linkerconfig --target /linkerconfig/ld.config.txt
+    # Copy prebuilt ld.config.txt into linkerconfig directory
+    copy /system/etc/ld.config.txt /linkerconfig/ld.config.txt
     chmod 444 /linkerconfig/ld.config.txt
 
     start ueventd
diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp
deleted file mode 100644
index e0b2d96..0000000
--- a/fsck_unshare_blocks.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2018 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 "fsck_unshare_blocks.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <spawn.h>
-#include <string.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/unique_fd.h>
-#include <fs_mgr/roots.h>
-
-#include "recovery_utils/roots.h"
-
-static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static";
-static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin";
-
-static bool copy_file(const char* source, const char* dest) {
-  android::base::unique_fd source_fd(open(source, O_RDONLY));
-  if (source_fd < 0) {
-    PLOG(ERROR) << "open %s failed" << source;
-    return false;
-  }
-
-  android::base::unique_fd dest_fd(open(dest, O_CREAT | O_WRONLY, S_IRWXU));
-  if (dest_fd < 0) {
-    PLOG(ERROR) << "open %s failed" << dest;
-    return false;
-  }
-
-  for (;;) {
-    char buf[4096];
-    ssize_t rv = read(source_fd, buf, sizeof(buf));
-    if (rv < 0) {
-      PLOG(ERROR) << "read failed";
-      return false;
-    }
-    if (rv == 0) {
-      break;
-    }
-    if (write(dest_fd, buf, rv) != rv) {
-      PLOG(ERROR) << "write failed";
-      return false;
-    }
-  }
-  return true;
-}
-
-static bool run_e2fsck(const std::string& partition) {
-  Volume* volume = volume_for_mount_point(partition);
-  if (!volume) {
-    LOG(INFO) << "No fstab entry for " << partition << ", skipping.";
-    return true;
-  }
-
-  LOG(INFO) << "Running e2fsck on device " << volume->blk_device;
-
-  std::vector<std::string> args = { TMP_E2FSCK_BIN, "-p", "-E", "unshare_blocks",
-                                    volume->blk_device };
-  std::vector<char*> argv(args.size());
-  std::transform(args.cbegin(), args.cend(), argv.begin(),
-                 [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
-  argv.push_back(nullptr);
-
-  pid_t child;
-  char* env[] = { nullptr };
-  if (posix_spawn(&child, argv[0], nullptr, nullptr, argv.data(), env)) {
-    PLOG(ERROR) << "posix_spawn failed";
-    return false;
-  }
-
-  int status = 0;
-  int ret = TEMP_FAILURE_RETRY(waitpid(child, &status, 0));
-  if (ret < 0) {
-    PLOG(ERROR) << "waitpid failed";
-    return false;
-  }
-  if (!WIFEXITED(status)) {
-    LOG(ERROR) << "e2fsck exited abnormally: " << status;
-    return false;
-  }
-  int return_code = WEXITSTATUS(status);
-  if (return_code >= 8) {
-    LOG(ERROR) << "e2fsck could not unshare blocks: " << return_code;
-    return false;
-  }
-
-  LOG(INFO) << "Successfully unshared blocks on " << partition;
-  return true;
-}
-
-bool do_fsck_unshare_blocks() {
-  // List of partitions we will try to e2fsck -E unshare_blocks.
-  std::vector<std::string> partitions = { "/odm", "/oem", "/product", "/vendor" };
-
-  // Temporarily mount system so we can copy e2fsck_static.
-  auto system_root = android::fs_mgr::GetSystemRoot();
-  bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1;
-  partitions.push_back(system_root);
-
-  if (!mounted) {
-    LOG(ERROR) << "Failed to mount system image.";
-    return false;
-  }
-  if (!copy_file(SYSTEM_E2FSCK_BIN, TMP_E2FSCK_BIN)) {
-    LOG(ERROR) << "Could not copy e2fsck to /tmp.";
-    return false;
-  }
-  if (umount("/mnt/system") < 0) {
-    PLOG(ERROR) << "umount failed";
-    return false;
-  }
-
-  bool ok = true;
-  for (const auto& partition : partitions) {
-    ok &= run_e2fsck(partition);
-  }
-
-  if (ok) {
-    LOG(INFO) << "Finished running e2fsck.";
-  } else {
-    LOG(ERROR) << "Finished running e2fsck, but not all partitions succceeded.";
-  }
-  return ok;
-}
diff --git a/fsck_unshare_blocks.h b/fsck_unshare_blocks.h
deleted file mode 100644
index 9de8ef9..0000000
--- a/fsck_unshare_blocks.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2018 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 _FILESYSTEM_CMDS_H
-#define _FILESYSTEM_CMDS_H
-
-bool do_fsck_unshare_blocks();
-
-#endif  // _FILESYSTEM_CMDS_H
diff --git a/install/Android.bp b/install/Android.bp
index be08506..bed3bc5 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -42,7 +42,6 @@
         "libsnapshot_nobinder",
 
         // external dependencies
-        "libvintf_recovery",
         "libvintf",
     ],
 }
diff --git a/install/include/install/install.h b/install/include/install/install.h
index b4b3a91..87d43ab 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -59,10 +59,6 @@
 // result to |metadata|. Return true if succeed, otherwise return false.
 bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
 
-// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
-// entry doesn't exist.
-bool verify_package_compatibility(ZipArchiveHandle package_zip);
-
 // Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type,
 // pre-device and serial number (if presents). A/B OTA specific checks: pre-build version,
 // fingerprint, timestamp.
diff --git a/install/include/install/snapshot_utils.h b/install/include/install/snapshot_utils.h
index 00c3ef7..f4b978d 100644
--- a/install/include/install/snapshot_utils.h
+++ b/install/include/install/snapshot_utils.h
@@ -19,3 +19,12 @@
 #include "recovery_ui/device.h"
 
 bool FinishPendingSnapshotMerges(Device* device);
+
+/*
+ * This function tries to create the snapshotted devices in the case a Virtual
+ * A/B device is updating.
+ * The function returns false in case of critical failure that would prevent
+ * the further mountings of devices, or true in case of success, if either the
+ * devices were created or there was no need to.
+ */
+bool CreateSnapshotPartitions();
diff --git a/install/install.cpp b/install/install.cpp
index 9166f9c..4bb0903 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -44,7 +44,6 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <vintf/VintfObjectRecovery.h>
 
 #include "install/package.h"
 #include "install/verifier.h"
@@ -505,73 +504,6 @@
   return INSTALL_SUCCESS;
 }
 
-// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
-// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA
-// package.
-bool verify_package_compatibility(ZipArchiveHandle package_zip) {
-  LOG(INFO) << "Verifying package compatibility...";
-
-  static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
-  ZipEntry compatibility_entry;
-  if (FindEntry(package_zip, COMPATIBILITY_ZIP_ENTRY, &compatibility_entry) != 0) {
-    LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
-    return true;
-  }
-
-  std::string zip_content(compatibility_entry.uncompressed_length, '\0');
-  int32_t ret;
-  if ((ret = ExtractToMemory(package_zip, &compatibility_entry,
-                             reinterpret_cast<uint8_t*>(&zip_content[0]),
-                             compatibility_entry.uncompressed_length)) != 0) {
-    LOG(ERROR) << "Failed to read " << COMPATIBILITY_ZIP_ENTRY << ": " << ErrorCodeString(ret);
-    return false;
-  }
-
-  ZipArchiveHandle zip_handle;
-  ret = OpenArchiveFromMemory(static_cast<void*>(const_cast<char*>(zip_content.data())),
-                              zip_content.size(), COMPATIBILITY_ZIP_ENTRY, &zip_handle);
-  if (ret != 0) {
-    LOG(ERROR) << "Failed to OpenArchiveFromMemory: " << ErrorCodeString(ret);
-    return false;
-  }
-
-  // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
-  void* cookie;
-  ret = StartIteration(zip_handle, &cookie);
-  if (ret != 0) {
-    LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
-    CloseArchive(zip_handle);
-    return false;
-  }
-  std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
-
-  std::vector<std::string> compatibility_info;
-  ZipEntry info_entry;
-  std::string_view info_name;
-  while (Next(cookie, &info_entry, &info_name) == 0) {
-    std::string content(info_entry.uncompressed_length, '\0');
-    int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]),
-                                  info_entry.uncompressed_length);
-    if (ret != 0) {
-      LOG(ERROR) << "Failed to read " << info_name << ": " << ErrorCodeString(ret);
-      CloseArchive(zip_handle);
-      return false;
-    }
-    compatibility_info.emplace_back(std::move(content));
-  }
-  CloseArchive(zip_handle);
-
-  // VintfObjectRecovery::CheckCompatibility returns zero on success.
-  std::string err;
-  int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err);
-  if (result == 0) {
-    return true;
-  }
-
-  LOG(ERROR) << "Failed to verify package compatibility (result " << result << "): " << err;
-  return false;
-}
-
 static InstallResult VerifyAndInstallPackage(Package* package, bool* wipe_cache,
                                              std::vector<std::string>* log_buffer, int retry_count,
                                              int* max_temperature, RecoveryUI* ui) {
@@ -586,19 +518,6 @@
     return INSTALL_CORRUPT;
   }
 
-  // Try to open the package.
-  ZipArchiveHandle zip = package->GetZipArchiveHandle();
-  if (!zip) {
-    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
-    return INSTALL_CORRUPT;
-  }
-
-  // Additionally verify the compatibility of the package if it's a fresh install.
-  if (retry_count == 0 && !verify_package_compatibility(zip)) {
-    log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
-    return INSTALL_CORRUPT;
-  }
-
   // Verify and install the contents of the package.
   ui->Print("Installing update...\n");
   if (retry_count > 0) {
diff --git a/install/snapshot_utils.cpp b/install/snapshot_utils.cpp
index 69da5ee..7235e67 100644
--- a/install/snapshot_utils.cpp
+++ b/install/snapshot_utils.cpp
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <libsnapshot/snapshot.h>
 
@@ -22,6 +23,7 @@
 #include "recovery_ui/ui.h"
 #include "recovery_utils/roots.h"
 
+using android::snapshot::CreateResult;
 using android::snapshot::SnapshotManager;
 
 bool FinishPendingSnapshotMerges(Device* device) {
@@ -47,3 +49,26 @@
   }
   return true;
 }
+
+bool CreateSnapshotPartitions() {
+  if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
+    // If the device does not support Virtual A/B, there's no need to create
+    // snapshot devices.
+    return true;
+  }
+
+  auto sm = SnapshotManager::NewForFirstStageMount();
+  if (!sm) {
+    // SnapshotManager could not be created. The device is still in a
+    // consistent state and can continue with the mounting of the existing
+    // devices, but cannot initialize snapshot devices.
+    LOG(WARNING) << "Could not create SnapshotManager";
+    return true;
+  }
+
+  auto ret = sm->RecoveryCreateSnapshotDevices();
+  if (ret == CreateResult::ERROR) {
+    return false;
+  }
+  return true;
+}
diff --git a/minui/events.cpp b/minui/events.cpp
index f331ed6..87f8112 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -90,9 +90,11 @@
 
   // The inotify will put one or several complete events.
   // Should not read part of one event.
-  size_t event_len;
-  int ret = ioctl(fd, FIONREAD, &event_len);
+  int event_len_int;
+  int ret = ioctl(fd, FIONREAD, &event_len_int);
   if (ret != 0) return -1;
+  if (event_len_int < 0) return -1;
+  size_t event_len = event_len_int;
 
   std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(INPUT_DEV_DIR), closedir);
   if (!dir) {
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index 6dbf862..ad101ed 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -35,12 +35,10 @@
 #include <string.h>
 #include <unistd.h>
 
-#include <limits>
 #include <string>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
-#include <metricslogger/metrics_logger.h>
 #include <private/android_logger.h> /* private pmsg functions */
 
 #include "recovery_utils/logging.h"
@@ -112,20 +110,6 @@
     return android::base::WriteStringToFile(buffer, destination.c_str());
 }
 
-// Parses the LAST_INSTALL file and reports the update metrics saved under recovery mode.
-static void report_metrics_from_last_install(const std::string& file_name) {
-  auto metrics = ParseLastInstall(file_name);
-  // TODO(xunchang) report the installation result.
-  for (const auto& [event, value] : metrics) {
-    if (value > std::numeric_limits<int>::max()) {
-      LOG(WARNING) << event << " (" << value << ") exceeds integer max.";
-    } else {
-      LOG(INFO) << "Uploading " << value << " to " << event;
-      android::metricslogger::LogHistogram(event, value);
-    }
-  }
-}
-
 int main(int argc, char **argv) {
 
     /* Is /cache a mount?, we have been delivered where we are not wanted */
@@ -157,7 +141,6 @@
     if (has_cache) {
       // Collects and reports the non-a/b update metrics from last_install; and removes the file
       // to avoid duplicate report.
-      report_metrics_from_last_install(LAST_INSTALL_FILE_IN_CACHE);
       if (access(LAST_INSTALL_FILE_IN_CACHE, F_OK) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) {
         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE_IN_CACHE;
       }
@@ -181,7 +164,6 @@
     // For those device without /cache, the last_install file has been copied to
     // /data/misc/recovery from pmsg. Looks for the sideload history only.
     if (!has_cache) {
-      report_metrics_from_last_install(LAST_INSTALL_FILE);
       if (access(LAST_INSTALL_FILE, F_OK) && unlink(LAST_INSTALL_FILE) == -1) {
         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE;
       }
diff --git a/recovery.cpp b/recovery.cpp
index f59a940..9ea616e 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -45,11 +45,11 @@
 #include <ziparchive/zip_archive.h>
 
 #include "bootloader_message/bootloader_message.h"
-#include "fsck_unshare_blocks.h"
 #include "install/adb_install.h"
 #include "install/fuse_install.h"
 #include "install/install.h"
 #include "install/package.h"
+#include "install/snapshot_utils.h"
 #include "install/wipe_data.h"
 #include "install/wipe_device.h"
 #include "otautil/boot_state.h"
@@ -310,11 +310,56 @@
   ui->ShowText(true);
 }
 
+static void WriteUpdateInProgress() {
+  std::string err;
+  if (!update_bootloader_message({ "--reason=update_in_progress" }, &err)) {
+    LOG(ERROR) << "Failed to WriteUpdateInProgress: " << err;
+  }
+}
+
+static bool AskToReboot(Device* device, Device::BuiltinAction chosen_action) {
+  bool is_non_ab = android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+  bool is_virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
+  if (!is_non_ab && !is_virtual_ab) {
+    // Only prompt for non-A/B or Virtual A/B devices.
+    return true;
+  }
+
+  std::string header_text;
+  std::string item_text;
+  switch (chosen_action) {
+    case Device::REBOOT:
+      header_text = "reboot";
+      item_text = " Reboot system now";
+      break;
+    case Device::SHUTDOWN:
+      header_text = "power off";
+      item_text = " Power off";
+      break;
+    default:
+      LOG(FATAL) << "Invalid chosen action " << chosen_action;
+      break;
+  }
+
+  std::vector<std::string> headers{ "WARNING: Previous installation has failed.",
+                                    "  Your device may fail to boot if you " + header_text +
+                                        " now.",
+                                    "  Confirm reboot?" };
+  std::vector<std::string> items{ " Cancel", item_text };
+
+  size_t chosen_item = device->GetUI()->ShowMenu(
+      headers, items, 0, true /* menu_only */,
+      std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+  return (chosen_item == 1);
+}
+
 // Shows the recovery UI and waits for user input. Returns one of the device builtin actions, such
 // as REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, which
 // is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
 static Device::BuiltinAction PromptAndWait(Device* device, InstallResult status) {
   auto ui = device->GetUI();
+  bool update_in_progress = (device->GetReason().value_or("") == "update_in_progress");
   for (;;) {
     FinishRecovery(ui);
     switch (status) {
@@ -339,8 +384,14 @@
     }
     ui->SetProgressType(RecoveryUI::EMPTY);
 
+    std::vector<std::string> headers;
+    if (update_in_progress) {
+      headers = { "WARNING: Previous installation has failed.",
+                  "  Your device may fail to boot if you reboot or power off now." };
+    }
+
     size_t chosen_item = ui->ShowMenu(
-        {}, device->GetMenuItems(), 0, false,
+        headers, device->GetMenuItems(), 0, false,
         std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
     // Handle Interrupt key
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
@@ -361,14 +412,27 @@
 
       case Device::ENTER_FASTBOOT:
       case Device::ENTER_RECOVERY:
-      case Device::REBOOT:
       case Device::REBOOT_BOOTLOADER:
       case Device::REBOOT_FASTBOOT:
       case Device::REBOOT_RECOVERY:
       case Device::REBOOT_RESCUE:
-      case Device::SHUTDOWN:
         return chosen_action;
 
+      case Device::REBOOT:
+      case Device::SHUTDOWN:
+        if (!ui->IsTextVisible()) {
+          return Device::REBOOT;
+        }
+        // okay to reboot; no need to ask.
+        if (!update_in_progress) {
+          return Device::REBOOT;
+        }
+        // An update might have been failed. Ask if user really wants to reboot.
+        if (AskToReboot(device, chosen_action)) {
+          return Device::REBOOT;
+        }
+        break;
+
       case Device::WIPE_DATA:
         save_current_log = true;
         if (ui->IsTextVisible()) {
@@ -396,6 +460,9 @@
       case Device::ENTER_RESCUE: {
         save_current_log = true;
 
+        update_in_progress = true;
+        WriteUpdateInProgress();
+
         bool adb = true;
         Device::BuiltinAction reboot_action;
         if (chosen_action == Device::ENTER_RESCUE) {
@@ -414,12 +481,15 @@
           return reboot_action;
         }
 
-        if (status != INSTALL_SUCCESS) {
+        if (status == INSTALL_SUCCESS) {
+          update_in_progress = false;
+          if (!ui->IsTextVisible()) {
+            return Device::NO_ACTION;  // reboot if logs aren't visible
+          }
+        } else {
           ui->SetBackground(RecoveryUI::ERROR);
           ui->Print("Installation aborted.\n");
           copy_logs(save_current_log);
-        } else if (!ui->IsTextVisible()) {
-          return Device::NO_ACTION;  // reboot if logs aren't visible
         }
         break;
       }
@@ -437,7 +507,13 @@
         screen_ui->CheckBackgroundTextImages();
         break;
       }
+
       case Device::MOUNT_SYSTEM:
+        // For Virtual A/B, set up the snapshot devices (if exist).
+        if (!CreateSnapshotPartitions()) {
+          ui->Print("Virtual A/B: snapshot partitions creation failed.\n");
+          break;
+        }
         if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
           ui->Print("Mounted /system.\n");
         }
@@ -517,7 +593,6 @@
 Device::BuiltinAction start_recovery(Device* device, const std::vector<std::string>& args) {
   static constexpr struct option OPTIONS[] = {
     { "fastboot", no_argument, nullptr, 0 },
-    { "fsck_unshare_blocks", no_argument, nullptr, 0 },
     { "install_with_fuse", no_argument, nullptr, 0 },
     { "just_exit", no_argument, nullptr, 'x' },
     { "locale", required_argument, nullptr, 0 },
@@ -550,7 +625,6 @@
   bool rescue = false;
   bool just_exit = false;
   bool shutdown_after = false;
-  bool fsck_unshare_blocks = false;
   int retry_count = 0;
   bool security_update = false;
   std::string locale;
@@ -573,9 +647,7 @@
         break;
       case 0: {
         std::string option = OPTIONS[option_index].name;
-        if (option == "fsck_unshare_blocks") {
-          fsck_unshare_blocks = true;
-        } else if (option == "install_with_fuse") {
+        if (option == "install_with_fuse") {
           install_with_fuse = true;
         } else if (option == "locale" || option == "fastboot" || option == "reason") {
           // Handled in recovery_main.cpp
@@ -772,10 +844,6 @@
     save_current_log = true;
     status = ApplyFromAdb(device, true /* rescue_mode */, &next_action);
     ui->Print("\nInstall from ADB complete (status: %d).\n", status);
-  } else if (fsck_unshare_blocks) {
-    if (!do_fsck_unshare_blocks()) {
-      status = INSTALL_ERROR;
-    }
   } else if (!just_exit) {
     // 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
diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp
index 087fc0e..6dcb161 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -448,7 +448,9 @@
     int frame_height = gr_get_height(frame);
     int frame_x = (ScreenWidth() - frame_width) / 2;
     int frame_y = GetAnimationBaseline();
-    DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
+    if (frame_x >= 0 && frame_y >= 0 && (frame_x + frame_width) < ScreenWidth() &&
+        (frame_y + frame_height) < ScreenHeight())
+      DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
   }
 
   if (progressBarType != EMPTY) {
diff --git a/recovery_utils/roots.cpp b/recovery_utils/roots.cpp
index fe3a07a..58a3139 100644
--- a/recovery_utils/roots.cpp
+++ b/recovery_utils/roots.cpp
@@ -30,6 +30,7 @@
 #include <vector>
 
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <cryptfs.h>
@@ -152,6 +153,14 @@
     return -1;
   }
 
+  bool needs_casefold = false;
+  bool needs_projid = false;
+
+  if (volume == "/data") {
+    needs_casefold = android::base::GetBoolProperty("ro.emulated_storage.casefold", false);
+    needs_projid = android::base::GetBoolProperty("ro.emulated_storage.projid", false);
+  }
+
   // If there's a key_loc that looks like a path, it should be a block device for storing encryption
   // metadata. Wipe it too.
   if (!v->key_loc.empty() && v->key_loc[0] == '/') {
@@ -187,6 +196,21 @@
       "/system/bin/mke2fs", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize),
     };
 
+    // Project ID's require wider inodes. The Quotas themselves are enabled by tune2fs on boot.
+    if (needs_projid) {
+      mke2fs_args.push_back("-I");
+      mke2fs_args.push_back("512");
+    }
+
+    if (v->fs_mgr_flags.ext_meta_csum) {
+      mke2fs_args.push_back("-O");
+      mke2fs_args.push_back("metadata_csum");
+      mke2fs_args.push_back("-O");
+      mke2fs_args.push_back("64bit");
+      mke2fs_args.push_back("-O");
+      mke2fs_args.push_back("extent");
+    }
+
     int raid_stride = v->logical_blk_size / kBlockSize;
     int raid_stripe_width = v->erase_blk_size / kBlockSize;
     // stride should be the max of 8KB and logical block size
@@ -224,8 +248,18 @@
     "/system/bin/make_f2fs",
     "-g",
     "android",
-    v->blk_device,
   };
+  if (needs_projid) {
+    make_f2fs_cmd.push_back("-O");
+    make_f2fs_cmd.push_back("project_quota,extra_attr");
+  }
+  if (needs_casefold) {
+    make_f2fs_cmd.push_back("-O");
+    make_f2fs_cmd.push_back("casefold");
+    make_f2fs_cmd.push_back("-C");
+    make_f2fs_cmd.push_back("utf8");
+  }
+  make_f2fs_cmd.push_back(v->blk_device);
   if (length >= kSectorSize) {
     make_f2fs_cmd.push_back(std::to_string(length / kSectorSize));
   }
diff --git a/tests/Android.bp b/tests/Android.bp
index 5b881e3..3d22390 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -39,6 +39,7 @@
         android: {
             shared_libs: [
                 "libutils",
+                "libvndksupport",
             ],
         },
 
@@ -81,7 +82,6 @@
     "libotautil",
 
     "libhealthhalutils",
-    "libvintf_recovery",
     "libvintf",
 
     "android.hardware.health@2.0",
@@ -90,12 +90,28 @@
     "libfs_mgr",
     "libhidl-gen-utils",
     "libhidlbase",
-    "libbinderthreadstate",
     "liblp",
-    "libvndksupport",
     "libtinyxml2",
 ]
 
+// recovery image for unittests.
+// ========================================================
+genrule {
+    name: "recovery_image",
+    cmd: "cat $(location testdata/recovery_head) <(cat $(location testdata/recovery_body) | $(location minigzip)) $(location testdata/recovery_tail) > $(out)",
+    srcs: [
+        "testdata/recovery_head",
+        "testdata/recovery_body",
+        "testdata/recovery_tail",
+    ],
+    tools: [
+        "minigzip",
+    ],
+    out: [
+        "testdata/recovery.img",
+    ],
+}
+
 cc_test {
     name: "recovery_unit_test",
     isolated: true,
@@ -129,6 +145,7 @@
 
     data: [
         "testdata/*",
+        ":recovery_image",
         ":res-testdata",
     ],
 }
diff --git a/tests/testdata/recovery.img b/tests/testdata/recovery.img
deleted file mode 100644
index b862e6f..0000000
--- a/tests/testdata/recovery.img
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/recovery_body b/tests/testdata/recovery_body
new file mode 100644
index 0000000..48d7c10
--- /dev/null
+++ b/tests/testdata/recovery_body
Binary files differ
diff --git a/tests/testdata/recovery_head b/tests/testdata/recovery_head
new file mode 100644
index 0000000..7f494d0
--- /dev/null
+++ b/tests/testdata/recovery_head
Binary files differ
diff --git a/tests/testdata/recovery_tail b/tests/testdata/recovery_tail
new file mode 100644
index 0000000..7fe2c6c
--- /dev/null
+++ b/tests/testdata/recovery_tail
Binary files differ
diff --git a/tests/unit/install_test.cpp b/tests/unit/install_test.cpp
index 4ec4099..370fbdc 100644
--- a/tests/unit/install_test.cpp
+++ b/tests/unit/install_test.cpp
@@ -28,7 +28,6 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <gtest/gtest.h>
-#include <vintf/VintfObjectRecovery.h>
 #include <ziparchive/zip_archive.h>
 #include <ziparchive/zip_writer.h>
 
@@ -50,29 +49,6 @@
   ASSERT_EQ(0, fclose(zip_file));
 }
 
-TEST(InstallTest, verify_package_compatibility_no_entry) {
-  TemporaryFile temp_file;
-  // The archive must have something to be opened correctly.
-  BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
-
-  // Doesn't contain compatibility zip entry.
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  ASSERT_TRUE(verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
-TEST(InstallTest, verify_package_compatibility_invalid_entry) {
-  TemporaryFile temp_file;
-  BuildZipArchive({ { "compatibility.zip", "" } }, temp_file.release(), kCompressStored);
-
-  // Empty compatibility zip entry.
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  ASSERT_FALSE(verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
 TEST(InstallTest, read_metadata_from_package_smoke) {
   TemporaryFile temp_file;
   const std::string content("abc=defg");
@@ -134,64 +110,6 @@
   ASSERT_EQ(expected, read_partition_list);
 }
 
-TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
-  TemporaryFile compatibility_zip_file;
-  std::string malformed_xml = "malformed";
-  BuildZipArchive({ { "system_manifest.xml", malformed_xml } }, compatibility_zip_file.release(),
-                  kCompressDeflated);
-
-  TemporaryFile temp_file;
-  std::string compatibility_zip_content;
-  ASSERT_TRUE(
-      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
-  BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
-                  kCompressStored);
-
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  std::vector<std::string> compatibility_info;
-  compatibility_info.push_back(malformed_xml);
-  // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to
-  // libvintf.
-  std::string err;
-  bool result =
-      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
-  ASSERT_EQ(result, verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
-TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) {
-  static constexpr const char* system_manifest_xml_path = "/system/manifest.xml";
-  if (access(system_manifest_xml_path, R_OK) == -1) {
-    GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml.";
-    return;
-  }
-  std::string system_manifest_xml_content;
-  ASSERT_TRUE(
-      android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
-  TemporaryFile compatibility_zip_file;
-  BuildZipArchive({ { "system_manifest.xml", system_manifest_xml_content } },
-                  compatibility_zip_file.release(), kCompressDeflated);
-
-  TemporaryFile temp_file;
-  std::string compatibility_zip_content;
-  ASSERT_TRUE(
-      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
-  BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
-                  kCompressStored);
-
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  std::vector<std::string> compatibility_info;
-  compatibility_info.push_back(system_manifest_xml_content);
-  std::string err;
-  bool result =
-      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
-  // Make sure the result is consistent with libvintf library.
-  ASSERT_EQ(result, verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
 TEST(InstallTest, SetUpNonAbUpdateCommands) {
   TemporaryFile temp_file;
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
diff --git a/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml
index 2af36d6..a9cd2d1 100644
--- a/tools/recovery_l10n/res/values-ar/strings.xml
+++ b/tools/recovery_l10n/res/values-ar/strings.xml
@@ -6,9 +6,9 @@
     <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_wipe_data_menu_header" msgid="550255032058254478">"‏يتعذَّر تحميل نظام Android، حيث قد تكون بياناتك تالفة. وإذا استمر ظهور هذه الرسالة، قد يتعيَّن عليك إجراء إعادة الضبط بحسب بيانات المصنع ومحو جميع بيانات المستخدم المُخزَّنة على هذا الجهاز."</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"‏يتعذَّر تحميل نظام Android، حيث قد تكون بياناتك تالفة. وإذا استمر ظهور هذه الرسالة، قد يتعيَّن عليك إجراء إعادة الضبط على الإعدادات الأصلية ومحو جميع بيانات المستخدم المُخزَّنة على هذا الجهاز."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"إعادة المحاولة"</string>
-    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"إعادة الضبط بحسب بيانات المصنع"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"إعادة الضبط على الإعدادات الأصلية"</string>
     <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"هل تريد حجب كل بيانات المستخدم؟\n\n لا يمكن التراجع عن هذا الإجراء."</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"إلغاء"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml
index e51b36d..e6f2ffd 100644
--- a/tools/recovery_l10n/res/values-gl/strings.xml
+++ b/tools/recovery_l10n/res/values-gl/strings.xml
@@ -6,9 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Non hai ningún comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erro"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando actualización de seguranza"</string>
-    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos de usuario almacenados neste dispositivo."</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos do usuario almacenados neste dispositivo."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"Tentar de novo"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restablecemento dos datos de fábrica"</string>
-    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos de usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos do usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml
index 15a78ec..43c9deb 100644
--- a/tools/recovery_l10n/res/values-in/strings.xml
+++ b/tools/recovery_l10n/res/values-in/strings.xml
@@ -9,6 +9,6 @@
     <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Tidak dapat memuat sistem Android. Data Anda mungkin rusak. Jika terus mendapatkan pesan ini, Anda mungkin perlu melakukan reset ke setelan pabrik dan menghapus semua data pengguna yang disimpan di perangkat ini."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"Coba lagi"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Reset ke setelan pabrik"</string>
-    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Hapus total semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!"</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Batal"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml
index 3d66372..2d6c0ab 100644
--- a/tools/recovery_l10n/res/values-ja/strings.xml
+++ b/tools/recovery_l10n/res/values-ja/strings.xml
@@ -6,7 +6,7 @@
     <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_wipe_data_menu_header" msgid="550255032058254478">"Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、この端末に保存されているすべてのユーザー データを消去することが必要な場合があります。"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、このデバイスに保存されているすべてのユーザー データを消去することが必要な場合があります。"</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"再試行"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"データの初期化"</string>
     <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"すべてのユーザー データをワイプしますか?\n\nこの操作は元に戻せません。"</string>
diff --git a/tools/recovery_l10n/res/values-ky/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml
index 1cd69ea..837cf7d 100644
--- a/tools/recovery_l10n/res/values-ky/strings.xml
+++ b/tools/recovery_l10n/res/values-ky/strings.xml
@@ -6,7 +6,7 @@
     <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_wipe_data_menu_header" msgid="550255032058254478">"Android тутуму жүктөлбөй жатат. Дайындарыңыз бузук болушу мүмкүн. Бул билдирүү дагы деле келе берсе, түзмөктү кайра башынан жөндөп, анда сакталган бардык колдонуучу дайындарын тазалашыңыз керек."</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android тутуму жүктөлбөй жатат. Дайын-даректериңиз бузук болушу мүмкүн. Бул билдирүү дагы деле келе берсе, түзмөктү кайра башынан жөндөп, анда сакталган бардык колдонуучу дайындарын тазалашыңыз керек."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"Кайталоо"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Кайра башынан жөндөө"</string>
     <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Колдонуучу дайындарынын баары жашырылсынбы?\n\n МУНУ АРТКА КАЙТАРУУ МҮМКҮН ЭМЕС!"</string>
diff --git a/updater/Android.bp b/updater/Android.bp
index 8a60ef7..cbef430 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -13,11 +13,7 @@
 // limitations under the License.
 
 cc_defaults {
-    name: "libupdater_defaults",
-
-    defaults: [
-        "recovery_defaults",
-    ],
+    name: "libupdater_static_libs",
 
     static_libs: [
         "libapplypatch",
@@ -45,6 +41,15 @@
         "libcutils",
         "libutils",
     ],
+}
+
+cc_defaults {
+    name: "libupdater_defaults",
+
+    defaults: [
+        "recovery_defaults",
+        "libupdater_static_libs",
+    ],
 
     shared_libs: [
         "libcrypto",
@@ -64,7 +69,7 @@
         "libext2_uuid",
         "libext2_e2p",
         "libext2fs",
-    ]
+    ],
 }
 
 cc_library_static {
@@ -155,3 +160,29 @@
         "include",
     ],
 }
+
+cc_binary_host {
+    name: "update_host_simulator",
+    defaults: ["libupdater_static_libs"],
+
+    srcs: ["update_simulator_main.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    static_libs: [
+        "libupdater_host",
+        "libupdater_core",
+        "libcrypto_static",
+        "libfstab",
+        "libc++fs",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index 6f54d89..8a4cd86 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -111,32 +111,3 @@
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
 include $(BUILD_EXECUTABLE)
-
-# TODO(xunchang) move to bp file
-# update_host_simulator (host executable)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := update_host_simulator
-LOCAL_MODULE_HOST_OS := linux
-
-LOCAL_SRC_FILES := \
-    update_simulator_main.cpp
-
-LOCAL_C_INCLUDES := \
-    $(LOCAL_PATH)/include
-
-LOCAL_CFLAGS := \
-    -Wall \
-    -Werror
-
-LOCAL_STATIC_LIBRARIES := \
-    libupdater_host \
-    libupdater_core \
-    $(updater_common_static_libraries) \
-    libfstab \
-    libc++fs
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-
-include $(BUILD_HOST_EXECUTABLE)
diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml
index 0a25116..981cd8e 100644
--- a/updater_sample/AndroidManifest.xml
+++ b/updater_sample/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27" />
 
     <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application
         android:icon="@mipmap/ic_launcher"