Merge "fs_mgr: overlayfs: fsck scratch filesystem"
diff --git a/adb/Android.bp b/adb/Android.bp
index 3dc70b5..2f9c8fc 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -27,7 +27,6 @@
         "-DADB_HOST=1",         // overridden by adbd_defaults
         "-DALLOW_ADBD_ROOT=0",  // overridden by adbd_defaults
         "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1",
-        "-DENABLE_FASTDEPLOY=1", // enable fast deploy
     ],
     cpp_std: "experimental",
 
@@ -691,6 +690,7 @@
     name: "libfastdeploy_host",
     defaults: ["adb_defaults"],
     srcs: [
+        "fastdeploy/deploypatchgenerator/apk_archive.cpp",
         "fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp",
         "fastdeploy/deploypatchgenerator/patch_utils.cpp",
         "fastdeploy/proto/ApkEntry.proto",
@@ -727,6 +727,7 @@
     name: "fastdeploy_test",
     defaults: ["adb_defaults"],
     srcs: [
+        "fastdeploy/deploypatchgenerator/apk_archive_test.cpp",
         "fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp",
         "fastdeploy/deploypatchgenerator/patch_utils_test.cpp",
     ],
@@ -754,6 +755,9 @@
         },
     },
     data: [
+        "fastdeploy/testdata/rotating_cube-metadata-release.data",
         "fastdeploy/testdata/rotating_cube-release.apk",
+        "fastdeploy/testdata/sample.apk",
+        "fastdeploy/testdata/sample.cd",
     ],
 }
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 042a2d6..73dcde1 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -37,9 +37,7 @@
 #include "commandline.h"
 #include "fastdeploy.h"
 
-#if defined(ENABLE_FASTDEPLOY)
 static constexpr int kFastDeployMinApi = 24;
-#endif
 
 namespace {
 
@@ -146,15 +144,7 @@
     *buf = '\0';
 }
 
-#if defined(ENABLE_FASTDEPLOY)
-static int delete_device_patch_file(const char* apkPath) {
-    std::string patchDevicePath = get_patch_path(apkPath);
-    return delete_device_file(patchDevicePath);
-}
-#endif
-
-static int install_app_streamed(int argc, const char** argv, bool use_fastdeploy,
-                                bool use_localagent) {
+static int install_app_streamed(int argc, const char** argv, bool use_fastdeploy) {
     printf("Performing Streamed Install\n");
 
     // The last argument must be the APK file
@@ -176,31 +166,15 @@
         error_exit("--fastdeploy doesn't support .apex files");
     }
 
-    if (use_fastdeploy == true) {
-#if defined(ENABLE_FASTDEPLOY)
-        TemporaryFile metadataTmpFile;
-        std::string patchTmpFilePath;
-        {
-            TemporaryFile patchTmpFile;
-            patchTmpFile.DoNotRemove();
-            patchTmpFilePath = patchTmpFile.path;
+    if (use_fastdeploy) {
+        auto metadata = extract_metadata(file);
+        if (metadata.has_value()) {
+            // pass all but 1st (command) and last (apk path) parameters through to pm for
+            // session creation
+            std::vector<const char*> pm_args{argv + 1, argv + argc - 1};
+            auto patchFd = install_patch(pm_args.size(), pm_args.data());
+            return stream_patch(file, std::move(metadata.value()), std::move(patchFd));
         }
-
-        FILE* metadataFile = fopen(metadataTmpFile.path, "wb");
-        extract_metadata(file, metadataFile);
-        fclose(metadataFile);
-
-        create_patch(file, metadataTmpFile.path, patchTmpFilePath.c_str());
-        // pass all but 1st (command) and last (apk path) parameters through to pm for
-        // session creation
-        std::vector<const char*> pm_args{argv + 1, argv + argc - 1};
-        install_patch(file, patchTmpFilePath.c_str(), pm_args.size(), pm_args.data());
-        adb_unlink(patchTmpFilePath.c_str());
-        delete_device_patch_file(file);
-        return 0;
-#else
-        error_exit("fastdeploy is disabled");
-#endif
     }
 
     struct stat sb;
@@ -265,8 +239,7 @@
     return 1;
 }
 
-static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy,
-                              bool use_localagent) {
+static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy) {
     printf("Performing Push Install\n");
 
     // Find last APK argument.
@@ -287,35 +260,26 @@
     int result = -1;
     std::vector<const char*> apk_file = {argv[last_apk]};
     std::string apk_dest = "/data/local/tmp/" + android::base::Basename(argv[last_apk]);
-
-    if (use_fastdeploy == true) {
-#if defined(ENABLE_FASTDEPLOY)
-        TemporaryFile metadataTmpFile;
-        TemporaryFile patchTmpFile;
-
-        FILE* metadataFile = fopen(metadataTmpFile.path, "wb");
-        extract_metadata(apk_file[0], metadataFile);
-        fclose(metadataFile);
-
-        create_patch(apk_file[0], metadataTmpFile.path, patchTmpFile.path);
-        apply_patch_on_device(apk_file[0], patchTmpFile.path, apk_dest.c_str());
-#else
-        error_exit("fastdeploy is disabled");
-#endif
-    } else {
-        if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk;
-    }
-
     argv[last_apk] = apk_dest.c_str(); /* destination name, not source location */
-    result = pm_command(argc, argv);
 
-cleanup_apk:
-    if (use_fastdeploy == true) {
-#if defined(ENABLE_FASTDEPLOY)
-        delete_device_patch_file(apk_file[0]);
-#endif
+    if (use_fastdeploy) {
+        auto metadata = extract_metadata(apk_file[0]);
+        if (metadata.has_value()) {
+            auto patchFd = apply_patch_on_device(apk_dest.c_str());
+            int status = stream_patch(apk_file[0], std::move(metadata.value()), std::move(patchFd));
+
+            result = pm_command(argc, argv);
+            delete_device_file(apk_dest);
+
+            return status;
+        }
     }
-    delete_device_file(apk_dest);
+
+    if (do_sync_push(apk_file, apk_dest.c_str(), false)) {
+        result = pm_command(argc, argv);
+        delete_device_file(apk_dest);
+    }
+
     return result;
 }
 
@@ -324,7 +288,6 @@
     InstallMode installMode = INSTALL_DEFAULT;
     bool use_fastdeploy = false;
     bool is_reinstall = false;
-    bool use_localagent = false;
     FastDeploy_AgentUpdateStrategy agent_update_strategy = FastDeploy_AgentUpdateDifferentVersion;
 
     for (int i = 1; i < argc; i++) {
@@ -353,11 +316,6 @@
         } else if (!strcmp(argv[i], "--version-check-agent")) {
             processedArgIndicies.push_back(i);
             agent_update_strategy = FastDeploy_AgentUpdateDifferentVersion;
-#ifndef _WIN32
-        } else if (!strcmp(argv[i], "--local-agent")) {
-            processedArgIndicies.push_back(i);
-            use_localagent = true;
-#endif
         }
     }
 
@@ -369,14 +327,13 @@
         error_exit("Attempting to use streaming install on unsupported device");
     }
 
-#if defined(ENABLE_FASTDEPLOY)
-    if (use_fastdeploy == true && get_device_api_level() < kFastDeployMinApi) {
+    if (use_fastdeploy && get_device_api_level() < kFastDeployMinApi) {
         printf("Fast Deploy is only compatible with devices of API version %d or higher, "
                "ignoring.\n",
                kFastDeployMinApi);
         use_fastdeploy = false;
     }
-#endif
+    fastdeploy_set_agent_update_strategy(agent_update_strategy);
 
     std::vector<const char*> passthrough_argv;
     for (int i = 0; i < argc; i++) {
@@ -389,26 +346,13 @@
         error_exit("install requires an apk argument");
     }
 
-    if (use_fastdeploy == true) {
-#if defined(ENABLE_FASTDEPLOY)
-        fastdeploy_set_local_agent(use_localagent);
-        update_agent(agent_update_strategy);
-
-        // The last argument must be the APK file
-        const char* file = passthrough_argv.back();
-        use_fastdeploy = find_package(file);
-#else
-        error_exit("fastdeploy is disabled");
-#endif
-    }
-
     switch (installMode) {
         case INSTALL_PUSH:
             return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(),
-                                      use_fastdeploy, use_localagent);
+                                      use_fastdeploy);
         case INSTALL_STREAM:
             return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(),
-                                        use_fastdeploy, use_localagent);
+                                        use_fastdeploy);
         case INSTALL_DEFAULT:
         default:
             return 1;
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index a0818d3..0ffdbc2 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -255,13 +255,8 @@
 }
 #endif
 
-// Reads from |fd| and prints received data. If |use_shell_protocol| is true
-// this expects that incoming data will use the shell protocol, in which case
-// stdout/stderr are routed independently and the remote exit code will be
-// returned.
-// if |callback| is non-null, stdout/stderr output will be handled by it.
-int read_and_dump(borrowed_fd fd, bool use_shell_protocol = false,
-                  StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK) {
+int read_and_dump(borrowed_fd fd, bool use_shell_protocol,
+                  StandardStreamsCallbackInterface* callback) {
     int exit_code = 0;
     if (fd < 0) return exit_code;
 
diff --git a/adb/client/commandline.h b/adb/client/commandline.h
index cd5933a..ab77b29 100644
--- a/adb/client/commandline.h
+++ b/adb/client/commandline.h
@@ -109,6 +109,14 @@
         const std::string& command, bool disable_shell_protocol = false,
         StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK);
 
+// Reads from |fd| and prints received data. If |use_shell_protocol| is true
+// this expects that incoming data will use the shell protocol, in which case
+// stdout/stderr are routed independently and the remote exit code will be
+// returned.
+// if |callback| is non-null, stdout/stderr output will be handled by it.
+int read_and_dump(borrowed_fd fd, bool use_shell_protocol = false,
+                  StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK);
+
 // Connects to the device "abb" service with |command| and returns the fd.
 template <typename ContainerT>
 unique_fd send_abb_exec_command(const ContainerT& command_args, std::string* error) {
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
index fbae219..bdc9e56 100644
--- a/adb/client/fastdeploy.cpp
+++ b/adb/client/fastdeploy.cpp
@@ -30,112 +30,114 @@
 #include "deployagent.inc"        // Generated include via build rule.
 #include "deployagentscript.inc"  // Generated include via build rule.
 #include "fastdeploy/deploypatchgenerator/deploy_patch_generator.h"
+#include "fastdeploy/deploypatchgenerator/patch_utils.h"
+#include "fastdeploy/proto/ApkEntry.pb.h"
 #include "fastdeploycallbacks.h"
 #include "sysdeps.h"
 
 #include "adb_utils.h"
 
-static constexpr long kRequiredAgentVersion = 0x00000002;
+static constexpr long kRequiredAgentVersion = 0x00000003;
 
-static constexpr const char* kDeviceAgentPath = "/data/local/tmp/";
+static constexpr int kPackageMissing = 3;
+static constexpr int kInvalidAgentVersion = 4;
+
 static constexpr const char* kDeviceAgentFile = "/data/local/tmp/deployagent.jar";
 static constexpr const char* kDeviceAgentScript = "/data/local/tmp/deployagent";
 
-static bool g_use_localagent = false;
+static constexpr bool g_verbose_timings = false;
+static FastDeploy_AgentUpdateStrategy g_agent_update_strategy =
+        FastDeploy_AgentUpdateDifferentVersion;
 
-long get_agent_version() {
-    std::vector<char> versionOutputBuffer;
-    std::vector<char> versionErrorBuffer;
+using APKMetaData = com::android::fastdeploy::APKMetaData;
 
-    int statusCode = capture_shell_command("/data/local/tmp/deployagent version",
-                                           &versionOutputBuffer, &versionErrorBuffer);
-    long version = -1;
+namespace {
 
-    if (statusCode == 0 && versionOutputBuffer.size() > 0) {
-        version = strtol((char*)versionOutputBuffer.data(), NULL, 16);
+struct TimeReporter {
+    TimeReporter(const char* label) : label_(label) {}
+    ~TimeReporter() {
+        if (g_verbose_timings) {
+            auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+                    std::chrono::steady_clock::now() - start_);
+            fprintf(stderr, "%s finished in %lldms\n", label_,
+                    static_cast<long long>(duration.count()));
+        }
     }
 
-    return version;
-}
+  private:
+    const char* label_;
+    std::chrono::steady_clock::time_point start_ = std::chrono::steady_clock::now();
+};
+#define REPORT_FUNC_TIME() TimeReporter reporter(__func__)
+
+struct FileDeleter {
+    FileDeleter(const char* path) : path_(path) {}
+    ~FileDeleter() { adb_unlink(path_); }
+
+  private:
+    const char* const path_;
+};
+
+}  // namespace
 
 int get_device_api_level() {
-    std::vector<char> sdkVersionOutputBuffer;
-    std::vector<char> sdkVersionErrorBuffer;
+    REPORT_FUNC_TIME();
+    std::vector<char> sdk_version_output_buffer;
+    std::vector<char> sdk_version_error_buffer;
     int api_level = -1;
 
-    int statusCode = capture_shell_command("getprop ro.build.version.sdk", &sdkVersionOutputBuffer,
-                                           &sdkVersionErrorBuffer);
-    if (statusCode == 0 && sdkVersionOutputBuffer.size() > 0) {
-        api_level = strtol((char*)sdkVersionOutputBuffer.data(), NULL, 10);
+    int statusCode = capture_shell_command("getprop ro.build.version.sdk",
+                                           &sdk_version_output_buffer, &sdk_version_error_buffer);
+    if (statusCode == 0 && sdk_version_output_buffer.size() > 0) {
+        api_level = strtol((char*)sdk_version_output_buffer.data(), NULL, 10);
     }
 
     return api_level;
 }
 
-void fastdeploy_set_local_agent(bool use_localagent) {
-    g_use_localagent = use_localagent;
+void fastdeploy_set_agent_update_strategy(FastDeploy_AgentUpdateStrategy agent_update_strategy) {
+    g_agent_update_strategy = agent_update_strategy;
 }
 
-static bool deploy_agent(bool checkTimeStamps) {
+static void push_to_device(const void* data, size_t byte_count, const char* dst, bool sync) {
     std::vector<const char*> srcs;
-    // TODO: Deploy agent from bin2c directly instead of writing to disk first.
-    TemporaryFile tempAgent;
-    android::base::WriteFully(tempAgent.fd, kDeployAgent, sizeof(kDeployAgent));
-    srcs.push_back(tempAgent.path);
-    if (!do_sync_push(srcs, kDeviceAgentFile, checkTimeStamps)) {
+    {
+        TemporaryFile temp;
+        android::base::WriteFully(temp.fd, data, byte_count);
+        srcs.push_back(temp.path);
+
+        // On Windows, the file needs to be flushed before pushing to device.
+        // closing the file flushes its content, but we still need to remove it after push.
+        // FileDeleter does exactly that.
+        temp.DoNotRemove();
+    }
+    FileDeleter temp_deleter(srcs.back());
+
+    if (!do_sync_push(srcs, dst, sync)) {
         error_exit("Failed to push fastdeploy agent to device.");
     }
-    srcs.clear();
-    // TODO: Deploy agent from bin2c directly instead of writing to disk first.
-    TemporaryFile tempAgentScript;
-    android::base::WriteFully(tempAgentScript.fd, kDeployAgentScript, sizeof(kDeployAgentScript));
-    srcs.push_back(tempAgentScript.path);
-    if (!do_sync_push(srcs, kDeviceAgentScript, checkTimeStamps)) {
-        error_exit("Failed to push fastdeploy agent script to device.");
-    }
-    srcs.clear();
+}
+
+static bool deploy_agent(bool check_time_stamps) {
+    REPORT_FUNC_TIME();
+
+    push_to_device(kDeployAgent, sizeof(kDeployAgent), kDeviceAgentFile, check_time_stamps);
+    push_to_device(kDeployAgentScript, sizeof(kDeployAgentScript), kDeviceAgentScript,
+                   check_time_stamps);
+
     // on windows the shell script might have lost execute permission
     // so need to set this explicitly
     const char* kChmodCommandPattern = "chmod 777 %s";
-    std::string chmodCommand =
+    std::string chmod_command =
             android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentScript);
-    int ret = send_shell_command(chmodCommand);
+    int ret = send_shell_command(chmod_command);
     if (ret != 0) {
-        error_exit("Error executing %s returncode: %d", chmodCommand.c_str(), ret);
+        error_exit("Error executing %s returncode: %d", chmod_command.c_str(), ret);
     }
 
     return true;
 }
 
-void update_agent(FastDeploy_AgentUpdateStrategy agentUpdateStrategy) {
-    long agent_version = get_agent_version();
-    switch (agentUpdateStrategy) {
-        case FastDeploy_AgentUpdateAlways:
-            deploy_agent(false);
-            break;
-        case FastDeploy_AgentUpdateNewerTimeStamp:
-            deploy_agent(true);
-            break;
-        case FastDeploy_AgentUpdateDifferentVersion:
-            if (agent_version != kRequiredAgentVersion) {
-                if (agent_version < 0) {
-                    printf("Could not detect agent on device, deploying\n");
-                } else {
-                    printf("Device agent version is (%ld), (%ld) is required, re-deploying\n",
-                           agent_version, kRequiredAgentVersion);
-                }
-                deploy_agent(false);
-            }
-            break;
-    }
-
-    agent_version = get_agent_version();
-    if (agent_version != kRequiredAgentVersion) {
-        error_exit("After update agent version remains incorrect! Expected %ld but version is %ld",
-                   kRequiredAgentVersion, agent_version);
-    }
-}
-
 static std::string get_string_from_utf16(const char16_t* input, int input_len) {
     ssize_t utf8_length = utf16_to_utf8_length(input, input_len);
     if (utf8_length <= 0) {
@@ -147,29 +149,29 @@
     return utf8;
 }
 
-static std::string get_packagename_from_apk(const char* apkPath) {
+static std::string get_package_name_from_apk(const char* apk_path) {
 #undef open
-    std::unique_ptr<android::ZipFileRO> zipFile(android::ZipFileRO::open(apkPath));
+    std::unique_ptr<android::ZipFileRO> zip_file((android::ZipFileRO::open)(apk_path));
 #define open ___xxx_unix_open
-    if (zipFile == nullptr) {
-        perror_exit("Could not open %s", apkPath);
+    if (zip_file == nullptr) {
+        perror_exit("Could not open %s", apk_path);
     }
-    android::ZipEntryRO entry = zipFile->findEntryByName("AndroidManifest.xml");
+    android::ZipEntryRO entry = zip_file->findEntryByName("AndroidManifest.xml");
     if (entry == nullptr) {
-        error_exit("Could not find AndroidManifest.xml inside %s", apkPath);
+        error_exit("Could not find AndroidManifest.xml inside %s", apk_path);
     }
     uint32_t manifest_len = 0;
-    if (!zipFile->getEntryInfo(entry, NULL, &manifest_len, NULL, NULL, NULL, NULL)) {
-        error_exit("Could not read AndroidManifest.xml inside %s", apkPath);
+    if (!zip_file->getEntryInfo(entry, NULL, &manifest_len, NULL, NULL, NULL, NULL)) {
+        error_exit("Could not read AndroidManifest.xml inside %s", apk_path);
     }
     std::vector<char> manifest_data(manifest_len);
-    if (!zipFile->uncompressEntry(entry, manifest_data.data(), manifest_len)) {
-        error_exit("Could not uncompress AndroidManifest.xml inside %s", apkPath);
+    if (!zip_file->uncompressEntry(entry, manifest_data.data(), manifest_len)) {
+        error_exit("Could not uncompress AndroidManifest.xml inside %s", apk_path);
     }
     android::ResXMLTree tree;
     android::status_t setto_status = tree.setTo(manifest_data.data(), manifest_len, true);
     if (setto_status != android::OK) {
-        error_exit("Could not parse AndroidManifest.xml inside %s", apkPath);
+        error_exit("Could not parse AndroidManifest.xml inside %s", apk_path);
     }
     android::ResXMLParser::event_code_t code;
     while ((code = tree.next()) != android::ResXMLParser::BAD_DOCUMENT &&
@@ -210,80 +212,97 @@
                 break;
         }
     }
-    error_exit("Could not find package name tag in AndroidManifest.xml inside %s", apkPath);
+    error_exit("Could not find package name tag in AndroidManifest.xml inside %s", apk_path);
 }
 
-void extract_metadata(const char* apkPath, FILE* outputFp) {
-    std::string packageName = get_packagename_from_apk(apkPath);
-    const char* kAgentExtractCommandPattern = "/data/local/tmp/deployagent extract %s";
-    std::string extractCommand =
-            android::base::StringPrintf(kAgentExtractCommandPattern, packageName.c_str());
+static long parse_agent_version(const std::vector<char>& version_buffer) {
+    long version = -1;
+    if (!version_buffer.empty()) {
+        version = strtol((char*)version_buffer.data(), NULL, 16);
+    }
+    return version;
+}
 
-    std::vector<char> extractErrorBuffer;
-    DeployAgentFileCallback cb(outputFp, &extractErrorBuffer);
-    int returnCode = send_shell_command(extractCommand, false, &cb);
+static void update_agent_if_necessary() {
+    switch (g_agent_update_strategy) {
+        case FastDeploy_AgentUpdateAlways:
+            deploy_agent(/*check_time_stamps=*/false);
+            break;
+        case FastDeploy_AgentUpdateNewerTimeStamp:
+            deploy_agent(/*check_time_stamps=*/true);
+            break;
+        default:
+            break;
+    }
+}
+
+std::optional<APKMetaData> extract_metadata(const char* apk_path) {
+    // Update agent if there is a command line argument forcing to do so.
+    update_agent_if_necessary();
+
+    REPORT_FUNC_TIME();
+
+    std::string package_name = get_package_name_from_apk(apk_path);
+
+    // Dump apk command checks the required vs current agent version and if they match then returns
+    // the APK dump for package. Doing this in a single call saves round-trip and agent launch time.
+    constexpr const char* kAgentDumpCommandPattern = "/data/local/tmp/deployagent dump %ld %s";
+    std::string dump_command = android::base::StringPrintf(
+            kAgentDumpCommandPattern, kRequiredAgentVersion, package_name.c_str());
+
+    std::vector<char> dump_out_buffer;
+    std::vector<char> dump_error_buffer;
+    int returnCode =
+            capture_shell_command(dump_command.c_str(), &dump_out_buffer, &dump_error_buffer);
+    if (returnCode >= kInvalidAgentVersion) {
+        // Agent has wrong version or missing.
+        long agent_version = parse_agent_version(dump_out_buffer);
+        if (agent_version < 0) {
+            printf("Could not detect agent on device, deploying\n");
+        } else {
+            printf("Device agent version is (%ld), (%ld) is required, re-deploying\n",
+                   agent_version, kRequiredAgentVersion);
+        }
+        deploy_agent(/*check_time_stamps=*/false);
+
+        // Retry with new agent.
+        dump_out_buffer.clear();
+        dump_error_buffer.clear();
+        returnCode =
+                capture_shell_command(dump_command.c_str(), &dump_out_buffer, &dump_error_buffer);
+    }
     if (returnCode != 0) {
-        fprintf(stderr, "Executing %s returned %d\n", extractCommand.c_str(), returnCode);
-        fprintf(stderr, "%*s\n", int(extractErrorBuffer.size()), extractErrorBuffer.data());
+        if (returnCode == kInvalidAgentVersion) {
+            long agent_version = parse_agent_version(dump_out_buffer);
+            error_exit(
+                    "After update agent version remains incorrect! Expected %ld but version is %ld",
+                    kRequiredAgentVersion, agent_version);
+        }
+        if (returnCode == kPackageMissing) {
+            fprintf(stderr, "Package %s not found, falling back to install\n",
+                    package_name.c_str());
+            return {};
+        }
+        fprintf(stderr, "Executing %s returned %d\n", dump_command.c_str(), returnCode);
+        fprintf(stderr, "%*s\n", int(dump_error_buffer.size()), dump_error_buffer.data());
         error_exit("Aborting");
     }
+
+    com::android::fastdeploy::APKDump dump;
+    if (!dump.ParseFromArray(dump_out_buffer.data(), dump_out_buffer.size())) {
+        fprintf(stderr, "Can't parse output of %s\n", dump_command.c_str());
+        error_exit("Aborting");
+    }
+
+    return PatchUtils::GetDeviceAPKMetaData(dump);
 }
 
-void create_patch(const char* apkPath, const char* metadataPath, const char* patchPath) {
-    DeployPatchGenerator generator(false);
-    unique_fd patchFd(adb_open(patchPath, O_WRONLY | O_CREAT | O_CLOEXEC));
-    if (patchFd < 0) {
-        perror_exit("adb: failed to create %s", patchPath);
-    }
-    bool success = generator.CreatePatch(apkPath, metadataPath, patchFd);
-    if (!success) {
-        error_exit("Failed to create patch for %s", apkPath);
-    }
-}
+unique_fd install_patch(int argc, const char** argv) {
+    REPORT_FUNC_TIME();
+    constexpr char kAgentApplyServicePattern[] = "shell:/data/local/tmp/deployagent apply - -pm %s";
 
-std::string get_patch_path(const char* apkPath) {
-    std::string packageName = get_packagename_from_apk(apkPath);
-    std::string patchDevicePath =
-            android::base::StringPrintf("%s%s.patch", kDeviceAgentPath, packageName.c_str());
-    return patchDevicePath;
-}
-
-void apply_patch_on_device(const char* apkPath, const char* patchPath, const char* outputPath) {
-    const std::string kAgentApplyCommandPattern = "/data/local/tmp/deployagent apply %s %s -o %s";
-    std::string packageName = get_packagename_from_apk(apkPath);
-    std::string patchDevicePath = get_patch_path(apkPath);
-
-    std::vector<const char*> srcs = {patchPath};
-    bool push_ok = do_sync_push(srcs, patchDevicePath.c_str(), false);
-    if (!push_ok) {
-        error_exit("Error pushing %s to %s returned", patchPath, patchDevicePath.c_str());
-    }
-
-    std::string applyPatchCommand =
-            android::base::StringPrintf(kAgentApplyCommandPattern.c_str(), packageName.c_str(),
-                                        patchDevicePath.c_str(), outputPath);
-
-    int returnCode = send_shell_command(applyPatchCommand);
-    if (returnCode != 0) {
-        error_exit("Executing %s returned %d", applyPatchCommand.c_str(), returnCode);
-    }
-}
-
-void install_patch(const char* apkPath, const char* patchPath, int argc, const char** argv) {
-    const std::string kAgentApplyCommandPattern = "/data/local/tmp/deployagent apply %s %s -pm %s";
-    std::string packageName = get_packagename_from_apk(apkPath);
-
-    std::string patchDevicePath =
-            android::base::StringPrintf("%s%s.patch", kDeviceAgentPath, packageName.c_str());
-
-    std::vector<const char*> srcs{patchPath};
-    bool push_ok = do_sync_push(srcs, patchDevicePath.c_str(), false);
-    if (!push_ok) {
-        error_exit("Error pushing %s to %s returned", patchPath, patchDevicePath.c_str());
-    }
-
-    std::vector<unsigned char> applyOutputBuffer;
-    std::vector<unsigned char> applyErrorBuffer;
+    std::vector<unsigned char> apply_output_buffer;
+    std::vector<unsigned char> apply_error_buffer;
     std::string argsString;
 
     bool rSwitchPresent = false;
@@ -298,17 +317,42 @@
         argsString.append("-r");
     }
 
-    std::string applyPatchCommand =
-            android::base::StringPrintf(kAgentApplyCommandPattern.c_str(), packageName.c_str(),
-                                        patchDevicePath.c_str(), argsString.c_str());
-    int returnCode = send_shell_command(applyPatchCommand);
-    if (returnCode != 0) {
-        error_exit("Executing %s returned %d", applyPatchCommand.c_str(), returnCode);
+    std::string error;
+    std::string apply_patch_service_string =
+            android::base::StringPrintf(kAgentApplyServicePattern, argsString.c_str());
+    unique_fd fd{adb_connect(apply_patch_service_string, &error)};
+    if (fd < 0) {
+        error_exit("Executing %s returned %s", apply_patch_service_string.c_str(), error.c_str());
+    }
+    return fd;
+}
+
+unique_fd apply_patch_on_device(const char* output_path) {
+    REPORT_FUNC_TIME();
+    constexpr char kAgentApplyServicePattern[] = "shell:/data/local/tmp/deployagent apply - -o %s";
+
+    std::string error;
+    std::string apply_patch_service_string =
+            android::base::StringPrintf(kAgentApplyServicePattern, output_path);
+    unique_fd fd{adb_connect(apply_patch_service_string, &error)};
+    if (fd < 0) {
+        error_exit("Executing %s returned %s", apply_patch_service_string.c_str(), error.c_str());
+    }
+    return fd;
+}
+
+static void create_patch(const char* apk_path, APKMetaData metadata, borrowed_fd patch_fd) {
+    REPORT_FUNC_TIME();
+    DeployPatchGenerator generator(/*is_verbose=*/false);
+    bool success = generator.CreatePatch(apk_path, std::move(metadata), patch_fd);
+    if (!success) {
+        error_exit("Failed to create patch for %s", apk_path);
     }
 }
 
-bool find_package(const char* apkPath) {
-    const std::string findCommand =
-            "/data/local/tmp/deployagent find " + get_packagename_from_apk(apkPath);
-    return !send_shell_command(findCommand);
+int stream_patch(const char* apk_path, APKMetaData metadata, unique_fd patch_fd) {
+    create_patch(apk_path, std::move(metadata), patch_fd);
+
+    REPORT_FUNC_TIME();
+    return read_and_dump(patch_fd.get());
 }
diff --git a/adb/client/fastdeploy.h b/adb/client/fastdeploy.h
index 7b7f2ec..830aeb2 100644
--- a/adb/client/fastdeploy.h
+++ b/adb/client/fastdeploy.h
@@ -16,6 +16,11 @@
 
 #pragma once
 
+#include "adb_unique_fd.h"
+
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+#include <optional>
 #include <string>
 
 enum FastDeploy_AgentUpdateStrategy {
@@ -24,12 +29,11 @@
     FastDeploy_AgentUpdateDifferentVersion
 };
 
-void fastdeploy_set_local_agent(bool use_localagent);
+void fastdeploy_set_agent_update_strategy(FastDeploy_AgentUpdateStrategy agent_update_strategy);
 int get_device_api_level();
-void update_agent(FastDeploy_AgentUpdateStrategy agentUpdateStrategy);
-void extract_metadata(const char* apkPath, FILE* outputFp);
-void create_patch(const char* apkPath, const char* metadataPath, const char* patchPath);
-void apply_patch_on_device(const char* apkPath, const char* patchPath, const char* outputPath);
-void install_patch(const char* apkPath, const char* patchPath, int argc, const char** argv);
-std::string get_patch_path(const char* apkPath);
-bool find_package(const char* apkPath);
+
+std::optional<com::android::fastdeploy::APKMetaData> extract_metadata(const char* apk_path);
+unique_fd install_patch(int argc, const char** argv);
+unique_fd apply_patch_on_device(const char* output_path);
+int stream_patch(const char* apk_path, com::android::fastdeploy::APKMetaData metadata,
+                 unique_fd patch_fd);
diff --git a/adb/client/fastdeploycallbacks.cpp b/adb/client/fastdeploycallbacks.cpp
index 23a0aca..86ceaa1 100644
--- a/adb/client/fastdeploycallbacks.cpp
+++ b/adb/client/fastdeploycallbacks.cpp
@@ -49,35 +49,7 @@
 int capture_shell_command(const char* command, std::vector<char>* outBuffer,
                           std::vector<char>* errBuffer) {
     DeployAgentBufferCallback cb(outBuffer, errBuffer);
-    return send_shell_command(command, false, &cb);
-}
-
-DeployAgentFileCallback::DeployAgentFileCallback(FILE* outputFile, std::vector<char>* errBuffer) {
-    mpOutFile = outputFile;
-    mpErrBuffer = errBuffer;
-    mBytesWritten = 0;
-}
-
-void DeployAgentFileCallback::OnStdout(const char* buffer, int length) {
-    if (mpOutFile != NULL) {
-        int bytes_written = fwrite(buffer, 1, length, mpOutFile);
-        if (bytes_written != length) {
-            printf("Write error %d\n", bytes_written);
-        }
-        mBytesWritten += bytes_written;
-    }
-}
-
-void DeployAgentFileCallback::OnStderr(const char* buffer, int length) {
-    appendBuffer(mpErrBuffer, buffer, length);
-}
-
-int DeployAgentFileCallback::Done(int status) {
-    return status;
-}
-
-int DeployAgentFileCallback::getBytesWritten() {
-    return mBytesWritten;
+    return send_shell_command(command, /*disable_shell_protocol=*/false, &cb);
 }
 
 DeployAgentBufferCallback::DeployAgentBufferCallback(std::vector<char>* outBuffer,
diff --git a/adb/client/fastdeploycallbacks.h b/adb/client/fastdeploycallbacks.h
index 7e049c5..4a6fb99 100644
--- a/adb/client/fastdeploycallbacks.h
+++ b/adb/client/fastdeploycallbacks.h
@@ -17,23 +17,6 @@
 #pragma once
 
 #include <vector>
-#include "commandline.h"
-
-class DeployAgentFileCallback : public StandardStreamsCallbackInterface {
-  public:
-    DeployAgentFileCallback(FILE* outputFile, std::vector<char>* errBuffer);
-
-    virtual void OnStdout(const char* buffer, int length);
-    virtual void OnStderr(const char* buffer, int length);
-    virtual int Done(int status);
-
-    int getBytesWritten();
-
-  private:
-    FILE* mpOutFile;
-    std::vector<char>* mpErrBuffer;
-    int mBytesWritten;
-};
 
 int capture_shell_command(const char* command, std::vector<char>* outBuffer,
                           std::vector<char>* errBuffer);
diff --git a/adb/fastdeploy/Android.bp b/adb/fastdeploy/Android.bp
index 95e1a28..245d52a 100644
--- a/adb/fastdeploy/Android.bp
+++ b/adb/fastdeploy/Android.bp
@@ -13,15 +13,76 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-java_binary {
-    name: "deployagent",
+java_library {
+    name: "deployagent_lib",
     sdk_version: "24",
-    srcs: ["deployagent/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
-    static_libs: ["apkzlib_zip"],
+    srcs: [
+        "deployagent/src/**/*.java",
+        "proto/**/*.proto",
+    ],
     proto: {
         type: "lite",
     },
+}
+
+java_binary {
+    name: "deployagent",
+    static_libs: [
+        "deployagent_lib",
+    ],
     dex_preopt: {
         enabled: false,
     }
-}
\ No newline at end of file
+}
+
+android_test {
+    name: "FastDeployTests",
+
+    manifest: "AndroidManifest.xml",
+
+    srcs: [
+        "deployagent/test/com/android/fastdeploy/ApkArchiveTest.java",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "deployagent_lib",
+        "mockito-target-inline-minus-junit4",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+
+    data: [
+        "testdata/sample.apk",
+        "testdata/sample.cd",
+    ],
+
+    optimize: {
+        enabled: false,
+    },
+}
+
+java_test_host {
+    name: "FastDeployHostTests",
+    srcs: [
+        "deployagent/test/com/android/fastdeploy/FastDeployTest.java",
+    ],
+    data: [
+        "testdata/helloworld5.apk",
+        "testdata/helloworld7.apk",
+    ],
+    libs: [
+        "compatibility-host-util",
+        "cts-tradefed",
+        "tradefed",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+}
diff --git a/adb/fastdeploy/AndroidManifest.xml b/adb/fastdeploy/AndroidManifest.xml
new file mode 100644
index 0000000..89dc745
--- /dev/null
+++ b/adb/fastdeploy/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.fastdeploytests">
+
+    <application android:testOnly="true"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.fastdeploytests"
+        android:label="FastDeploy Tests" />
+</manifest>
\ No newline at end of file
diff --git a/adb/fastdeploy/AndroidTest.xml b/adb/fastdeploy/AndroidTest.xml
new file mode 100644
index 0000000..24a72bc
--- /dev/null
+++ b/adb/fastdeploy/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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
+  -->
+<configuration description="Runs Device Tests for FastDeploy.">
+    <option name="test-suite-tag" value="FastDeployTests"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="install-arg" value="-t"/>
+        <option name="test-file-name" value="FastDeployTests.apk"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="false" />
+        <option name="push-file" key="sample.apk" value="/data/local/tmp/FastDeployTests/sample.apk" />
+        <option name="push-file" key="sample.cd" value="/data/local/tmp/FastDeployTests/sample.cd" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.fastdeploytests"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="FastDeployHostTests.jar" />
+    </test>
+</configuration>
diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/ApkArchive.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/ApkArchive.java
new file mode 100644
index 0000000..31e0502
--- /dev/null
+++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/ApkArchive.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.fastdeploy;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+
+/**
+ * Extremely light-weight APK parser class.
+ * Aware of Central Directory, Local File Headers and Signature.
+ * No Zip64 support yet.
+ */
+public final class ApkArchive {
+    private static final String TAG = "ApkArchive";
+
+    // Central Directory constants.
+    private static final int EOCD_SIGNATURE = 0x06054b50;
+    private static final int EOCD_MIN_SIZE = 22;
+    private static final long EOCD_MAX_SIZE = 65_535L + EOCD_MIN_SIZE;
+
+    private static final int CD_ENTRY_HEADER_SIZE_BYTES = 22;
+    private static final int CD_LOCAL_FILE_HEADER_SIZE_OFFSET = 12;
+
+    // Signature constants.
+    private static final int EOSIGNATURE_SIZE = 24;
+
+    public final static class Dump {
+        final byte[] cd;
+        final byte[] signature;
+
+        Dump(byte[] cd, byte[] signature) {
+            this.cd = cd;
+            this.signature = signature;
+        }
+    }
+
+    final static class Location {
+        final long offset;
+        final long size;
+
+        public Location(long offset, long size) {
+            this.offset = offset;
+            this.size = size;
+        }
+    }
+
+    private final RandomAccessFile mFile;
+    private final FileChannel mChannel;
+
+    public ApkArchive(File apk) throws IOException {
+        mFile = new RandomAccessFile(apk, "r");
+        mChannel = mFile.getChannel();
+    }
+
+    /**
+     * Extract the APK metadata: content of Central Directory and Signature.
+     *
+     * @return raw content from APK representing CD and Signature data.
+     */
+    public Dump extractMetadata() throws IOException {
+        Location cdLoc = getCDLocation();
+        byte[] cd = readMetadata(cdLoc);
+
+        byte[] signature = null;
+        Location sigLoc = getSignatureLocation(cdLoc.offset);
+        if (sigLoc != null) {
+            signature = readMetadata(sigLoc);
+            long size = ByteBuffer.wrap(signature).order(ByteOrder.LITTLE_ENDIAN).getLong();
+            if (sigLoc.size != size) {
+                Log.e(TAG, "Mismatching signature sizes: " + sigLoc.size + " != " + size);
+                signature = null;
+            }
+        }
+
+        return new Dump(cd, signature);
+    }
+
+    private long findEndOfCDRecord() throws IOException {
+        final long fileSize = mChannel.size();
+        int sizeToRead = Math.toIntExact(Math.min(fileSize, EOCD_MAX_SIZE));
+        final long readOffset = fileSize - sizeToRead;
+        ByteBuffer buffer = mChannel.map(FileChannel.MapMode.READ_ONLY, readOffset,
+                sizeToRead).order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.position(sizeToRead - EOCD_MIN_SIZE);
+        while (true) {
+            int signature = buffer.getInt(); // Read 4 bytes.
+            if (signature == EOCD_SIGNATURE) {
+                return readOffset + buffer.position() - 4;
+            }
+            if (buffer.position() == 4) {
+                break;
+            }
+            buffer.position(buffer.position() - Integer.BYTES - 1); // Backtrack 5 bytes.
+        }
+
+        return -1L;
+    }
+
+    private Location findCDRecord(ByteBuffer buf) {
+        if (buf.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+        if (buf.remaining() < CD_ENTRY_HEADER_SIZE_BYTES) {
+            throw new IllegalArgumentException(
+                    "Input too short. Need at least " + CD_ENTRY_HEADER_SIZE_BYTES
+                            + " bytes, available: " + buf.remaining() + "bytes.");
+        }
+
+        int originalPosition = buf.position();
+        int recordSignature = buf.getInt();
+        if (recordSignature != EOCD_SIGNATURE) {
+            throw new IllegalArgumentException(
+                    "Not a Central Directory record. Signature: 0x"
+                            + Long.toHexString(recordSignature & 0xffffffffL));
+        }
+
+        buf.position(originalPosition + CD_LOCAL_FILE_HEADER_SIZE_OFFSET);
+        long size = buf.getInt() & 0xffffffffL;
+        long offset = buf.getInt() & 0xffffffffL;
+        return new Location(offset, size);
+    }
+
+    // Retrieve the location of the Central Directory Record.
+    Location getCDLocation() throws IOException {
+        long eocdRecord = findEndOfCDRecord();
+        if (eocdRecord < 0) {
+            throw new IllegalArgumentException("Unable to find End of Central Directory record.");
+        }
+
+        Location location = findCDRecord(mChannel.map(FileChannel.MapMode.READ_ONLY, eocdRecord,
+                CD_ENTRY_HEADER_SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN));
+        if (location == null) {
+            throw new IllegalArgumentException("Unable to find Central Directory File Header.");
+        }
+
+        return location;
+    }
+
+    // Retrieve the location of the signature block starting from Central
+    // Directory Record or null if signature is not found.
+    Location getSignatureLocation(long cdRecordOffset) throws IOException {
+        long signatureOffset = cdRecordOffset - EOSIGNATURE_SIZE;
+        if (signatureOffset < 0) {
+            Log.e(TAG, "Unable to find Signature.");
+            return null;
+        }
+
+        ByteBuffer signature = mChannel.map(FileChannel.MapMode.READ_ONLY, signatureOffset,
+                EOSIGNATURE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+
+        long size = signature.getLong();
+
+        byte[] sign = new byte[16];
+        signature.get(sign);
+        String signAsString = new String(sign);
+        if (!"APK Sig Block 42".equals(signAsString)) {
+            Log.e(TAG, "Signature magic does not match: " + signAsString);
+            return null;
+        }
+
+        long offset = cdRecordOffset - size - 8;
+
+        return new Location(offset, size);
+    }
+
+    private byte[] readMetadata(Location loc) throws IOException {
+        byte[] payload = new byte[(int) loc.size];
+        ByteBuffer buffer = mChannel.map(FileChannel.MapMode.READ_ONLY, loc.offset, loc.size);
+        buffer.get(payload);
+        return payload;
+    }
+}
diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
index a8103c4..3812307 100644
--- a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
+++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
@@ -24,18 +24,22 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-import java.io.OutputStream;
 import java.io.RandomAccessFile;
-import java.util.Set;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.WritableByteChannel;
 
+import com.android.fastdeploy.PatchFormatException;
+import com.android.fastdeploy.ApkArchive;
+import com.android.fastdeploy.APKDump;
 import com.android.fastdeploy.APKMetaData;
 import com.android.fastdeploy.PatchUtils;
 
+import com.google.protobuf.ByteString;
+
 public final class DeployAgent {
     private static final int BUFFER_SIZE = 128 * 1024;
-    private static final int AGENT_VERSION = 0x00000002;
+    private static final int AGENT_VERSION = 0x00000003;
 
     public static void main(String[] args) {
         int exitCode = 0;
@@ -45,68 +49,70 @@
             }
 
             String commandString = args[0];
+            switch (commandString) {
+                case "dump": {
+                    if (args.length != 3) {
+                        showUsage(1);
+                    }
 
-            if (commandString.equals("extract")) {
-                if (args.length != 2) {
-                    showUsage(1);
-                }
-
-                String packageName = args[1];
-                extractMetaData(packageName);
-            } else if (commandString.equals("find")) {
-                if (args.length != 2) {
-                    showUsage(1);
-                }
-
-                String packageName = args[1];
-                if (getFilenameFromPackageName(packageName) == null) {
-                    exitCode = 3;
-                }
-            } else if (commandString.equals("apply")) {
-                if (args.length < 4) {
-                    showUsage(1);
-                }
-
-                String packageName = args[1];
-                String patchPath = args[2];
-                String outputParam = args[3];
-
-                InputStream deltaInputStream = null;
-                if (patchPath.compareTo("-") == 0) {
-                    deltaInputStream = System.in;
-                } else {
-                    deltaInputStream = new FileInputStream(patchPath);
-                }
-
-                if (outputParam.equals("-o")) {
-                    OutputStream outputStream = null;
-                    if (args.length > 4) {
-                        String outputPath = args[4];
-                        if (!outputPath.equals("-")) {
-                            outputStream = new FileOutputStream(outputPath);
+                    String requiredVersion = args[1];
+                    if (AGENT_VERSION == Integer.parseInt(requiredVersion)) {
+                        String packageName = args[2];
+                        String packagePath = getFilenameFromPackageName(packageName);
+                        if (packagePath != null) {
+                            dumpApk(packageName, packagePath);
+                        } else {
+                            exitCode = 3;
                         }
+                    } else {
+                        System.out.printf("0x%08X\n", AGENT_VERSION);
+                        exitCode = 4;
                     }
-                    if (outputStream == null) {
-                        outputStream = System.out;
-                    }
-                    File deviceFile = getFileFromPackageName(packageName);
-                    writePatchToStream(
-                            new RandomAccessFile(deviceFile, "r"), deltaInputStream, outputStream);
-                } else if (outputParam.equals("-pm")) {
-                    String[] sessionArgs = null;
-                    if (args.length > 4) {
-                        int numSessionArgs = args.length-4;
-                        sessionArgs = new String[numSessionArgs];
-                        for (int i=0 ; i<numSessionArgs ; i++) {
-                            sessionArgs[i] = args[i+4];
-                        }
-                    }
-                    exitCode = applyPatch(packageName, deltaInputStream, sessionArgs);
+                    break;
                 }
-            } else if (commandString.equals("version")) {
-                System.out.printf("0x%08X\n", AGENT_VERSION);
-            } else {
-                showUsage(1);
+                case "apply": {
+                    if (args.length < 3) {
+                        showUsage(1);
+                    }
+
+                    String patchPath = args[1];
+                    String outputParam = args[2];
+
+                    InputStream deltaInputStream = null;
+                    if (patchPath.compareTo("-") == 0) {
+                        deltaInputStream = System.in;
+                    } else {
+                        deltaInputStream = new FileInputStream(patchPath);
+                    }
+
+                    if (outputParam.equals("-o")) {
+                        OutputStream outputStream = null;
+                        if (args.length > 3) {
+                            String outputPath = args[3];
+                            if (!outputPath.equals("-")) {
+                                outputStream = new FileOutputStream(outputPath);
+                            }
+                        }
+                        if (outputStream == null) {
+                            outputStream = System.out;
+                        }
+                        writePatchToStream(deltaInputStream, outputStream);
+                    } else if (outputParam.equals("-pm")) {
+                        String[] sessionArgs = null;
+                        if (args.length > 3) {
+                            int numSessionArgs = args.length - 3;
+                            sessionArgs = new String[numSessionArgs];
+                            for (int i = 0; i < numSessionArgs; i++) {
+                                sessionArgs[i] = args[i + 3];
+                            }
+                        }
+                        exitCode = applyPatch(deltaInputStream, sessionArgs);
+                    }
+                    break;
+                }
+                default:
+                    showUsage(1);
+                    break;
             }
         } catch (Exception e) {
             System.err.println("Error: " + e);
@@ -118,16 +124,16 @@
 
     private static void showUsage(int exitCode) {
         System.err.println(
-            "usage: deployagent <command> [<args>]\n\n" +
-            "commands:\n" +
-            "version                             get the version\n" +
-            "find PKGNAME                        return zero if package found, else non-zero\n" +
-            "extract PKGNAME                     extract an installed package's metadata\n" +
-            "apply PKGNAME PATCHFILE [-o|-pm]    apply a patch from PATCHFILE (- for stdin) to an installed package\n" +
-            " -o <FILE> directs output to FILE, default or - for stdout\n" +
-            " -pm <ARGS> directs output to package manager, passes <ARGS> to 'pm install-create'\n"
-            );
-
+                "usage: deployagent <command> [<args>]\n\n" +
+                        "commands:\n" +
+                        "dump VERSION PKGNAME  dump info for an installed package given that " +
+                        "VERSION equals current agent's version\n" +
+                        "apply PATCHFILE [-o|-pm]    apply a patch from PATCHFILE " +
+                        "(- for stdin) to an installed package\n" +
+                        " -o <FILE> directs output to FILE, default or - for stdout\n" +
+                        " -pm <ARGS> directs output to package manager, passes <ARGS> to " +
+                        "'pm install-create'\n"
+        );
         System.exit(exitCode);
     }
 
@@ -162,32 +168,34 @@
                 }
                 int equalsIndex = line.lastIndexOf(packageSuffix);
                 String fileName =
-                    line.substring(packageIndex + packagePrefix.length(), equalsIndex);
+                        line.substring(packageIndex + packagePrefix.length(), equalsIndex);
                 return fileName;
             }
         }
         return null;
     }
 
-    private static File getFileFromPackageName(String packageName) throws IOException {
-        String filename = getFilenameFromPackageName(packageName);
-        if (filename == null) {
-            // Should not happen (function is only called when we know the package exists)
-            throw new IOException("package not found");
-        }
-        return new File(filename);
-    }
+    private static void dumpApk(String packageName, String packagePath) throws IOException {
+        File apk = new File(packagePath);
+        ApkArchive.Dump dump = new ApkArchive(apk).extractMetadata();
 
-    private static void extractMetaData(String packageName) throws IOException {
-        File apkFile = getFileFromPackageName(packageName);
-        APKMetaData apkMetaData = PatchUtils.getAPKMetaData(apkFile);
-        apkMetaData.writeTo(System.out);
+        APKDump.Builder apkDumpBuilder = APKDump.newBuilder();
+        apkDumpBuilder.setName(packageName);
+        if (dump.cd != null) {
+            apkDumpBuilder.setCd(ByteString.copyFrom(dump.cd));
+        }
+        if (dump.signature != null) {
+            apkDumpBuilder.setSignature(ByteString.copyFrom(dump.signature));
+        }
+        apkDumpBuilder.setAbsolutePath(apk.getAbsolutePath());
+
+        apkDumpBuilder.build().writeTo(System.out);
     }
 
     private static int createInstallSession(String[] args) throws IOException {
         StringBuilder commandBuilder = new StringBuilder();
         commandBuilder.append("pm install-create ");
-        for (int i=0 ; args != null && i<args.length ; i++) {
+        for (int i = 0; args != null && i < args.length; i++) {
             commandBuilder.append(args[i] + " ");
         }
 
@@ -199,7 +207,8 @@
         String successLineEnd = "]";
         while ((line = reader.readLine()) != null) {
             if (line.startsWith(successLineStart) && line.endsWith(successLineEnd)) {
-                return Integer.parseInt(line.substring(successLineStart.length(), line.lastIndexOf(successLineEnd)));
+                return Integer.parseInt(line.substring(successLineStart.length(),
+                        line.lastIndexOf(successLineEnd)));
             }
         }
 
@@ -213,16 +222,15 @@
         return p.exitValue();
     }
 
-    private static int applyPatch(String packageName, InputStream deltaStream, String[] sessionArgs)
+    private static int applyPatch(InputStream deltaStream, String[] sessionArgs)
             throws IOException, PatchFormatException {
-        File deviceFile = getFileFromPackageName(packageName);
         int sessionId = createInstallSession(sessionArgs);
         if (sessionId < 0) {
             System.err.println("PM Create Session Failed");
             return -1;
         }
 
-        int writeExitCode = writePatchedDataToSession(new RandomAccessFile(deviceFile, "r"), deltaStream, sessionId);
+        int writeExitCode = writePatchedDataToSession(deltaStream, sessionId);
         if (writeExitCode == 0) {
             return commitInstallSession(sessionId);
         } else {
@@ -230,84 +238,94 @@
         }
     }
 
-    private static long writePatchToStream(RandomAccessFile oldData, InputStream patchData,
-        OutputStream outputStream) throws IOException, PatchFormatException {
+    private static long writePatchToStream(InputStream patchData,
+            OutputStream outputStream) throws IOException, PatchFormatException {
         long newSize = readPatchHeader(patchData);
-        long bytesWritten = writePatchedDataToStream(oldData, newSize, patchData, outputStream);
+        long bytesWritten = writePatchedDataToStream(newSize, patchData, outputStream);
         outputStream.flush();
         if (bytesWritten != newSize) {
             throw new PatchFormatException(String.format(
-                "output size mismatch (expected %ld but wrote %ld)", newSize, bytesWritten));
+                    "output size mismatch (expected %ld but wrote %ld)", newSize, bytesWritten));
         }
         return bytesWritten;
     }
 
     private static long readPatchHeader(InputStream patchData)
-        throws IOException, PatchFormatException {
+            throws IOException, PatchFormatException {
         byte[] signatureBuffer = new byte[PatchUtils.SIGNATURE.length()];
         try {
-            PatchUtils.readFully(patchData, signatureBuffer, 0, signatureBuffer.length);
+            PatchUtils.readFully(patchData, signatureBuffer);
         } catch (IOException e) {
             throw new PatchFormatException("truncated signature");
         }
 
-        String signature = new String(signatureBuffer, 0, signatureBuffer.length, "US-ASCII");
+        String signature = new String(signatureBuffer);
         if (!PatchUtils.SIGNATURE.equals(signature)) {
             throw new PatchFormatException("bad signature");
         }
 
-        long newSize = PatchUtils.readBsdiffLong(patchData);
-        if (newSize < 0 || newSize > Integer.MAX_VALUE) {
-            throw new PatchFormatException("bad newSize");
+        long newSize = PatchUtils.readLELong(patchData);
+        if (newSize < 0) {
+            throw new PatchFormatException("bad newSize: " + newSize);
         }
 
         return newSize;
     }
 
     // Note that this function assumes patchData has been seek'ed to the start of the delta stream
-    // (i.e. the signature has already been read by readPatchHeader). For a stream that points to the
-    // start of a patch file call writePatchToStream
-    private static long writePatchedDataToStream(RandomAccessFile oldData, long newSize,
-        InputStream patchData, OutputStream outputStream) throws IOException {
+    // (i.e. the signature has already been read by readPatchHeader). For a stream that points to
+    // the start of a patch file call writePatchToStream
+    private static long writePatchedDataToStream(long newSize, InputStream patchData,
+            OutputStream outputStream) throws IOException {
+        String deviceFile = PatchUtils.readString(patchData);
+        RandomAccessFile oldDataFile = new RandomAccessFile(deviceFile, "r");
+        FileChannel oldData = oldDataFile.getChannel();
+
+        WritableByteChannel newData = Channels.newChannel(outputStream);
+
         long newDataBytesWritten = 0;
         byte[] buffer = new byte[BUFFER_SIZE];
 
         while (newDataBytesWritten < newSize) {
-            long copyLen = PatchUtils.readFormattedLong(patchData);
-            if (copyLen > 0) {
-                PatchUtils.pipe(patchData, outputStream, buffer, (int) copyLen);
+            long newDataLen = PatchUtils.readLELong(patchData);
+            if (newDataLen > 0) {
+                PatchUtils.pipe(patchData, outputStream, buffer, newDataLen);
             }
 
-            long oldDataOffset = PatchUtils.readFormattedLong(patchData);
-            long oldDataLen = PatchUtils.readFormattedLong(patchData);
-            oldData.seek(oldDataOffset);
-            if (oldDataLen > 0) {
-                PatchUtils.pipe(oldData, outputStream, buffer, (int) oldDataLen);
+            long oldDataOffset = PatchUtils.readLELong(patchData);
+            long oldDataLen = PatchUtils.readLELong(patchData);
+            if (oldDataLen >= 0) {
+                long offset = oldDataOffset;
+                long len = oldDataLen;
+                while (len > 0) {
+                    long chunkLen = Math.min(len, 1024*1024*1024);
+                    oldData.transferTo(offset, chunkLen, newData);
+                    offset += chunkLen;
+                    len -= chunkLen;
+                }
             }
-            newDataBytesWritten += copyLen + oldDataLen;
+            newDataBytesWritten += newDataLen + oldDataLen;
         }
 
         return newDataBytesWritten;
     }
 
-    private static int writePatchedDataToSession(RandomAccessFile oldData, InputStream patchData, int sessionId)
+    private static int writePatchedDataToSession(InputStream patchData, int sessionId)
             throws IOException, PatchFormatException {
         try {
             Process p;
             long newSize = readPatchHeader(patchData);
-            StringBuilder commandBuilder = new StringBuilder();
-            commandBuilder.append(String.format("pm install-write -S %d %d -- -", newSize, sessionId));
-
-            String command = commandBuilder.toString();
+            String command = String.format("pm install-write -S %d %d -- -", newSize, sessionId);
             p = Runtime.getRuntime().exec(command);
 
             OutputStream sessionOutputStream = p.getOutputStream();
-            long bytesWritten = writePatchedDataToStream(oldData, newSize, patchData, sessionOutputStream);
+            long bytesWritten = writePatchedDataToStream(newSize, patchData, sessionOutputStream);
             sessionOutputStream.flush();
             p.waitFor();
             if (bytesWritten != newSize) {
                 throw new PatchFormatException(
-                        String.format("output size mismatch (expected %d but wrote %)", newSize, bytesWritten));
+                        String.format("output size mismatch (expected %d but wrote %)", newSize,
+                                bytesWritten));
             }
             return p.exitValue();
         } catch (InterruptedException e) {
diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/PatchFormatException.java
similarity index 100%
rename from adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java
rename to adb/fastdeploy/deployagent/src/com/android/fastdeploy/PatchFormatException.java
diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/PatchUtils.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/PatchUtils.java
new file mode 100644
index 0000000..54be26f
--- /dev/null
+++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/PatchUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package com.android.fastdeploy;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class PatchUtils {
+    public static final String SIGNATURE = "FASTDEPLOY";
+
+    /**
+     * Reads a 64-bit signed integer in Little Endian format from the specified {@link
+     * DataInputStream}.
+     *
+     * @param in the stream to read from.
+     */
+    static long readLELong(InputStream in) throws IOException {
+        byte[] buffer = new byte[Long.BYTES];
+        readFully(in, buffer);
+        ByteBuffer buf = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
+        return buf.getLong();
+    }
+
+    static String readString(InputStream in) throws IOException {
+        int size = (int) readLELong(in);
+        byte[] buffer = new byte[size];
+        readFully(in, buffer);
+        return new String(buffer);
+    }
+
+    static void readFully(final InputStream in, final byte[] destination, final int startAt,
+            final int numBytes) throws IOException {
+        int numRead = 0;
+        while (numRead < numBytes) {
+            int readNow = in.read(destination, startAt + numRead, numBytes - numRead);
+            if (readNow == -1) {
+                throw new IOException("truncated input stream");
+            }
+            numRead += readNow;
+        }
+    }
+
+    static void readFully(final InputStream in, final byte[] destination) throws IOException {
+        readFully(in, destination, 0, destination.length);
+    }
+
+    static void pipe(final InputStream in, final OutputStream out, final byte[] buffer,
+            long copyLength) throws IOException {
+        while (copyLength > 0) {
+            int maxCopy = (int) Math.min(buffer.length, copyLength);
+            readFully(in, buffer, 0, maxCopy);
+            out.write(buffer, 0, maxCopy);
+            copyLength -= maxCopy;
+        }
+    }
+}
diff --git a/adb/fastdeploy/deployagent/test/com/android/fastdeploy/ApkArchiveTest.java b/adb/fastdeploy/deployagent/test/com/android/fastdeploy/ApkArchiveTest.java
new file mode 100644
index 0000000..7c2468f
--- /dev/null
+++ b/adb/fastdeploy/deployagent/test/com/android/fastdeploy/ApkArchiveTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.fastdeploy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.fastdeploy.ApkArchive;
+
+import java.io.File;
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ApkArchiveTest {
+    private static final File SAMPLE_APK = new File("/data/local/tmp/FastDeployTests/sample.apk");
+    private static final File WRONG_APK = new File("/data/local/tmp/FastDeployTests/sample.cd");
+
+    @Test
+    public void testApkArchiveSizes() throws IOException {
+        ApkArchive archive = new ApkArchive(SAMPLE_APK);
+
+        ApkArchive.Location cdLoc = archive.getCDLocation();
+        assertNotEquals(cdLoc, null);
+        assertEquals(cdLoc.offset, 2044145);
+        assertEquals(cdLoc.size, 49390);
+
+        // Check that block can be retrieved
+        ApkArchive.Location sigLoc = archive.getSignatureLocation(cdLoc.offset);
+        assertNotEquals(sigLoc, null);
+        assertEquals(sigLoc.offset, 2040049);
+        assertEquals(sigLoc.size, 4088);
+    }
+
+    @Test
+    public void testApkArchiveDump() throws IOException {
+        ApkArchive archive = new ApkArchive(SAMPLE_APK);
+
+        ApkArchive.Dump dump = archive.extractMetadata();
+        assertNotEquals(dump, null);
+        assertNotEquals(dump.cd, null);
+        assertNotEquals(dump.signature, null);
+        assertEquals(dump.cd.length, 49390);
+        assertEquals(dump.signature.length, 4088);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testApkArchiveDumpWrongApk() throws IOException {
+        ApkArchive archive = new ApkArchive(WRONG_APK);
+
+        archive.extractMetadata();
+    }
+}
diff --git a/adb/fastdeploy/deployagent/test/com/android/fastdeploy/FastDeployTest.java b/adb/fastdeploy/deployagent/test/com/android/fastdeploy/FastDeployTest.java
new file mode 100644
index 0000000..ef6ccae
--- /dev/null
+++ b/adb/fastdeploy/deployagent/test/com/android/fastdeploy/FastDeployTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+package com.android.fastdeploy;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class FastDeployTest extends BaseHostJUnit4Test {
+
+    private static final String TEST_APP_PACKAGE = "com.example.helloworld";
+    private static final String TEST_APK5_NAME = "helloworld5.apk";
+    private static final String TEST_APK7_NAME = "helloworld7.apk";
+
+    private String mTestApk5Path;
+    private String mTestApk7Path;
+
+    @Before
+    public void setUp() throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        getDevice().uninstallPackage(TEST_APP_PACKAGE);
+        mTestApk5Path = buildHelper.getTestFile(TEST_APK5_NAME).getAbsolutePath();
+        mTestApk7Path = buildHelper.getTestFile(TEST_APK7_NAME).getAbsolutePath();
+    }
+
+    @Test
+    public void testAppInstalls() throws Exception {
+        fastInstallPackage(mTestApk5Path);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        getDevice().uninstallPackage(TEST_APP_PACKAGE);
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testAppPatch() throws Exception {
+        fastInstallPackage(mTestApk5Path);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        fastInstallPackage(mTestApk7Path);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        getDevice().uninstallPackage(TEST_APP_PACKAGE);
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    private boolean isAppInstalled(String packageName) throws DeviceNotAvailableException {
+        final String commandResult = getDevice().executeShellCommand("pm list packages");
+        final int prefixLength = "package:".length();
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .anyMatch(line -> line.substring(prefixLength).equals(packageName));
+    }
+
+    // Mostly copied from PkgInstallSignatureVerificationTest.java.
+    private String fastInstallPackage(String apkPath)
+            throws IOException, DeviceNotAvailableException {
+        return getDevice().executeAdbCommand("install", "-t", "--fastdeploy", "--force-agent",
+                apkPath);
+    }
+}
+
+
diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
deleted file mode 100644
index c60f9a6..0000000
--- a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
+++ /dev/null
@@ -1,186 +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.
- */
-
-package com.android.fastdeploy;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-
-import com.android.tools.build.apkzlib.zip.ZFile;
-import com.android.tools.build.apkzlib.zip.ZFileOptions;
-import com.android.tools.build.apkzlib.zip.StoredEntry;
-import com.android.tools.build.apkzlib.zip.StoredEntryType;
-import com.android.tools.build.apkzlib.zip.CentralDirectoryHeaderCompressInfo;
-import com.android.tools.build.apkzlib.zip.CentralDirectoryHeader;
-
-import com.android.fastdeploy.APKMetaData;
-import com.android.fastdeploy.APKEntry;
-
-class PatchUtils {
-    private static final long NEGATIVE_MASK = 1L << 63;
-    private static final long NEGATIVE_LONG_SIGN_MASK = 1L << 63;
-    public static final String SIGNATURE = "FASTDEPLOY";
-
-    private static long getOffsetFromEntry(StoredEntry entry) {
-        return entry.getCentralDirectoryHeader().getOffset() + entry.getLocalHeaderSize();
-    }
-
-    public static APKMetaData getAPKMetaData(File apkFile) throws IOException {
-        APKMetaData.Builder apkEntriesBuilder = APKMetaData.newBuilder();
-        ZFileOptions options = new ZFileOptions();
-        ZFile zFile = new ZFile(apkFile, options);
-
-        ArrayList<StoredEntry> metaDataEntries = new ArrayList<StoredEntry>();
-
-        for (StoredEntry entry : zFile.entries()) {
-            if (entry.getType() != StoredEntryType.FILE) {
-                continue;
-            }
-            metaDataEntries.add(entry);
-        }
-
-        Collections.sort(metaDataEntries, new Comparator<StoredEntry>() {
-            private long getOffsetFromEntry(StoredEntry entry) {
-                return PatchUtils.getOffsetFromEntry(entry);
-            }
-
-            @Override
-            public int compare(StoredEntry lhs, StoredEntry rhs) {
-                // -1 - less than, 1 - greater than, 0 - equal, all inversed for descending
-                return Long.compare(getOffsetFromEntry(lhs), getOffsetFromEntry(rhs));
-            }
-        });
-
-        for (StoredEntry entry : metaDataEntries) {
-            CentralDirectoryHeader cdh = entry.getCentralDirectoryHeader();
-            CentralDirectoryHeaderCompressInfo cdhci = cdh.getCompressionInfoWithWait();
-
-            APKEntry.Builder entryBuilder = APKEntry.newBuilder();
-            entryBuilder.setCrc32(cdh.getCrc32());
-            entryBuilder.setFileName(cdh.getName());
-            entryBuilder.setCompressedSize(cdhci.getCompressedSize());
-            entryBuilder.setUncompressedSize(cdh.getUncompressedSize());
-            entryBuilder.setDataOffset(getOffsetFromEntry(entry));
-
-            apkEntriesBuilder.addEntries(entryBuilder);
-            apkEntriesBuilder.build();
-        }
-        return apkEntriesBuilder.build();
-    }
-
-    /**
-     * Writes a 64-bit signed integer to the specified {@link OutputStream}. The least significant
-     * byte is written first and the most significant byte is written last.
-     * @param value the value to write
-     * @param outputStream the stream to write to
-     */
-    static void writeFormattedLong(final long value, OutputStream outputStream) throws IOException {
-        long y = value;
-        if (y < 0) {
-            y = (-y) | NEGATIVE_MASK;
-        }
-
-        for (int i = 0; i < 8; ++i) {
-            outputStream.write((byte) (y & 0xff));
-            y >>>= 8;
-        }
-    }
-
-    /**
-     * Reads a 64-bit signed integer written by {@link #writeFormattedLong(long, OutputStream)} from
-     * the specified {@link InputStream}.
-     * @param inputStream the stream to read from
-     */
-    static long readFormattedLong(InputStream inputStream) throws IOException {
-        long result = 0;
-        for (int bitshift = 0; bitshift < 64; bitshift += 8) {
-            result |= ((long) inputStream.read()) << bitshift;
-        }
-
-        if ((result - NEGATIVE_MASK) > 0) {
-            result = (result & ~NEGATIVE_MASK) * -1;
-        }
-        return result;
-    }
-
-    static final long readBsdiffLong(InputStream in) throws PatchFormatException, IOException {
-        long result = 0;
-        for (int bitshift = 0; bitshift < 64; bitshift += 8) {
-            result |= ((long) in.read()) << bitshift;
-        }
-
-        if (result == NEGATIVE_LONG_SIGN_MASK) {
-            // "Negative zero", which is valid in signed-magnitude format.
-            // NB: No sane patch generator should ever produce such a value.
-            throw new PatchFormatException("read negative zero");
-        }
-
-        if ((result & NEGATIVE_LONG_SIGN_MASK) != 0) {
-            result = -(result & ~NEGATIVE_LONG_SIGN_MASK);
-        }
-
-        return result;
-    }
-
-    static void readFully(final InputStream in, final byte[] destination, final int startAt,
-        final int numBytes) throws IOException {
-        int numRead = 0;
-        while (numRead < numBytes) {
-            int readNow = in.read(destination, startAt + numRead, numBytes - numRead);
-            if (readNow == -1) {
-                throw new IOException("truncated input stream");
-            }
-            numRead += readNow;
-        }
-    }
-
-    static void pipe(final InputStream in, final OutputStream out, final byte[] buffer,
-        long copyLength) throws IOException {
-        while (copyLength > 0) {
-            int maxCopy = Math.min(buffer.length, (int) copyLength);
-            readFully(in, buffer, 0, maxCopy);
-            out.write(buffer, 0, maxCopy);
-            copyLength -= maxCopy;
-        }
-    }
-
-    static void pipe(final RandomAccessFile in, final OutputStream out, final byte[] buffer,
-        long copyLength) throws IOException {
-        while (copyLength > 0) {
-            int maxCopy = Math.min(buffer.length, (int) copyLength);
-            in.readFully(buffer, 0, maxCopy);
-            out.write(buffer, 0, maxCopy);
-            copyLength -= maxCopy;
-        }
-    }
-
-    static void fill(byte value, final OutputStream out, final byte[] buffer, long fillLength)
-        throws IOException {
-        while (fillLength > 0) {
-            int maxCopy = Math.min(buffer.length, (int) fillLength);
-            Arrays.fill(buffer, 0, maxCopy, value);
-            out.write(buffer, 0, maxCopy);
-            fillLength -= maxCopy;
-        }
-    }
-}
diff --git a/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp b/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
new file mode 100644
index 0000000..3dc5e50
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#define TRACE_TAG ADB
+
+#include "apk_archive.h"
+
+#include "adb_trace.h"
+#include "sysdeps.h"
+
+#include <android-base/endian.h>
+#include <android-base/mapped_file.h>
+
+#include <openssl/md5.h>
+
+constexpr uint16_t kCompressStored = 0;
+
+// mask value that signifies that the entry has a DD
+static const uint32_t kGPBDDFlagMask = 0x0008;
+
+namespace {
+struct FileRegion {
+    FileRegion(borrowed_fd fd, off64_t offset, size_t length)
+        : mapped_(android::base::MappedFile::FromOsHandle(adb_get_os_handle(fd), offset, length,
+                                                          PROT_READ)) {
+        if (mapped_.data() != nullptr) {
+            return;
+        }
+
+        // Mapped file failed, falling back to pread.
+        buffer_.resize(length);
+        if (auto err = adb_pread(fd.get(), buffer_.data(), length, offset); size_t(err) != length) {
+            fprintf(stderr, "Unable to read %lld bytes at offset %" PRId64 " \n",
+                    static_cast<long long>(length), offset);
+            buffer_.clear();
+            return;
+        }
+    }
+
+    const char* data() const { return mapped_.data() ? mapped_.data() : buffer_.data(); }
+    size_t size() const { return mapped_.data() ? mapped_.size() : buffer_.size(); }
+
+  private:
+    FileRegion() = default;
+    DISALLOW_COPY_AND_ASSIGN(FileRegion);
+
+    android::base::MappedFile mapped_;
+    std::string buffer_;
+};
+}  // namespace
+
+using com::android::fastdeploy::APKDump;
+
+ApkArchive::ApkArchive(const std::string& path) : path_(path), size_(0) {
+    fd_.reset(adb_open(path_.c_str(), O_RDONLY));
+    if (fd_ == -1) {
+        fprintf(stderr, "Unable to open file '%s'\n", path_.c_str());
+        return;
+    }
+
+    struct stat st;
+    if (stat(path_.c_str(), &st) == -1) {
+        fprintf(stderr, "Unable to stat file '%s'\n", path_.c_str());
+        return;
+    }
+    size_ = st.st_size;
+}
+
+ApkArchive::~ApkArchive() {}
+
+APKDump ApkArchive::ExtractMetadata() {
+    D("ExtractMetadata");
+    if (!ready()) {
+        return {};
+    }
+
+    Location cdLoc = GetCDLocation();
+    if (!cdLoc.valid) {
+        return {};
+    }
+
+    APKDump dump;
+    dump.set_absolute_path(path_);
+    dump.set_cd(ReadMetadata(cdLoc));
+
+    Location sigLoc = GetSignatureLocation(cdLoc.offset);
+    if (sigLoc.valid) {
+        dump.set_signature(ReadMetadata(sigLoc));
+    }
+    return dump;
+}
+
+off_t ApkArchive::FindEndOfCDRecord() const {
+    constexpr int endOfCDSignature = 0x06054b50;
+    constexpr off_t endOfCDMinSize = 22;
+    constexpr off_t endOfCDMaxSize = 65535 + endOfCDMinSize;
+
+    auto sizeToRead = std::min(size_, endOfCDMaxSize);
+    auto readOffset = size_ - sizeToRead;
+    FileRegion mapped(fd_, readOffset, sizeToRead);
+
+    // Start scanning from the end
+    auto* start = mapped.data();
+    auto* cursor = start + mapped.size() - sizeof(endOfCDSignature);
+
+    // Search for End of Central Directory record signature.
+    while (cursor >= start) {
+        if (*(int32_t*)cursor == endOfCDSignature) {
+            return readOffset + (cursor - start);
+        }
+        cursor--;
+    }
+    return -1;
+}
+
+ApkArchive::Location ApkArchive::FindCDRecord(const char* cursor) {
+    struct ecdr_t {
+        int32_t signature;
+        uint16_t diskNumber;
+        uint16_t numDisk;
+        uint16_t diskEntries;
+        uint16_t numEntries;
+        uint32_t crSize;
+        uint32_t offsetToCdHeader;
+        uint16_t commentSize;
+        uint8_t comment[0];
+    } __attribute__((packed));
+    ecdr_t* header = (ecdr_t*)cursor;
+
+    Location location;
+    location.offset = header->offsetToCdHeader;
+    location.size = header->crSize;
+    location.valid = true;
+    return location;
+}
+
+ApkArchive::Location ApkArchive::GetCDLocation() {
+    constexpr off_t cdEntryHeaderSizeBytes = 22;
+    Location location;
+
+    // Find End of Central Directory Record
+    off_t eocdRecord = FindEndOfCDRecord();
+    if (eocdRecord < 0) {
+        fprintf(stderr, "Unable to find End of Central Directory record in file '%s'\n",
+                path_.c_str());
+        return location;
+    }
+
+    // Find Central Directory Record
+    FileRegion mapped(fd_, eocdRecord, cdEntryHeaderSizeBytes);
+    location = FindCDRecord(mapped.data());
+    if (!location.valid) {
+        fprintf(stderr, "Unable to find Central Directory File Header in file '%s'\n",
+                path_.c_str());
+        return location;
+    }
+
+    return location;
+}
+
+ApkArchive::Location ApkArchive::GetSignatureLocation(off_t cdRecordOffset) {
+    Location location;
+
+    // Signature constants.
+    constexpr off_t endOfSignatureSize = 24;
+    off_t signatureOffset = cdRecordOffset - endOfSignatureSize;
+    if (signatureOffset < 0) {
+        fprintf(stderr, "Unable to find signature in file '%s'\n", path_.c_str());
+        return location;
+    }
+
+    FileRegion mapped(fd_, signatureOffset, endOfSignatureSize);
+
+    uint64_t signatureSize = *(uint64_t*)mapped.data();
+    auto* signature = mapped.data() + sizeof(signatureSize);
+    // Check if there is a v2/v3 Signature block here.
+    if (memcmp(signature, "APK Sig Block 42", 16)) {
+        return location;
+    }
+
+    // This is likely a signature block.
+    location.size = signatureSize;
+    location.offset = cdRecordOffset - location.size - 8;
+    location.valid = true;
+
+    return location;
+}
+
+std::string ApkArchive::ReadMetadata(Location loc) const {
+    FileRegion mapped(fd_, loc.offset, loc.size);
+    return {mapped.data(), mapped.size()};
+}
+
+size_t ApkArchive::ParseCentralDirectoryRecord(const char* input, size_t size, std::string* md5Hash,
+                                               int64_t* localFileHeaderOffset, int64_t* dataSize) {
+    // A structure representing the fixed length fields for a single
+    // record in the central directory of the archive. In addition to
+    // the fixed length fields listed here, each central directory
+    // record contains a variable length "file_name" and "extra_field"
+    // whose lengths are given by |file_name_length| and |extra_field_length|
+    // respectively.
+    static constexpr int kCDFileHeaderMagic = 0x02014b50;
+    struct CentralDirectoryRecord {
+        // The start of record signature. Must be |kSignature|.
+        uint32_t record_signature;
+        // Source tool version. Top byte gives source OS.
+        uint16_t version_made_by;
+        // Tool version. Ignored by this implementation.
+        uint16_t version_needed;
+        // The "general purpose bit flags" for this entry. The only
+        // flag value that we currently check for is the "data descriptor"
+        // flag.
+        uint16_t gpb_flags;
+        // The compression method for this entry, one of |kCompressStored|
+        // and |kCompressDeflated|.
+        uint16_t compression_method;
+        // The file modification time and date for this entry.
+        uint16_t last_mod_time;
+        uint16_t last_mod_date;
+        // The CRC-32 checksum for this entry.
+        uint32_t crc32;
+        // The compressed size (in bytes) of this entry.
+        uint32_t compressed_size;
+        // The uncompressed size (in bytes) of this entry.
+        uint32_t uncompressed_size;
+        // The length of the entry file name in bytes. The file name
+        // will appear immediately after this record.
+        uint16_t file_name_length;
+        // The length of the extra field info (in bytes). This data
+        // will appear immediately after the entry file name.
+        uint16_t extra_field_length;
+        // The length of the entry comment (in bytes). This data will
+        // appear immediately after the extra field.
+        uint16_t comment_length;
+        // The start disk for this entry. Ignored by this implementation).
+        uint16_t file_start_disk;
+        // File attributes. Ignored by this implementation.
+        uint16_t internal_file_attributes;
+        // File attributes. For archives created on Unix, the top bits are the
+        // mode.
+        uint32_t external_file_attributes;
+        // The offset to the local file header for this entry, from the
+        // beginning of this archive.
+        uint32_t local_file_header_offset;
+
+      private:
+        CentralDirectoryRecord() = default;
+        DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord);
+    } __attribute__((packed));
+
+    const CentralDirectoryRecord* cdr;
+    if (size < sizeof(*cdr)) {
+        return 0;
+    }
+
+    auto begin = input;
+    cdr = reinterpret_cast<const CentralDirectoryRecord*>(begin);
+    if (cdr->record_signature != kCDFileHeaderMagic) {
+        fprintf(stderr, "Invalid Central Directory Record signature\n");
+        return 0;
+    }
+    auto end = begin + sizeof(*cdr) + cdr->file_name_length + cdr->extra_field_length +
+               cdr->comment_length;
+
+    uint8_t md5Digest[MD5_DIGEST_LENGTH];
+    MD5((const unsigned char*)begin, end - begin, md5Digest);
+    md5Hash->assign((const char*)md5Digest, sizeof(md5Digest));
+
+    *localFileHeaderOffset = cdr->local_file_header_offset;
+    *dataSize = (cdr->compression_method == kCompressStored) ? cdr->uncompressed_size
+                                                             : cdr->compressed_size;
+
+    return end - begin;
+}
+
+size_t ApkArchive::CalculateLocalFileEntrySize(int64_t localFileHeaderOffset,
+                                               int64_t dataSize) const {
+    // The local file header for a given entry. This duplicates information
+    // present in the central directory of the archive. It is an error for
+    // the information here to be different from the central directory
+    // information for a given entry.
+    static constexpr int kLocalFileHeaderMagic = 0x04034b50;
+    struct LocalFileHeader {
+        // The local file header signature, must be |kSignature|.
+        uint32_t lfh_signature;
+        // Tool version. Ignored by this implementation.
+        uint16_t version_needed;
+        // The "general purpose bit flags" for this entry. The only
+        // flag value that we currently check for is the "data descriptor"
+        // flag.
+        uint16_t gpb_flags;
+        // The compression method for this entry, one of |kCompressStored|
+        // and |kCompressDeflated|.
+        uint16_t compression_method;
+        // The file modification time and date for this entry.
+        uint16_t last_mod_time;
+        uint16_t last_mod_date;
+        // The CRC-32 checksum for this entry.
+        uint32_t crc32;
+        // The compressed size (in bytes) of this entry.
+        uint32_t compressed_size;
+        // The uncompressed size (in bytes) of this entry.
+        uint32_t uncompressed_size;
+        // The length of the entry file name in bytes. The file name
+        // will appear immediately after this record.
+        uint16_t file_name_length;
+        // The length of the extra field info (in bytes). This data
+        // will appear immediately after the entry file name.
+        uint16_t extra_field_length;
+
+      private:
+        LocalFileHeader() = default;
+        DISALLOW_COPY_AND_ASSIGN(LocalFileHeader);
+    } __attribute__((packed));
+    static constexpr int kLocalFileHeaderSize = sizeof(LocalFileHeader);
+    CHECK(ready()) << path_;
+
+    const LocalFileHeader* lfh;
+    if (localFileHeaderOffset + kLocalFileHeaderSize > size_) {
+        fprintf(stderr,
+                "Invalid Local File Header offset in file '%s' at offset %lld, file size %lld\n",
+                path_.c_str(), static_cast<long long>(localFileHeaderOffset),
+                static_cast<long long>(size_));
+        return 0;
+    }
+
+    FileRegion lfhMapped(fd_, localFileHeaderOffset, sizeof(LocalFileHeader));
+    lfh = reinterpret_cast<const LocalFileHeader*>(lfhMapped.data());
+    if (lfh->lfh_signature != kLocalFileHeaderMagic) {
+        fprintf(stderr, "Invalid Local File Header signature in file '%s' at offset %lld\n",
+                path_.c_str(), static_cast<long long>(localFileHeaderOffset));
+        return 0;
+    }
+
+    // The *optional* data descriptor start signature.
+    static constexpr int kOptionalDataDescriptorMagic = 0x08074b50;
+    struct DataDescriptor {
+        // CRC-32 checksum of the entry.
+        uint32_t crc32;
+        // Compressed size of the entry.
+        uint32_t compressed_size;
+        // Uncompressed size of the entry.
+        uint32_t uncompressed_size;
+
+      private:
+        DataDescriptor() = default;
+        DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
+    } __attribute__((packed));
+    static constexpr int kDataDescriptorSize = sizeof(DataDescriptor);
+
+    off_t ddOffset = localFileHeaderOffset + kLocalFileHeaderSize + lfh->file_name_length +
+                     lfh->extra_field_length + dataSize;
+    int64_t ddSize = 0;
+
+    int64_t localDataSize;
+    if (lfh->gpb_flags & kGPBDDFlagMask) {
+        // There is trailing data descriptor.
+        const DataDescriptor* dd;
+
+        if (ddOffset + int(sizeof(uint32_t)) > size_) {
+            fprintf(stderr,
+                    "Error reading trailing data descriptor signature in file '%s' at offset %lld, "
+                    "file size %lld\n",
+                    path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_));
+            return 0;
+        }
+
+        FileRegion ddMapped(fd_, ddOffset, sizeof(uint32_t) + sizeof(DataDescriptor));
+
+        off_t localDDOffset = 0;
+        if (kOptionalDataDescriptorMagic == *(uint32_t*)ddMapped.data()) {
+            ddOffset += sizeof(uint32_t);
+            localDDOffset += sizeof(uint32_t);
+            ddSize += sizeof(uint32_t);
+        }
+        if (ddOffset + kDataDescriptorSize > size_) {
+            fprintf(stderr,
+                    "Error reading trailing data descriptor in file '%s' at offset %lld, file size "
+                    "%lld\n",
+                    path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_));
+            return 0;
+        }
+
+        dd = reinterpret_cast<const DataDescriptor*>(ddMapped.data() + localDDOffset);
+        localDataSize = (lfh->compression_method == kCompressStored) ? dd->uncompressed_size
+                                                                     : dd->compressed_size;
+        ddSize += sizeof(*dd);
+    } else {
+        localDataSize = (lfh->compression_method == kCompressStored) ? lfh->uncompressed_size
+                                                                     : lfh->compressed_size;
+    }
+    if (localDataSize != dataSize) {
+        fprintf(stderr,
+                "Data sizes mismatch in file '%s' at offset %lld, CDr: %lld vs LHR/DD: %lld\n",
+                path_.c_str(), static_cast<long long>(localFileHeaderOffset),
+                static_cast<long long>(dataSize), static_cast<long long>(localDataSize));
+        return 0;
+    }
+
+    return kLocalFileHeaderSize + lfh->file_name_length + lfh->extra_field_length + dataSize +
+           ddSize;
+}
diff --git a/adb/fastdeploy/deploypatchgenerator/apk_archive.h b/adb/fastdeploy/deploypatchgenerator/apk_archive.h
new file mode 100644
index 0000000..7127800
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/apk_archive.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <adb_unique_fd.h>
+
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+class ApkArchiveTester;
+
+// Manipulates an APK archive. Process it by mmaping it in order to minimize
+// I/Os.
+class ApkArchive {
+  public:
+    friend ApkArchiveTester;
+
+    // A convenience struct to store the result of search operation when
+    // locating the EoCDr, CDr, and Signature Block.
+    struct Location {
+        off_t offset = 0;
+        off_t size = 0;
+        bool valid = false;
+    };
+
+    ApkArchive(const std::string& path);
+    ~ApkArchive();
+
+    com::android::fastdeploy::APKDump ExtractMetadata();
+
+    // Parses the CDr starting from |input| and returns number of bytes consumed.
+    // Extracts local file header offset, data size and calculates MD5 hash of the record.
+    // 0 indicates invalid CDr.
+    static size_t ParseCentralDirectoryRecord(const char* input, size_t size, std::string* md5Hash,
+                                              int64_t* localFileHeaderOffset, int64_t* dataSize);
+    // Calculates Local File Entry size including header using offset and data size from CDr.
+    // 0 indicates invalid Local File Entry.
+    size_t CalculateLocalFileEntrySize(int64_t localFileHeaderOffset, int64_t dataSize) const;
+
+  private:
+    std::string ReadMetadata(Location loc) const;
+
+    // Retrieve the location of the Central Directory Record.
+    Location GetCDLocation();
+
+    // Retrieve the location of the signature block starting from Central
+    // Directory Record
+    Location GetSignatureLocation(off_t cdRecordOffset);
+
+    // Find the End of Central Directory Record, starting from the end of the
+    // file.
+    off_t FindEndOfCDRecord() const;
+
+    // Find Central Directory Record, starting from the end of the file.
+    Location FindCDRecord(const char* cursor);
+
+    // Checks if the archive can be used.
+    bool ready() const { return fd_ >= 0; }
+
+    std::string path_;
+    off_t size_;
+    unique_fd fd_;
+};
diff --git a/adb/fastdeploy/deploypatchgenerator/apk_archive_test.cpp b/adb/fastdeploy/deploypatchgenerator/apk_archive_test.cpp
new file mode 100644
index 0000000..554cb57
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/apk_archive_test.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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 <iostream>
+
+#include <gtest/gtest.h>
+
+#include "apk_archive.h"
+
+// Friend test to get around private scope of ApkArchive private functions.
+class ApkArchiveTester {
+  public:
+    ApkArchiveTester(const std::string& path) : archive_(path) {}
+
+    bool ready() { return archive_.ready(); }
+
+    auto ExtractMetadata() { return archive_.ExtractMetadata(); }
+
+    ApkArchive::Location GetCDLocation() { return archive_.GetCDLocation(); }
+    ApkArchive::Location GetSignatureLocation(size_t start) {
+        return archive_.GetSignatureLocation(start);
+    }
+
+  private:
+    ApkArchive archive_;
+};
+
+TEST(ApkArchiveTest, TestApkArchiveSizes) {
+    ApkArchiveTester archiveTester("fastdeploy/testdata/sample.apk");
+    EXPECT_TRUE(archiveTester.ready());
+
+    ApkArchive::Location cdLoc = archiveTester.GetCDLocation();
+    EXPECT_TRUE(cdLoc.valid);
+    ASSERT_EQ(cdLoc.offset, 2044145u);
+    ASSERT_EQ(cdLoc.size, 49390u);
+
+    // Check that block can be retrieved
+    ApkArchive::Location sigLoc = archiveTester.GetSignatureLocation(cdLoc.offset);
+    EXPECT_TRUE(sigLoc.valid);
+    ASSERT_EQ(sigLoc.offset, 2040049u);
+    ASSERT_EQ(sigLoc.size, 4088u);
+}
+
+TEST(ApkArchiveTest, TestApkArchiveDump) {
+    ApkArchiveTester archiveTester("fastdeploy/testdata/sample.apk");
+    EXPECT_TRUE(archiveTester.ready());
+
+    auto dump = archiveTester.ExtractMetadata();
+    ASSERT_EQ(dump.cd().size(), 49390u);
+    ASSERT_EQ(dump.signature().size(), 4088u);
+}
+
+TEST(ApkArchiveTest, WrongApk) {
+    ApkArchiveTester archiveTester("fastdeploy/testdata/sample.cd");
+    EXPECT_TRUE(archiveTester.ready());
+
+    auto dump = archiveTester.ExtractMetadata();
+    ASSERT_EQ(dump.cd().size(), 0u);
+    ASSERT_EQ(dump.signature().size(), 0u);
+}
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
index 154c9b9..8aa7da7 100644
--- a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
@@ -25,8 +25,12 @@
 #include <iostream>
 #include <sstream>
 #include <string>
+#include <unordered_map>
+
+#include <openssl/md5.h>
 
 #include "adb_unique_fd.h"
+#include "adb_utils.h"
 #include "android-base/file.h"
 #include "patch_utils.h"
 #include "sysdeps.h"
@@ -34,9 +38,6 @@
 using namespace com::android::fastdeploy;
 
 void DeployPatchGenerator::Log(const char* fmt, ...) {
-    if (!is_verbose_) {
-        return;
-    }
     va_list ap;
     va_start(ap, fmt);
     vprintf(fmt, ap);
@@ -44,19 +45,34 @@
     va_end(ap);
 }
 
-void DeployPatchGenerator::APKEntryToLog(const APKEntry& entry) {
-    Log("Filename: %s", entry.filename().c_str());
-    Log("CRC32: 0x%08" PRIX64, entry.crc32());
-    Log("Data Offset: %" PRId64, entry.dataoffset());
-    Log("Compressed Size: %" PRId64, entry.compressedsize());
-    Log("Uncompressed Size: %" PRId64, entry.uncompressedsize());
+static std::string HexEncode(const void* in_buffer, unsigned int size) {
+    static const char kHexChars[] = "0123456789ABCDEF";
+
+    // Each input byte creates two output hex characters.
+    std::string out_buffer(size * 2, '\0');
+
+    for (unsigned int i = 0; i < size; ++i) {
+        char byte = ((const uint8_t*)in_buffer)[i];
+        out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
+        out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf];
+    }
+    return out_buffer;
 }
 
-void DeployPatchGenerator::APKMetaDataToLog(const char* file, const APKMetaData& metadata) {
+void DeployPatchGenerator::APKEntryToLog(const APKEntry& entry) {
     if (!is_verbose_) {
         return;
     }
-    Log("APK Metadata: %s", file);
+    Log("MD5: %s", HexEncode(entry.md5().data(), entry.md5().size()).c_str());
+    Log("Data Offset: %" PRId64, entry.dataoffset());
+    Log("Data Size: %" PRId64, entry.datasize());
+}
+
+void DeployPatchGenerator::APKMetaDataToLog(const APKMetaData& metadata) {
+    if (!is_verbose_) {
+        return;
+    }
+    Log("APK Metadata: %s", metadata.absolute_path().c_str());
     for (int i = 0; i < metadata.entries_size(); i++) {
         const APKEntry& entry = metadata.entries(i);
         APKEntryToLog(entry);
@@ -65,49 +81,93 @@
 
 void DeployPatchGenerator::ReportSavings(const std::vector<SimpleEntry>& identicalEntries,
                                          uint64_t totalSize) {
-    long totalEqualBytes = 0;
-    int totalEqualFiles = 0;
+    uint64_t totalEqualBytes = 0;
+    uint64_t totalEqualFiles = 0;
     for (size_t i = 0; i < identicalEntries.size(); i++) {
         if (identicalEntries[i].deviceEntry != nullptr) {
-            totalEqualBytes += identicalEntries[i].localEntry->compressedsize();
+            totalEqualBytes += identicalEntries[i].localEntry->datasize();
             totalEqualFiles++;
         }
     }
-    float savingPercent = (totalEqualBytes * 100.0f) / totalSize;
-    fprintf(stderr, "Detected %d equal APK entries\n", totalEqualFiles);
-    fprintf(stderr, "%ld bytes are equal out of %" PRIu64 " (%.2f%%)\n", totalEqualBytes, totalSize,
-            savingPercent);
+    double savingPercent = (totalEqualBytes * 100.0f) / totalSize;
+    fprintf(stderr, "Detected %" PRIu64 " equal APK entries\n", totalEqualFiles);
+    fprintf(stderr, "%" PRIu64 " bytes are equal out of %" PRIu64 " (%.2f%%)\n", totalEqualBytes,
+            totalSize, savingPercent);
+}
+
+struct PatchEntry {
+    int64_t deltaFromDeviceDataStart = 0;
+    int64_t deviceDataOffset = 0;
+    int64_t deviceDataLength = 0;
+};
+static void WritePatchEntry(const PatchEntry& patchEntry, borrowed_fd input, borrowed_fd output,
+                            size_t* realSizeOut) {
+    if (!(patchEntry.deltaFromDeviceDataStart | patchEntry.deviceDataOffset |
+          patchEntry.deviceDataLength)) {
+        return;
+    }
+
+    PatchUtils::WriteLong(patchEntry.deltaFromDeviceDataStart, output);
+    if (patchEntry.deltaFromDeviceDataStart > 0) {
+        PatchUtils::Pipe(input, output, patchEntry.deltaFromDeviceDataStart);
+    }
+    auto hostDataLength = patchEntry.deviceDataLength;
+    adb_lseek(input, hostDataLength, SEEK_CUR);
+
+    PatchUtils::WriteLong(patchEntry.deviceDataOffset, output);
+    PatchUtils::WriteLong(patchEntry.deviceDataLength, output);
+
+    *realSizeOut += patchEntry.deltaFromDeviceDataStart + hostDataLength;
 }
 
 void DeployPatchGenerator::GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
-                                         const char* localApkPath, borrowed_fd output) {
-    unique_fd input(adb_open(localApkPath, O_RDONLY | O_CLOEXEC));
+                                         const std::string& localApkPath,
+                                         const std::string& deviceApkPath, borrowed_fd output) {
+    unique_fd input(adb_open(localApkPath.c_str(), O_RDONLY | O_CLOEXEC));
     size_t newApkSize = adb_lseek(input, 0L, SEEK_END);
     adb_lseek(input, 0L, SEEK_SET);
 
+    // Header.
     PatchUtils::WriteSignature(output);
     PatchUtils::WriteLong(newApkSize, output);
+    PatchUtils::WriteString(deviceApkPath, output);
+
     size_t currentSizeOut = 0;
+    size_t realSizeOut = 0;
     // Write data from the host upto the first entry we have that matches a device entry. Then write
     // the metadata about the device entry and repeat for all entries that match on device. Finally
     // write out any data left. If the device and host APKs are exactly the same this ends up
     // writing out zip metadata from the local APK followed by offsets to the data to use from the
     // device APK.
-    for (auto&& entry : entriesToUseOnDevice) {
-        int64_t deviceDataOffset = entry.deviceEntry->dataoffset();
+    PatchEntry patchEntry;
+    for (size_t i = 0, size = entriesToUseOnDevice.size(); i < size; ++i) {
+        auto&& entry = entriesToUseOnDevice[i];
         int64_t hostDataOffset = entry.localEntry->dataoffset();
-        int64_t deviceDataLength = entry.deviceEntry->compressedsize();
+        int64_t hostDataLength = entry.localEntry->datasize();
+        int64_t deviceDataOffset = entry.deviceEntry->dataoffset();
+        // Both entries are the same, using host data length.
+        int64_t deviceDataLength = hostDataLength;
+
         int64_t deltaFromDeviceDataStart = hostDataOffset - currentSizeOut;
-        PatchUtils::WriteLong(deltaFromDeviceDataStart, output);
         if (deltaFromDeviceDataStart > 0) {
-            PatchUtils::Pipe(input, output, deltaFromDeviceDataStart);
+            WritePatchEntry(patchEntry, input, output, &realSizeOut);
+            patchEntry.deltaFromDeviceDataStart = deltaFromDeviceDataStart;
+            patchEntry.deviceDataOffset = deviceDataOffset;
+            patchEntry.deviceDataLength = deviceDataLength;
+        } else {
+            patchEntry.deviceDataLength += deviceDataLength;
         }
-        PatchUtils::WriteLong(deviceDataOffset, output);
-        PatchUtils::WriteLong(deviceDataLength, output);
-        adb_lseek(input, deviceDataLength, SEEK_CUR);
-        currentSizeOut += deltaFromDeviceDataStart + deviceDataLength;
+
+        currentSizeOut += deltaFromDeviceDataStart + hostDataLength;
     }
-    if (currentSizeOut != newApkSize) {
+    WritePatchEntry(patchEntry, input, output, &realSizeOut);
+    if (realSizeOut != currentSizeOut) {
+        fprintf(stderr, "Size mismatch current %lld vs real %lld\n",
+                static_cast<long long>(currentSizeOut), static_cast<long long>(realSizeOut));
+        error_exit("Aborting");
+    }
+
+    if (newApkSize > currentSizeOut) {
         PatchUtils::WriteLong(newApkSize - currentSizeOut, output);
         PatchUtils::Pipe(input, output, newApkSize - currentSizeOut);
         PatchUtils::WriteLong(0, output);
@@ -115,44 +175,72 @@
     }
 }
 
-bool DeployPatchGenerator::CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
-                                       borrowed_fd output) {
-    std::string content;
-    APKMetaData deviceApkMetadata;
-    if (android::base::ReadFileToString(deviceApkMetadataPath, &content)) {
-        deviceApkMetadata.ParsePartialFromString(content);
-    } else {
-        // TODO: What do we want to do if we don't find any metadata.
-        // The current fallback behavior is to build a patch with the contents of |localApkPath|.
-    }
+bool DeployPatchGenerator::CreatePatch(const char* localApkPath, APKMetaData deviceApkMetadata,
+                                       android::base::borrowed_fd output) {
+    return CreatePatch(PatchUtils::GetHostAPKMetaData(localApkPath), std::move(deviceApkMetadata),
+                       output);
+}
 
-    APKMetaData localApkMetadata = PatchUtils::GetAPKMetaData(localApkPath);
-    // Log gathered metadata info.
-    APKMetaDataToLog(deviceApkMetadataPath, deviceApkMetadata);
-    APKMetaDataToLog(localApkPath, localApkMetadata);
+bool DeployPatchGenerator::CreatePatch(APKMetaData localApkMetadata, APKMetaData deviceApkMetadata,
+                                       borrowed_fd output) {
+    // Log metadata info.
+    APKMetaDataToLog(deviceApkMetadata);
+    APKMetaDataToLog(localApkMetadata);
+
+    const std::string localApkPath = localApkMetadata.absolute_path();
+    const std::string deviceApkPath = deviceApkMetadata.absolute_path();
 
     std::vector<SimpleEntry> identicalEntries;
     uint64_t totalSize =
             BuildIdenticalEntries(identicalEntries, localApkMetadata, deviceApkMetadata);
     ReportSavings(identicalEntries, totalSize);
-    GeneratePatch(identicalEntries, localApkPath, output);
+    GeneratePatch(identicalEntries, localApkPath, deviceApkPath, output);
+
     return true;
 }
 
 uint64_t DeployPatchGenerator::BuildIdenticalEntries(std::vector<SimpleEntry>& outIdenticalEntries,
                                                      const APKMetaData& localApkMetadata,
                                                      const APKMetaData& deviceApkMetadata) {
+    outIdenticalEntries.reserve(
+            std::min(localApkMetadata.entries_size(), deviceApkMetadata.entries_size()));
+
+    using md5Digest = std::pair<uint64_t, uint64_t>;
+    struct md5Hash {
+        size_t operator()(const md5Digest& digest) const {
+            std::hash<uint64_t> hasher;
+            size_t seed = 0;
+            seed ^= hasher(digest.first) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+            seed ^= hasher(digest.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+            return seed;
+        }
+    };
+    static_assert(sizeof(md5Digest) == MD5_DIGEST_LENGTH);
+    std::unordered_map<md5Digest, std::vector<const APKEntry*>, md5Hash> deviceEntries;
+    for (const auto& deviceEntry : deviceApkMetadata.entries()) {
+        md5Digest md5;
+        memcpy(&md5, deviceEntry.md5().data(), deviceEntry.md5().size());
+
+        deviceEntries[md5].push_back(&deviceEntry);
+    }
+
     uint64_t totalSize = 0;
-    for (int i = 0; i < localApkMetadata.entries_size(); i++) {
-        const APKEntry& localEntry = localApkMetadata.entries(i);
-        totalSize += localEntry.compressedsize();
-        for (int j = 0; j < deviceApkMetadata.entries_size(); j++) {
-            const APKEntry& deviceEntry = deviceApkMetadata.entries(j);
-            if (deviceEntry.crc32() == localEntry.crc32() &&
-                deviceEntry.filename().compare(localEntry.filename()) == 0) {
+    for (const auto& localEntry : localApkMetadata.entries()) {
+        totalSize += localEntry.datasize();
+
+        md5Digest md5;
+        memcpy(&md5, localEntry.md5().data(), localEntry.md5().size());
+
+        auto deviceEntriesIt = deviceEntries.find(md5);
+        if (deviceEntriesIt == deviceEntries.end()) {
+            continue;
+        }
+
+        for (const auto* deviceEntry : deviceEntriesIt->second) {
+            if (deviceEntry->md5() == localEntry.md5()) {
                 SimpleEntry simpleEntry;
-                simpleEntry.localEntry = const_cast<APKEntry*>(&localEntry);
-                simpleEntry.deviceEntry = const_cast<APKEntry*>(&deviceEntry);
+                simpleEntry.localEntry = &localEntry;
+                simpleEntry.deviceEntry = deviceEntry;
                 APKEntryToLog(localEntry);
                 outIdenticalEntries.push_back(simpleEntry);
                 break;
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
index 30e41a5..fd7eaee 100644
--- a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
@@ -27,12 +27,15 @@
  */
 class DeployPatchGenerator {
   public:
+    using APKEntry = com::android::fastdeploy::APKEntry;
+    using APKMetaData = com::android::fastdeploy::APKMetaData;
+
     /**
      * Simple struct to hold mapping between local metadata and device metadata.
      */
     struct SimpleEntry {
-        com::android::fastdeploy::APKEntry* localEntry;
-        com::android::fastdeploy::APKEntry* deviceEntry;
+        const APKEntry* localEntry;
+        const APKEntry* deviceEntry;
     };
 
     /**
@@ -41,10 +44,10 @@
      */
     explicit DeployPatchGenerator(bool is_verbose) : is_verbose_(is_verbose) {}
     /**
-     * Given a |localApkPath|, and the |deviceApkMetadataPath| from an installed APK this function
+     * Given a |localApkPath|, and the |deviceApkMetadata| from an installed APK this function
      * writes a patch to the given |output|.
      */
-    bool CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
+    bool CreatePatch(const char* localApkPath, APKMetaData deviceApkMetadata,
                      android::base::borrowed_fd output);
 
   private:
@@ -57,14 +60,20 @@
 
     /**
      * Helper function to log the APKMetaData structure. If |is_verbose_| is false this function
-     * early outs. |file| is the path to the file represented by |metadata|. This function is used
-     * for debugging / information.
+     * early outs. This function is used for debugging / information.
      */
-    void APKMetaDataToLog(const char* file, const com::android::fastdeploy::APKMetaData& metadata);
+    void APKMetaDataToLog(const APKMetaData& metadata);
     /**
      * Helper function to log APKEntry.
      */
-    void APKEntryToLog(const com::android::fastdeploy::APKEntry& entry);
+    void APKEntryToLog(const APKEntry& entry);
+
+    /**
+     * Given the |localApkMetadata| metadata, and the |deviceApkMetadata| from an installed APK this
+     * function writes a patch to the given |output|.
+     */
+    bool CreatePatch(APKMetaData localApkMetadata, APKMetaData deviceApkMetadata,
+                     android::base::borrowed_fd output);
 
     /**
      * Helper function to report savings by fastdeploy. This function prints out savings even with
@@ -92,11 +101,11 @@
      * highest.
      */
     void GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
-                       const char* localApkPath, android::base::borrowed_fd output);
+                       const std::string& localApkPath, const std::string& deviceApkPath,
+                       android::base::borrowed_fd output);
 
   protected:
-    uint64_t BuildIdenticalEntries(
-            std::vector<SimpleEntry>& outIdenticalEntries,
-            const com::android::fastdeploy::APKMetaData& localApkMetadata,
-            const com::android::fastdeploy::APKMetaData& deviceApkMetadataPath);
-};
\ No newline at end of file
+    uint64_t BuildIdenticalEntries(std::vector<SimpleEntry>& outIdenticalEntries,
+                                   const APKMetaData& localApkMetadata,
+                                   const APKMetaData& deviceApkMetadata);
+};
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
index 9cdc44e..e4c96ea 100644
--- a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "deploy_patch_generator.h"
+#include "apk_archive.h"
 #include "patch_utils.h"
 
 #include <android-base/file.h>
@@ -31,21 +32,17 @@
     return "fastdeploy/testdata/" + name;
 }
 
-class TestPatchGenerator : DeployPatchGenerator {
-  public:
-    TestPatchGenerator() : DeployPatchGenerator(false) {}
-    void GatherIdenticalEntries(std::vector<DeployPatchGenerator::SimpleEntry>& outIdenticalEntries,
-                                const APKMetaData& metadataA, const APKMetaData& metadataB) {
-        BuildIdenticalEntries(outIdenticalEntries, metadataA, metadataB);
-    }
+struct TestPatchGenerator : DeployPatchGenerator {
+    using DeployPatchGenerator::BuildIdenticalEntries;
+    using DeployPatchGenerator::DeployPatchGenerator;
 };
 
 TEST(DeployPatchGeneratorTest, IdenticalFileEntries) {
     std::string apkPath = GetTestFile("rotating_cube-release.apk");
-    APKMetaData metadataA = PatchUtils::GetAPKMetaData(apkPath.c_str());
-    TestPatchGenerator generator;
+    APKMetaData metadataA = PatchUtils::GetHostAPKMetaData(apkPath.c_str());
+    TestPatchGenerator generator(false);
     std::vector<DeployPatchGenerator::SimpleEntry> entries;
-    generator.GatherIdenticalEntries(entries, metadataA, metadataA);
+    generator.BuildIdenticalEntries(entries, metadataA, metadataA);
     // Expect the entry count to match the number of entries in the metadata.
     const uint32_t identicalCount = entries.size();
     const uint32_t entriesCount = metadataA.entries_size();
@@ -64,9 +61,28 @@
     // Create a patch that is 100% different.
     TemporaryFile output;
     DeployPatchGenerator generator(true);
-    generator.CreatePatch(apkPath.c_str(), "", output.fd);
+    generator.CreatePatch(apkPath.c_str(), {}, output.fd);
 
     // Expect a patch file that has a size at least the size of our initial APK.
     long patchSize = adb_lseek(output.fd, 0L, SEEK_END);
     EXPECT_GT(patchSize, apkSize);
-}
\ No newline at end of file
+}
+
+TEST(DeployPatchGeneratorTest, ZeroSizePatch) {
+    std::string apkPath = GetTestFile("rotating_cube-release.apk");
+    ApkArchive archive(apkPath);
+    auto dump = archive.ExtractMetadata();
+    EXPECT_NE(dump.cd().size(), 0u);
+
+    APKMetaData metadata = PatchUtils::GetDeviceAPKMetaData(dump);
+
+    // Create a patch that is 100% the same.
+    TemporaryFile output;
+    output.DoNotRemove();
+    DeployPatchGenerator generator(true);
+    generator.CreatePatch(apkPath.c_str(), metadata, output.fd);
+
+    // Expect a patch file that is smaller than 0.5K.
+    int64_t patchSize = adb_lseek(output.fd, 0L, SEEK_END);
+    EXPECT_LE(patchSize, 512);
+}
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
index f11ddd1..2b00c80 100644
--- a/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
@@ -16,72 +16,94 @@
 
 #include "patch_utils.h"
 
-#include <androidfw/ZipFileRO.h>
 #include <stdio.h>
 
 #include "adb_io.h"
+#include "adb_utils.h"
 #include "android-base/endian.h"
 #include "sysdeps.h"
 
+#include "apk_archive.h"
+
 using namespace com::android;
 using namespace com::android::fastdeploy;
 using namespace android::base;
 
 static constexpr char kSignature[] = "FASTDEPLOY";
 
-APKMetaData PatchUtils::GetAPKMetaData(const char* apkPath) {
+APKMetaData PatchUtils::GetDeviceAPKMetaData(const APKDump& apk_dump) {
     APKMetaData apkMetaData;
-#undef open
-    std::unique_ptr<android::ZipFileRO> zipFile(android::ZipFileRO::open(apkPath));
-#define open ___xxx_unix_open
-    if (zipFile == nullptr) {
-        printf("Could not open %s", apkPath);
-        exit(1);
-    }
-    void* cookie;
-    if (zipFile->startIteration(&cookie)) {
-        android::ZipEntryRO entry;
-        while ((entry = zipFile->nextEntry(cookie)) != NULL) {
-            char fileName[256];
-            // Make sure we have a file name.
-            // TODO: Handle filenames longer than 256.
-            if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) {
-                continue;
-            }
+    apkMetaData.set_absolute_path(apk_dump.absolute_path());
 
-            uint32_t uncompressedSize, compressedSize, crc32;
-            int64_t dataOffset;
-            zipFile->getEntryInfo(entry, nullptr, &uncompressedSize, &compressedSize, &dataOffset,
-                                  nullptr, &crc32);
-            APKEntry* apkEntry = apkMetaData.add_entries();
-            apkEntry->set_crc32(crc32);
-            apkEntry->set_filename(fileName);
-            apkEntry->set_compressedsize(compressedSize);
-            apkEntry->set_uncompressedsize(uncompressedSize);
-            apkEntry->set_dataoffset(dataOffset);
-        }
+    std::string md5Hash;
+    int64_t localFileHeaderOffset;
+    int64_t dataSize;
+
+    const auto& cd = apk_dump.cd();
+    auto cur = cd.data();
+    int64_t size = cd.size();
+    while (auto consumed = ApkArchive::ParseCentralDirectoryRecord(
+                   cur, size, &md5Hash, &localFileHeaderOffset, &dataSize)) {
+        cur += consumed;
+        size -= consumed;
+
+        auto apkEntry = apkMetaData.add_entries();
+        apkEntry->set_md5(md5Hash);
+        apkEntry->set_dataoffset(localFileHeaderOffset);
+        apkEntry->set_datasize(dataSize);
     }
     return apkMetaData;
 }
 
+APKMetaData PatchUtils::GetHostAPKMetaData(const char* apkPath) {
+    ApkArchive archive(apkPath);
+    auto dump = archive.ExtractMetadata();
+    if (dump.cd().empty()) {
+        fprintf(stderr, "adb: Could not extract Central Directory from %s\n", apkPath);
+        error_exit("Aborting");
+    }
+
+    auto apkMetaData = GetDeviceAPKMetaData(dump);
+
+    // Now let's set data sizes.
+    for (auto& apkEntry : *apkMetaData.mutable_entries()) {
+        auto dataSize =
+                archive.CalculateLocalFileEntrySize(apkEntry.dataoffset(), apkEntry.datasize());
+        if (dataSize == 0) {
+            error_exit("Aborting");
+        }
+        apkEntry.set_datasize(dataSize);
+    }
+
+    return apkMetaData;
+}
+
 void PatchUtils::WriteSignature(borrowed_fd output) {
     WriteFdExactly(output, kSignature, sizeof(kSignature) - 1);
 }
 
 void PatchUtils::WriteLong(int64_t value, borrowed_fd output) {
-    int64_t toLittleEndian = htole64(value);
-    WriteFdExactly(output, &toLittleEndian, sizeof(int64_t));
+    int64_t littleEndian = htole64(value);
+    WriteFdExactly(output, &littleEndian, sizeof(littleEndian));
+}
+
+void PatchUtils::WriteString(const std::string& value, android::base::borrowed_fd output) {
+    WriteLong(value.size(), output);
+    WriteFdExactly(output, value);
 }
 
 void PatchUtils::Pipe(borrowed_fd input, borrowed_fd output, size_t amount) {
-    constexpr static int BUFFER_SIZE = 128 * 1024;
+    constexpr static size_t BUFFER_SIZE = 128 * 1024;
     char buffer[BUFFER_SIZE];
     size_t transferAmount = 0;
     while (transferAmount != amount) {
-        long chunkAmount =
-                amount - transferAmount > BUFFER_SIZE ? BUFFER_SIZE : amount - transferAmount;
-        long readAmount = adb_read(input, buffer, chunkAmount);
+        auto chunkAmount = std::min(amount - transferAmount, BUFFER_SIZE);
+        auto readAmount = adb_read(input, buffer, chunkAmount);
+        if (readAmount < 0) {
+            fprintf(stderr, "adb: failed to read from input: %s\n", strerror(errno));
+            error_exit("Aborting");
+        }
         WriteFdExactly(output, buffer, readAmount);
         transferAmount += readAmount;
     }
-}
\ No newline at end of file
+}
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.h b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
index 0ebfe8f..8dc9b9c 100644
--- a/adb/fastdeploy/deploypatchgenerator/patch_utils.h
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
@@ -25,11 +25,18 @@
 class PatchUtils {
   public:
     /**
+     * This function takes the dump of Central Directly and builds the APKMetaData required by the
+     * patching algorithm. The if this function has an error a string is printed to the terminal and
+     * exit(1) is called.
+     */
+    static com::android::fastdeploy::APKMetaData GetDeviceAPKMetaData(
+            const com::android::fastdeploy::APKDump& apk_dump);
+    /**
      * This function takes a local APK file and builds the APKMetaData required by the patching
      * algorithm. The if this function has an error a string is printed to the terminal and exit(1)
      * is called.
      */
-    static com::android::fastdeploy::APKMetaData GetAPKMetaData(const char* file);
+    static com::android::fastdeploy::APKMetaData GetHostAPKMetaData(const char* file);
     /**
      * Writes a fixed signature string to the header of the patch.
      */
@@ -39,8 +46,12 @@
      */
     static void WriteLong(int64_t value, android::base::borrowed_fd output);
     /**
+     * Writes string to the |output|.
+     */
+    static void WriteString(const std::string& value, android::base::borrowed_fd output);
+    /**
      * Copy |amount| of data from |input| to |output|.
      */
     static void Pipe(android::base::borrowed_fd input, android::base::borrowed_fd output,
                      size_t amount);
-};
\ No newline at end of file
+};
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
index a7eeebf..3ec5ab3 100644
--- a/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
@@ -23,10 +23,13 @@
 #include <sstream>
 #include <string>
 
+#include <google/protobuf/util/message_differencer.h>
+
 #include "adb_io.h"
 #include "sysdeps.h"
 
 using namespace com::android::fastdeploy;
+using google::protobuf::util::MessageDifferencer;
 
 static std::string GetTestFile(const std::string& name) {
     return "fastdeploy/testdata/" + name;
@@ -86,11 +89,56 @@
 
 TEST(PatchUtilsTest, GatherMetadata) {
     std::string apkFile = GetTestFile("rotating_cube-release.apk");
-    APKMetaData metadata = PatchUtils::GetAPKMetaData(apkFile.c_str());
+    APKMetaData actual = PatchUtils::GetHostAPKMetaData(apkFile.c_str());
+
     std::string expectedMetadata;
     android::base::ReadFileToString(GetTestFile("rotating_cube-metadata-release.data"),
                                     &expectedMetadata);
+    APKMetaData expected;
+    EXPECT_TRUE(expected.ParseFromString(expectedMetadata));
+
+    // Test paths might vary.
+    expected.set_absolute_path(actual.absolute_path());
+
     std::string actualMetadata;
-    metadata.SerializeToString(&actualMetadata);
+    actual.SerializeToString(&actualMetadata);
+
+    expected.SerializeToString(&expectedMetadata);
+
     EXPECT_EQ(expectedMetadata, actualMetadata);
-}
\ No newline at end of file
+}
+
+static inline void sanitize(APKMetaData& metadata) {
+    metadata.clear_absolute_path();
+    for (auto&& entry : *metadata.mutable_entries()) {
+        entry.clear_datasize();
+    }
+}
+
+TEST(PatchUtilsTest, GatherDumpMetadata) {
+    APKMetaData hostMetadata;
+    APKMetaData deviceMetadata;
+
+    hostMetadata = PatchUtils::GetHostAPKMetaData(GetTestFile("sample.apk").c_str());
+
+    {
+        std::string cd;
+        android::base::ReadFileToString(GetTestFile("sample.cd"), &cd);
+
+        APKDump dump;
+        dump.set_cd(std::move(cd));
+
+        deviceMetadata = PatchUtils::GetDeviceAPKMetaData(dump);
+    }
+
+    sanitize(hostMetadata);
+    sanitize(deviceMetadata);
+
+    std::string expectedMetadata;
+    hostMetadata.SerializeToString(&expectedMetadata);
+
+    std::string actualMetadata;
+    deviceMetadata.SerializeToString(&actualMetadata);
+
+    EXPECT_EQ(expectedMetadata, actualMetadata);
+}
diff --git a/adb/fastdeploy/proto/ApkEntry.proto b/adb/fastdeploy/proto/ApkEntry.proto
index 9460d15..d84c5a5 100644
--- a/adb/fastdeploy/proto/ApkEntry.proto
+++ b/adb/fastdeploy/proto/ApkEntry.proto
@@ -1,18 +1,26 @@
-syntax = "proto2";
+syntax = "proto3";
 
 package com.android.fastdeploy;
 
 option java_package = "com.android.fastdeploy";
+option java_outer_classname = "ApkEntryProto";
 option java_multiple_files = true;
+option optimize_for = LITE_RUNTIME;
+
+message APKDump {
+    string name = 1;
+    bytes cd = 2;
+    bytes signature = 3;
+    string absolute_path = 4;
+}
 
 message APKEntry {
-    required int64 crc32 = 1;
-    required string fileName = 2;
-    required int64 dataOffset = 3;
-    required int64 compressedSize = 4;
-    required int64 uncompressedSize = 5;
+    bytes md5 = 1;
+    int64 dataOffset = 2;
+    int64 dataSize = 3;
 }
 
 message APKMetaData {
-    repeated APKEntry entries = 1;
+    string absolute_path = 1;
+    repeated APKEntry entries = 2;
 }
diff --git a/adb/fastdeploy/testdata/helloworld5.apk b/adb/fastdeploy/testdata/helloworld5.apk
new file mode 100644
index 0000000..4a1539e
--- /dev/null
+++ b/adb/fastdeploy/testdata/helloworld5.apk
Binary files differ
diff --git a/adb/fastdeploy/testdata/helloworld7.apk b/adb/fastdeploy/testdata/helloworld7.apk
new file mode 100644
index 0000000..82c46df
--- /dev/null
+++ b/adb/fastdeploy/testdata/helloworld7.apk
Binary files differ
diff --git a/adb/fastdeploy/testdata/rotating_cube-metadata-release.data b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
index 0671bf3..52352ff 100644
--- a/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
+++ b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
Binary files differ
diff --git a/adb/fastdeploy/testdata/sample.apk b/adb/fastdeploy/testdata/sample.apk
new file mode 100644
index 0000000..c316205
--- /dev/null
+++ b/adb/fastdeploy/testdata/sample.apk
Binary files differ
diff --git a/adb/fastdeploy/testdata/sample.cd b/adb/fastdeploy/testdata/sample.cd
new file mode 100644
index 0000000..5e5b4d4
--- /dev/null
+++ b/adb/fastdeploy/testdata/sample.cd
Binary files differ
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index 987f994..466c2ce 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -601,6 +601,10 @@
     return path[0] == '/';
 }
 
+static __inline__ int adb_get_os_handle(borrowed_fd fd) {
+    return fd.get();
+}
+
 #endif /* !_WIN32 */
 
 static inline void disable_tcp_nagle(borrowed_fd fd) {
diff --git a/base/file.cpp b/base/file.cpp
index b1ddc5d..6321fc6 100644
--- a/base/file.cpp
+++ b/base/file.cpp
@@ -53,34 +53,47 @@
 
 #ifdef _WIN32
 static int mkstemp(char* name_template, size_t size_in_chars) {
-  auto path = name_template;
-  if (_mktemp_s(path, size_in_chars) != 0) {
+  std::wstring path;
+  CHECK(android::base::UTF8ToWide(name_template, &path))
+      << "path can't be converted to wchar: " << name_template;
+  if (_wmktemp_s(path.data(), path.size() + 1) != 0) {
     return -1;
   }
 
-  std::wstring path_wide;
-  CHECK(android::base::UTF8ToWide(path, &path_wide))
-      << "path can't be converted to wchar: " << path;
-
   // Use open() to match the close() that TemporaryFile's destructor does.
   // Use O_BINARY to match base file APIs.
-  return _wopen(path_wide.c_str(), O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR);
+  int fd = _wopen(path.c_str(), O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR);
+  if (fd < 0) {
+    return -1;
+  }
+
+  std::string path_utf8;
+  CHECK(android::base::WideToUTF8(path, &path_utf8)) << "path can't be converted to utf8";
+  CHECK(strcpy_s(name_template, size_in_chars, path_utf8.c_str()) == 0)
+      << "utf8 path can't be assigned back to name_template";
+
+  return fd;
 }
 
 static char* mkdtemp(char* name_template, size_t size_in_chars) {
-  auto path = name_template;
-  if (_mktemp_s(path, size_in_chars) != 0) {
+  std::wstring path;
+  CHECK(android::base::UTF8ToWide(name_template, &path))
+      << "path can't be converted to wchar: " << name_template;
+
+  if (_wmktemp_s(path.data(), path.size() + 1) != 0) {
     return nullptr;
   }
 
-  std::wstring path_wide;
-  CHECK(android::base::UTF8ToWide(path, &path_wide))
-      << "path can't be converted to wchar: " << path;
-
-  if (_wmkdir(path_wide.c_str()) != 0) {
+  if (_wmkdir(path.c_str()) != 0) {
     return nullptr;
   }
-  return path;
+
+  std::string path_utf8;
+  CHECK(android::base::WideToUTF8(path, &path_utf8)) << "path can't be converted to utf8";
+  CHECK(strcpy_s(name_template, size_in_chars, path_utf8.c_str()) == 0)
+      << "utf8 path can't be assigned back to name_template";
+
+  return name_template;
 }
 #endif
 
diff --git a/base/file_test.cpp b/base/file_test.cpp
index 65ee235..120228d 100644
--- a/base/file_test.cpp
+++ b/base/file_test.cpp
@@ -30,7 +30,7 @@
 #if !defined(_WIN32)
 #include <pwd.h>
 #else
-#include <processenv.h>
+#include <windows.h>
 #endif
 
 #include "android-base/logging.h"  // and must be after windows.h for ERROR
@@ -99,6 +99,11 @@
   std::wstring old_tmp;
   old_tmp.resize(kMaxEnvVariableValueSize);
   old_tmp.resize(GetEnvironmentVariableW(L"TMP", old_tmp.data(), old_tmp.size()));
+  if (old_tmp.empty()) {
+    // Can't continue with empty TMP folder.
+    return;
+  }
+
   std::wstring new_tmp = old_tmp;
   if (new_tmp.back() != L'\\') {
     new_tmp.push_back(L'\\');
@@ -156,14 +161,18 @@
 TEST(file, RootDirectoryWindows) {
   constexpr auto kMaxEnvVariableValueSize = 32767;
   std::wstring old_tmp;
+  bool tmp_is_empty = false;
   old_tmp.resize(kMaxEnvVariableValueSize);
   old_tmp.resize(GetEnvironmentVariableW(L"TMP", old_tmp.data(), old_tmp.size()));
+  if (old_tmp.empty()) {
+    tmp_is_empty = (GetLastError() == ERROR_ENVVAR_NOT_FOUND);
+  }
   SetEnvironmentVariableW(L"TMP", L"C:");
 
   TemporaryFile tf;
   ASSERT_NE(tf.fd, -1) << tf.path;
 
-  SetEnvironmentVariableW(L"TMP", old_tmp.c_str());
+  SetEnvironmentVariableW(L"TMP", tmp_is_empty ? nullptr : old_tmp.c_str());
 }
 #endif
 
diff --git a/fs_mgr/libdm/dm_table.cpp b/fs_mgr/libdm/dm_table.cpp
index 15c7ce1..efe03ab 100644
--- a/fs_mgr/libdm/dm_table.cpp
+++ b/fs_mgr/libdm/dm_table.cpp
@@ -26,6 +26,7 @@
     if (!target->Valid()) {
         return false;
     }
+    num_sectors_ += target->size();
     targets_.push_back(std::move(target));
     return true;
 }
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index eed21dc..39c908d 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -114,6 +114,7 @@
     ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop_a.device(), 0));
     ASSERT_TRUE(table.Emplace<DmTargetLinear>(1, 1, loop_b.device(), 0));
     ASSERT_TRUE(table.valid());
+    ASSERT_EQ(2u, table.num_sectors());
 
     TempDevice dev("libdm-test-dm-linear", table);
     ASSERT_TRUE(dev.valid());
@@ -176,6 +177,7 @@
     DmTable table;
     ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop_a.device(), 0));
     ASSERT_TRUE(table.valid());
+    ASSERT_EQ(1u, table.num_sectors());
 
     TempDevice dev("libdm-test-dm-suspend-resume", table);
     ASSERT_TRUE(dev.valid());
@@ -292,6 +294,7 @@
     ASSERT_TRUE(origin_table.AddTarget(make_unique<DmTargetSnapshotOrigin>(
             0, kBaseDeviceSize / kSectorSize, base_loop_->device())));
     ASSERT_TRUE(origin_table.valid());
+    ASSERT_EQ(kBaseDeviceSize / kSectorSize, origin_table.num_sectors());
 
     origin_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot-origin", origin_table);
     ASSERT_TRUE(origin_dev_->valid());
@@ -303,6 +306,7 @@
             0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(),
             SnapshotStorageMode::Persistent, 8)));
     ASSERT_TRUE(snap_table.valid());
+    ASSERT_EQ(kBaseDeviceSize / kSectorSize, snap_table.num_sectors());
 
     snapshot_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot", snap_table);
     ASSERT_TRUE(snapshot_dev_->valid());
@@ -322,6 +326,7 @@
             make_unique<DmTargetSnapshot>(0, kBaseDeviceSize / kSectorSize, base_loop_->device(),
                                           cow_loop_->device(), SnapshotStorageMode::Merge, 8)));
     ASSERT_TRUE(merge_table.valid());
+    ASSERT_EQ(kBaseDeviceSize / kSectorSize, merge_table.num_sectors());
 
     DeviceMapper& dm = DeviceMapper::Instance();
     ASSERT_TRUE(dm.LoadTableAndActivate("libdm-test-dm-snapshot", merge_table));
diff --git a/fs_mgr/libdm/include/libdm/loop_control.h b/fs_mgr/libdm/include/libdm/loop_control.h
index eeed6b5..ad53c11 100644
--- a/fs_mgr/libdm/include/libdm/loop_control.h
+++ b/fs_mgr/libdm/include/libdm/loop_control.h
@@ -64,7 +64,8 @@
   public:
     // Create a loop device for the given file descriptor. It is closed when
     // LoopDevice is destroyed only if auto_close is true.
-    LoopDevice(int fd, const std::chrono::milliseconds& timeout_ms, bool auto_close = false);
+    LoopDevice(android::base::borrowed_fd fd, const std::chrono::milliseconds& timeout_ms,
+               bool auto_close = false);
     // Create a loop device for the given file path. It will be opened for
     // reading and writing and closed when the loop device is detached.
     LoopDevice(const std::string& path, const std::chrono::milliseconds& timeout_ms);
@@ -81,8 +82,8 @@
   private:
     void Init(const std::chrono::milliseconds& timeout_ms);
 
-    android::base::unique_fd fd_;
-    bool owns_fd_;
+    android::base::borrowed_fd fd_;
+    android::base::unique_fd owned_fd_;
     std::string device_;
     LoopControl control_;
     bool valid_ = false;
diff --git a/fs_mgr/libdm/loop_control.cpp b/fs_mgr/libdm/loop_control.cpp
index edc9a45..2e40a18 100644
--- a/fs_mgr/libdm/loop_control.cpp
+++ b/fs_mgr/libdm/loop_control.cpp
@@ -133,18 +133,23 @@
     return true;
 }
 
-LoopDevice::LoopDevice(int fd, const std::chrono::milliseconds& timeout_ms, bool auto_close)
-    : fd_(fd), owns_fd_(auto_close) {
+LoopDevice::LoopDevice(android::base::borrowed_fd fd, const std::chrono::milliseconds& timeout_ms,
+                       bool auto_close)
+    : fd_(fd), owned_fd_(-1) {
+    if (auto_close) {
+        owned_fd_.reset(fd.get());
+    }
     Init(timeout_ms);
 }
 
 LoopDevice::LoopDevice(const std::string& path, const std::chrono::milliseconds& timeout_ms)
-    : fd_(-1), owns_fd_(true) {
-    fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC));
-    if (fd_ < -1) {
+    : fd_(-1), owned_fd_(-1) {
+    owned_fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC));
+    if (owned_fd_ == -1) {
         PLOG(ERROR) << "open failed for " << path;
         return;
     }
+    fd_ = owned_fd_;
     Init(timeout_ms);
 }
 
@@ -152,13 +157,10 @@
     if (valid()) {
         control_.Detach(device_);
     }
-    if (!owns_fd_) {
-        (void)fd_.release();
-    }
 }
 
 void LoopDevice::Init(const std::chrono::milliseconds& timeout_ms) {
-    valid_ = control_.Attach(fd_, timeout_ms, &device_);
+    valid_ = control_.Attach(fd_.get(), timeout_ms, &device_);
 }
 
 }  // namespace dm
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 834bf3b..8cf0f3b 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -49,11 +49,17 @@
         "libfiemap_headers",
     ],
     export_include_dirs: ["include"],
+    proto: {
+        type: "lite",
+        export_proto_headers: true,
+        canonical_path_from_root: false,
+    },
 }
 
 filegroup {
     name: "libsnapshot_sources",
     srcs: [
+        "android/snapshot/snapshot.proto",
         "snapshot.cpp",
         "snapshot_metadata_updater.cpp",
         "partition_cow_creator.cpp",
@@ -132,9 +138,10 @@
         "libbinder",
         "libext4_utils",
         "libfs_mgr",
-        "libutils",
         "liblog",
         "liblp",
+        "libprotobuf-cpp-lite",
+        "libutils",
     ],
     init_rc: [
         "snapshotctl.rc",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
new file mode 100644
index 0000000..629c3a4
--- /dev/null
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 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.
+
+syntax = "proto3";
+package android.snapshot;
+
+option optimize_for = LITE_RUNTIME;
+
+// Next: 4
+enum SnapshotState {
+    // No snapshot is found.
+    NONE = 0;
+
+    // The snapshot has been created and possibly written to. Rollbacks are
+    // possible by destroying the snapshot.
+    CREATED = 1;
+
+    // Changes are being merged. No rollbacks are possible beyond this point.
+    MERGING = 2;
+
+    // Changes have been merged, Future reboots may map the base device
+    // directly.
+    MERGE_COMPLETED = 3;
+}
+
+// Next: 9
+message SnapshotStatus {
+    // Name of the snapshot. This is usually the name of the snapshotted
+    // logical partition; for example, "system_b".
+    string name = 1;
+
+    SnapshotState state = 2;
+
+    // Size of the full (base) device.
+    uint64 device_size = 3;
+
+    // Size of the snapshot. This is the sum of lengths of ranges in the base
+    // device that needs to be snapshotted during the update.
+    // This must be less than or equal to |device_size|.
+    // This value is 0 if no snapshot is needed for this device because
+    // no changes
+    uint64 snapshot_size = 4;
+
+    // Size of the "COW partition". A COW partition is a special logical
+    // partition represented in the super partition metadata. This partition and
+    // the "COW image" form the "COW device" that supports the snapshot device.
+    //
+    // When SnapshotManager creates a COW device, it first searches for unused
+    // blocks in the super partition, and use those before creating the COW
+    // image if the COW partition is not big enough.
+    //
+    // This value is 0 if no space in super is left for the COW partition.
+    // |cow_partition_size + cow_file_size| must not be zero if |snapshot_size|
+    // is non-zero.
+    uint64 cow_partition_size = 5;
+
+    // Size of the "COW file", or "COW image". A COW file / image is created
+    // when the "COW partition" is not big enough to store changes to the
+    // snapshot device.
+    //
+    // This value is 0 if |cow_partition_size| is big enough to hold all changes
+    // to the snapshot device.
+    uint64 cow_file_size = 6;
+
+    // Sectors allocated for the COW device. Recording this value right after
+    // the update and before the merge allows us to infer the progress of the
+    // merge process.
+    // This is non-zero when |state| == MERGING or MERGE_COMPLETED.
+    uint64 sectors_allocated = 7;
+
+    // Metadata sectors allocated for the COW device. Recording this value right
+    // before the update and before the merge allows us to infer the progress of
+    // the merge process.
+    // This is non-zero when |state| == MERGING or MERGE_COMPLETED.
+    uint64 metadata_sectors = 8;
+}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 0d6aa2c..69f2895 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -53,8 +53,9 @@
 
 struct AutoDeleteCowImage;
 struct AutoDeleteSnapshot;
-struct PartitionCowCreator;
 struct AutoDeviceList;
+struct PartitionCowCreator;
+class SnapshotStatus;
 
 static constexpr const std::string_view kCowGroupName = "cow";
 
@@ -250,22 +251,6 @@
     std::unique_ptr<LockedFile> OpenFile(const std::string& file, int open_flags, int lock_flags);
     bool Truncate(LockedFile* file);
 
-    enum class SnapshotState : int { None, Created, Merging, MergeCompleted };
-    static std::string to_string(SnapshotState state);
-
-    // This state is persisted per-snapshot in /metadata/ota/snapshots/.
-    struct SnapshotStatus {
-        SnapshotState state = SnapshotState::None;
-        uint64_t device_size = 0;
-        uint64_t snapshot_size = 0;
-        uint64_t cow_partition_size = 0;
-        uint64_t cow_file_size = 0;
-
-        // These are non-zero when merging.
-        uint64_t sectors_allocated = 0;
-        uint64_t metadata_sectors = 0;
-    };
-
     // Create a new snapshot record. This creates the backing COW store and
     // persists information needed to map the device. The device can be mapped
     // with MapSnapshot().
@@ -282,7 +267,7 @@
     //
     // All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes
     // must be a multiple of the sector size (512 bytes).
-    bool CreateSnapshot(LockedFile* lock, const std::string& name, SnapshotStatus status);
+    bool CreateSnapshot(LockedFile* lock, SnapshotStatus* status);
 
     // |name| should be the base partition name (e.g. "system_a"). Create the
     // backing COW image using the size previously passed to CreateSnapshot().
@@ -363,8 +348,7 @@
     UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name);
 
     // Interact with status files under /metadata/ota/snapshots.
-    bool WriteSnapshotStatus(LockedFile* lock, const std::string& name,
-                             const SnapshotStatus& status);
+    bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status);
     bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status);
     std::string GetSnapshotStatusFilePath(const std::string& name);
 
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 404ef27..eedc1cd 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -18,6 +18,7 @@
 
 #include <android-base/logging.h>
 
+#include <android/snapshot/snapshot.pb.h>
 #include "utility.h"
 
 using android::dm::kSectorSize;
@@ -84,13 +85,14 @@
             << "logical_block_size is not power of 2";
 
     Return ret;
-    ret.snapshot_status.device_size = target_partition->size();
+    ret.snapshot_status.set_name(target_partition->name());
+    ret.snapshot_status.set_device_size(target_partition->size());
 
     // TODO(b/141889746): Optimize by using a smaller snapshot. Some ranges in target_partition
     // may be written directly.
-    ret.snapshot_status.snapshot_size = target_partition->size();
+    ret.snapshot_status.set_snapshot_size(target_partition->size());
 
-    auto cow_size = GetCowSize(ret.snapshot_status.snapshot_size);
+    auto cow_size = GetCowSize(ret.snapshot_status.snapshot_size());
     if (!cow_size.has_value()) return std::nullopt;
 
     // Compute regions that are free in both current and target metadata. These are the regions
@@ -106,18 +108,20 @@
     LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes";
 
     // Compute the COW partition size.
-    ret.snapshot_status.cow_partition_size = std::min(*cow_size, free_region_length);
+    uint64_t cow_partition_size = std::min(*cow_size, free_region_length);
     // Round it down to the nearest logical block. Logical partitions must be a multiple
     // of logical blocks.
-    ret.snapshot_status.cow_partition_size &= ~(logical_block_size - 1);
+    cow_partition_size &= ~(logical_block_size - 1);
+    ret.snapshot_status.set_cow_partition_size(cow_partition_size);
     // Assign cow_partition_usable_regions to indicate what regions should the COW partition uses.
     ret.cow_partition_usable_regions = std::move(free_regions);
 
     // The rest of the COW space is allocated on ImageManager.
-    ret.snapshot_status.cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size;
+    uint64_t cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size();
     // Round it up to the nearest sector.
-    ret.snapshot_status.cow_file_size += kSectorSize - 1;
-    ret.snapshot_status.cow_file_size &= ~(kSectorSize - 1);
+    cow_file_size += kSectorSize - 1;
+    cow_file_size &= ~(kSectorSize - 1);
+    ret.snapshot_status.set_cow_file_size(cow_file_size);
 
     return ret;
 }
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index 0e645c6..8888f78 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -20,8 +20,9 @@
 #include <string>
 
 #include <liblp/builder.h>
+#include <update_engine/update_metadata.pb.h>
 
-#include <libsnapshot/snapshot.h>
+#include <android/snapshot/snapshot.pb.h>
 
 namespace android {
 namespace snapshot {
@@ -51,7 +52,7 @@
     const RepeatedPtrField<InstallOperation>* operations = nullptr;
 
     struct Return {
-        SnapshotManager::SnapshotStatus snapshot_status;
+        SnapshotStatus snapshot_status;
         std::vector<Interval> cow_partition_usable_regions;
     };
 
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index ccd087e..feb3c2d 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -51,8 +51,8 @@
                                 .current_suffix = "_a"};
     auto ret = creator.Run();
     ASSERT_TRUE(ret.has_value());
-    ASSERT_EQ(40 * 1024, ret->snapshot_status.device_size);
-    ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size);
+    ASSERT_EQ(40 * 1024, ret->snapshot_status.device_size());
+    ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size());
 }
 
 TEST_F(PartitionCowCreatorTest, Holes) {
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index e085bc5..5b758c9 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -38,6 +38,7 @@
 #include <libfiemap/image_manager.h>
 #include <liblp/liblp.h>
 
+#include <android/snapshot/snapshot.pb.h>
 #include "partition_cow_creator.h"
 #include "snapshot_metadata_updater.h"
 #include "utility.h"
@@ -234,42 +235,50 @@
     return WriteUpdateState(lock.get(), UpdateState::Unverified);
 }
 
-bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
-                                     SnapshotManager::SnapshotStatus status) {
+bool SnapshotManager::CreateSnapshot(LockedFile* lock, SnapshotStatus* status) {
     CHECK(lock);
     CHECK(lock->lock_mode() == LOCK_EX);
+    CHECK(status);
+
+    if (status->name().empty()) {
+        LOG(ERROR) << "SnapshotStatus has no name.";
+        return false;
+    }
     // Sanity check these sizes. Like liblp, we guarantee the partition size
     // is respected, which means it has to be sector-aligned. (This guarantee
     // is useful for locating avb footers correctly). The COW file size, however,
     // can be arbitrarily larger than specified, so we can safely round it up.
-    if (status.device_size % kSectorSize != 0) {
-        LOG(ERROR) << "Snapshot " << name
-                   << " device size is not a multiple of the sector size: " << status.device_size;
+    if (status->device_size() % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << status->name()
+                   << " device size is not a multiple of the sector size: "
+                   << status->device_size();
         return false;
     }
-    if (status.snapshot_size % kSectorSize != 0) {
-        LOG(ERROR) << "Snapshot " << name << " snapshot size is not a multiple of the sector size: "
-                   << status.snapshot_size;
+    if (status->snapshot_size() % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << status->name()
+                   << " snapshot size is not a multiple of the sector size: "
+                   << status->snapshot_size();
         return false;
     }
-    if (status.cow_partition_size % kSectorSize != 0) {
-        LOG(ERROR) << "Snapshot " << name
+    if (status->cow_partition_size() % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << status->name()
                    << " cow partition size is not a multiple of the sector size: "
-                   << status.cow_partition_size;
+                   << status->cow_partition_size();
         return false;
     }
-    if (status.cow_file_size % kSectorSize != 0) {
-        LOG(ERROR) << "Snapshot " << name << " cow file size is not a multiple of the sector size: "
-                   << status.cow_partition_size;
+    if (status->cow_file_size() % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << status->name()
+                   << " cow file size is not a multiple of the sector size: "
+                   << status->cow_file_size();
         return false;
     }
 
-    status.state = SnapshotState::Created;
-    status.sectors_allocated = 0;
-    status.metadata_sectors = 0;
+    status->set_state(SnapshotState::CREATED);
+    status->set_sectors_allocated(0);
+    status->set_metadata_sectors(0);
 
-    if (!WriteSnapshotStatus(lock, name, status)) {
-        PLOG(ERROR) << "Could not write snapshot status: " << name;
+    if (!WriteSnapshotStatus(lock, *status)) {
+        PLOG(ERROR) << "Could not write snapshot status: " << status->name();
         return false;
     }
     return true;
@@ -287,15 +296,15 @@
 
     // The COW file size should have been rounded up to the nearest sector in CreateSnapshot.
     // Sanity check this.
-    if (status.cow_file_size % kSectorSize != 0) {
+    if (status.cow_file_size() % kSectorSize != 0) {
         LOG(ERROR) << "Snapshot " << name << " COW file size is not a multiple of the sector size: "
-                   << status.cow_file_size;
+                   << status.cow_file_size();
         return false;
     }
 
     std::string cow_image_name = GetCowImageDeviceName(name);
     int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT;
-    return images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags);
+    return images_->CreateBackingImage(cow_image_name, status.cow_file_size(), cow_flags);
 }
 
 bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
@@ -309,7 +318,7 @@
     if (!ReadSnapshotStatus(lock, name, &status)) {
         return false;
     }
-    if (status.state == SnapshotState::MergeCompleted) {
+    if (status.state() == SnapshotState::MERGE_COMPLETED) {
         LOG(ERROR) << "Should not create a snapshot device for " << name
                    << " after merging has completed.";
         return false;
@@ -328,24 +337,23 @@
             PLOG(ERROR) << "Could not determine block device size: " << base_device;
             return false;
         }
-        if (status.device_size != dev_size) {
+        if (status.device_size() != dev_size) {
             LOG(ERROR) << "Block device size for " << base_device << " does not match"
-                       << "(expected " << status.device_size << ", got " << dev_size << ")";
+                       << "(expected " << status.device_size() << ", got " << dev_size << ")";
             return false;
         }
     }
-    if (status.device_size % kSectorSize != 0) {
-        LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size;
+    if (status.device_size() % kSectorSize != 0) {
+        LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size();
         return false;
     }
-    if (status.snapshot_size % kSectorSize != 0 || status.snapshot_size > status.device_size) {
-        LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size;
+    if (status.snapshot_size() % kSectorSize != 0 ||
+        status.snapshot_size() > status.device_size()) {
+        LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size();
         return false;
     }
-    uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
-    uint64_t linear_sectors = (status.device_size - status.snapshot_size) / kSectorSize;
-
-
+    uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
+    uint64_t linear_sectors = (status.device_size() - status.snapshot_size()) / kSectorSize;
 
     auto& dm = DeviceMapper::Instance();
 
@@ -557,8 +565,9 @@
     if (!ReadSnapshotStatus(lock, name, &status)) {
         return false;
     }
-    if (status.state != SnapshotState::Created) {
-        LOG(WARNING) << "Snapshot " << name << " has unexpected state: " << to_string(status.state);
+    if (status.state() != SnapshotState::CREATED) {
+        LOG(WARNING) << "Snapshot " << name
+                     << " has unexpected state: " << SnapshotState_Name(status.state());
     }
 
     // After this, we return true because we technically did switch to a merge
@@ -568,15 +577,15 @@
         return false;
     }
 
-    status.state = SnapshotState::Merging;
+    status.set_state(SnapshotState::MERGING);
 
     DmTargetSnapshot::Status dm_status;
     if (!QuerySnapshotStatus(dm_name, nullptr, &dm_status)) {
         LOG(ERROR) << "Could not query merge status for snapshot: " << dm_name;
     }
-    status.sectors_allocated = dm_status.sectors_allocated;
-    status.metadata_sectors = dm_status.metadata_sectors;
-    if (!WriteSnapshotStatus(lock, name, status)) {
+    status.set_sectors_allocated(dm_status.sectors_allocated);
+    status.set_metadata_sectors(dm_status.metadata_sectors);
+    if (!WriteSnapshotStatus(lock, status)) {
         LOG(ERROR) << "Could not update status file for snapshot: " << name;
     }
     return true;
@@ -821,7 +830,7 @@
         // rebooted after this check, the device will still be a snapshot-merge
         // target. If the have rebooted, the device will now be a linear target,
         // and we can try cleanup again.
-        if (snapshot_status.state == SnapshotState::MergeCompleted) {
+        if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
             // NB: It's okay if this fails now, we gave cleanup our best effort.
             OnSnapshotMergeComplete(lock, name, snapshot_status);
             return UpdateState::MergeCompleted;
@@ -849,7 +858,7 @@
 
     // These two values are equal when merging is complete.
     if (status.sectors_allocated != status.metadata_sectors) {
-        if (snapshot_status.state == SnapshotState::MergeCompleted) {
+        if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
             LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
             return UpdateState::MergeFailed;
         }
@@ -864,8 +873,8 @@
     // This makes it simpler to reason about the next reboot: no matter what
     // part of cleanup failed, first-stage init won't try to create another
     // snapshot device for this partition.
-    snapshot_status.state = SnapshotState::MergeCompleted;
-    if (!WriteSnapshotStatus(lock, name, snapshot_status)) {
+    snapshot_status.set_state(SnapshotState::MERGE_COMPLETED);
+    if (!WriteSnapshotStatus(lock, snapshot_status)) {
         return UpdateState::MergeFailed;
     }
     if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
@@ -969,10 +978,10 @@
         return false;
     }
 
-    uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
-    if (snapshot_sectors * kSectorSize != status.snapshot_size) {
+    uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
+    if (snapshot_sectors * kSectorSize != status.snapshot_size()) {
         LOG(ERROR) << "Snapshot " << name
-                   << " size is not sector aligned: " << status.snapshot_size;
+                   << " size is not sector aligned: " << status.snapshot_size();
         return false;
     }
 
@@ -1003,7 +1012,7 @@
                        << " sectors, got: " << outer_table[0].spec.length;
             return false;
         }
-        uint64_t expected_device_sectors = status.device_size / kSectorSize;
+        uint64_t expected_device_sectors = status.device_size() / kSectorSize;
         uint64_t actual_device_sectors = outer_table[0].spec.length + outer_table[1].spec.length;
         if (expected_device_sectors != actual_device_sectors) {
             LOG(ERROR) << "Outer device " << name << " should have " << expected_device_sectors
@@ -1289,7 +1298,7 @@
             return false;
         }
         // No live snapshot if merge is completed.
-        if (live_snapshot_status->state == SnapshotState::MergeCompleted) {
+        if (live_snapshot_status->state() == SnapshotState::MERGE_COMPLETED) {
             live_snapshot_status.reset();
         }
     } while (0);
@@ -1390,7 +1399,7 @@
                                     AutoDeviceList* created_devices, std::string* cow_name) {
     CHECK(lock);
     if (!EnsureImageManager()) return false;
-    CHECK(snapshot_status.cow_partition_size + snapshot_status.cow_file_size > 0);
+    CHECK(snapshot_status.cow_partition_size() + snapshot_status.cow_file_size() > 0);
     auto begin = std::chrono::steady_clock::now();
 
     std::string partition_name = params.GetPartitionName();
@@ -1400,7 +1409,7 @@
     auto& dm = DeviceMapper::Instance();
 
     // Map COW image if necessary.
-    if (snapshot_status.cow_file_size > 0) {
+    if (snapshot_status.cow_file_size() > 0) {
         auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
         if (remaining_time.count() < 0) return false;
 
@@ -1411,7 +1420,7 @@
         created_devices->EmplaceBack<AutoUnmapImage>(images_.get(), cow_image_name);
 
         // If no COW partition exists, just return the image alone.
-        if (snapshot_status.cow_partition_size == 0) {
+        if (snapshot_status.cow_partition_size() == 0) {
             *cow_name = std::move(cow_image_name);
             LOG(INFO) << "Mapped COW image for " << partition_name << " at " << *cow_name;
             return true;
@@ -1421,7 +1430,7 @@
     auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
-    CHECK(snapshot_status.cow_partition_size > 0);
+    CHECK(snapshot_status.cow_partition_size() > 0);
 
     // Create the DmTable for the COW device. It is the DmTable of the COW partition plus
     // COW image device as the last extent.
@@ -1434,14 +1443,14 @@
         return false;
     }
     // If the COW image exists, append it as the last extent.
-    if (snapshot_status.cow_file_size > 0) {
+    if (snapshot_status.cow_file_size() > 0) {
         std::string cow_image_device;
         if (!dm.GetDeviceString(cow_image_name, &cow_image_device)) {
             LOG(ERROR) << "Cannot determine major/minor for: " << cow_image_name;
             return false;
         }
-        auto cow_partition_sectors = snapshot_status.cow_partition_size / kSectorSize;
-        auto cow_image_sectors = snapshot_status.cow_file_size / kSectorSize;
+        auto cow_partition_sectors = snapshot_status.cow_partition_size() / kSectorSize;
+        auto cow_image_sectors = snapshot_status.cow_file_size() / kSectorSize;
         table.Emplace<DmTargetLinear>(cow_partition_sectors, cow_image_sectors, cow_image_device,
                                       0);
     }
@@ -1602,101 +1611,38 @@
         return false;
     }
 
-    std::string contents;
-    if (!android::base::ReadFdToString(fd, &contents)) {
-        PLOG(ERROR) << "read failed: " << path;
-        return false;
-    }
-    auto pieces = android::base::Split(contents, " ");
-    if (pieces.size() != 7) {
-        LOG(ERROR) << "Invalid status line for snapshot: " << path;
+    if (!status->ParseFromFileDescriptor(fd.get())) {
+        PLOG(ERROR) << "Unable to parse " << path << " as SnapshotStatus";
         return false;
     }
 
-    if (pieces[0] == "none") {
-        status->state = SnapshotState::None;
-    } else if (pieces[0] == "created") {
-        status->state = SnapshotState::Created;
-    } else if (pieces[0] == "merging") {
-        status->state = SnapshotState::Merging;
-    } else if (pieces[0] == "merge-completed") {
-        status->state = SnapshotState::MergeCompleted;
-    } else {
-        LOG(ERROR) << "Unrecognized state " << pieces[0] << " for snapshot: " << name;
-        return false;
+    if (status->name() != name) {
+        LOG(WARNING) << "Found snapshot status named " << status->name() << " in " << path;
+        status->set_name(name);
     }
 
-    if (!android::base::ParseUint(pieces[1], &status->device_size)) {
-        LOG(ERROR) << "Invalid device size in status line for: " << path;
-        return false;
-    }
-    if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) {
-        LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
-        return false;
-    }
-    if (!android::base::ParseUint(pieces[3], &status->cow_partition_size)) {
-        LOG(ERROR) << "Invalid cow linear size in status line for: " << path;
-        return false;
-    }
-    if (!android::base::ParseUint(pieces[4], &status->cow_file_size)) {
-        LOG(ERROR) << "Invalid cow file size in status line for: " << path;
-        return false;
-    }
-    if (!android::base::ParseUint(pieces[5], &status->sectors_allocated)) {
-        LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
-        return false;
-    }
-    if (!android::base::ParseUint(pieces[6], &status->metadata_sectors)) {
-        LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
-        return false;
-    }
     return true;
 }
 
-std::string SnapshotManager::to_string(SnapshotState state) {
-    switch (state) {
-        case SnapshotState::None:
-            return "none";
-        case SnapshotState::Created:
-            return "created";
-        case SnapshotState::Merging:
-            return "merging";
-        case SnapshotState::MergeCompleted:
-            return "merge-completed";
-        default:
-            LOG(ERROR) << "Unknown snapshot state: " << (int)state;
-            return "unknown";
-    }
-}
-
-bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const std::string& name,
-                                          const SnapshotStatus& status) {
+bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status) {
     // The caller must take an exclusive lock to modify snapshots.
     CHECK(lock);
     CHECK(lock->lock_mode() == LOCK_EX);
+    CHECK(!status.name().empty());
 
-    auto path = GetSnapshotStatusFilePath(name);
-    unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC, 0660));
+    auto path = GetSnapshotStatusFilePath(status.name());
+    unique_fd fd(
+            open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC | O_TRUNC, 0660));
     if (fd < 0) {
         PLOG(ERROR) << "Open failed: " << path;
         return false;
     }
 
-    std::vector<std::string> pieces = {
-            to_string(status.state),
-            std::to_string(status.device_size),
-            std::to_string(status.snapshot_size),
-            std::to_string(status.cow_partition_size),
-            std::to_string(status.cow_file_size),
-            std::to_string(status.sectors_allocated),
-            std::to_string(status.metadata_sectors),
-    };
-    auto contents = android::base::Join(pieces, " ");
-
-    if (!android::base::WriteStringToFd(contents, fd)) {
-        PLOG(ERROR) << "write failed: " << path;
+    if (!status.SerializeToFileDescriptor(fd.get())) {
+        PLOG(ERROR) << "Unable to write SnapshotStatus to " << path;
         return false;
     }
+
     return true;
 }
 
@@ -1714,7 +1660,7 @@
 
 std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name,
                                                    const SnapshotStatus& status) {
-    if (status.device_size != status.snapshot_size) {
+    if (status.device_size() != status.snapshot_size()) {
         return GetSnapshotExtraDeviceName(snapshot_name);
     }
     return snapshot_name;
@@ -1884,11 +1830,11 @@
         }
 
         LOG(INFO) << "For partition " << target_partition->name()
-                  << ", device size = " << cow_creator_ret->snapshot_status.device_size
-                  << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size
+                  << ", device size = " << cow_creator_ret->snapshot_status.device_size()
+                  << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size()
                   << ", cow partition size = "
-                  << cow_creator_ret->snapshot_status.cow_partition_size
-                  << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size;
+                  << cow_creator_ret->snapshot_status.cow_partition_size()
+                  << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size();
 
         // Delete any existing snapshot before re-creating one.
         if (!DeleteSnapshot(lock, target_partition->name())) {
@@ -1899,9 +1845,9 @@
 
         // It is possible that the whole partition uses free space in super, and snapshot / COW
         // would not be needed. In this case, skip the partition.
-        bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size > 0;
-        bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size +
-                          cow_creator_ret->snapshot_status.cow_file_size) > 0;
+        bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size() > 0;
+        bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size() +
+                          cow_creator_ret->snapshot_status.cow_file_size()) > 0;
         CHECK(needs_snapshot == needs_cow);
 
         if (!needs_snapshot) {
@@ -1911,17 +1857,17 @@
         }
 
         // Store these device sizes to snapshot status file.
-        if (!CreateSnapshot(lock, target_partition->name(), cow_creator_ret->snapshot_status)) {
+        if (!CreateSnapshot(lock, &cow_creator_ret->snapshot_status)) {
             return false;
         }
         created_devices->EmplaceBack<AutoDeleteSnapshot>(this, lock, target_partition->name());
 
         // Create the COW partition. That is, use any remaining free space in super partition before
         // creating the COW images.
-        if (cow_creator_ret->snapshot_status.cow_partition_size > 0) {
-            CHECK(cow_creator_ret->snapshot_status.cow_partition_size % kSectorSize == 0)
+        if (cow_creator_ret->snapshot_status.cow_partition_size() > 0) {
+            CHECK(cow_creator_ret->snapshot_status.cow_partition_size() % kSectorSize == 0)
                     << "cow_partition_size == "
-                    << cow_creator_ret->snapshot_status.cow_partition_size
+                    << cow_creator_ret->snapshot_status.cow_partition_size()
                     << " is not a multiple of sector size " << kSectorSize;
             auto cow_partition = target_metadata->AddPartition(GetCowName(target_partition->name()),
                                                                kCowGroupName, 0 /* flags */);
@@ -1930,10 +1876,10 @@
             }
 
             if (!target_metadata->ResizePartition(
-                        cow_partition, cow_creator_ret->snapshot_status.cow_partition_size,
+                        cow_partition, cow_creator_ret->snapshot_status.cow_partition_size(),
                         cow_creator_ret->cow_partition_usable_regions)) {
                 LOG(ERROR) << "Cannot create COW partition on metadata with size "
-                           << cow_creator_ret->snapshot_status.cow_partition_size;
+                           << cow_creator_ret->snapshot_status.cow_partition_size();
                 return false;
             }
             // Only the in-memory target_metadata is modified; nothing to clean up if there is an
@@ -1941,7 +1887,7 @@
         }
 
         // Create the backing COW image if necessary.
-        if (cow_creator_ret->snapshot_status.cow_file_size > 0) {
+        if (cow_creator_ret->snapshot_status.cow_file_size() > 0) {
             if (!CreateCowImage(lock, target_partition->name())) {
                 return false;
             }
@@ -1978,7 +1924,7 @@
         }
 
         auto it = all_snapshot_status.find(target_partition->name());
-        CHECK(it != all_snapshot_status.end()) << target_partition->name();
+        if (it == all_snapshot_status.end()) continue;
         cow_params.partition_name = target_partition->name();
         std::string cow_name;
         if (!MapCowDevices(lock, cow_params, it->second, &created_devices_for_cow, &cow_name)) {
@@ -2049,13 +1995,13 @@
             ok = false;
             continue;
         }
-        ss << "    state: " << to_string(status.state) << std::endl;
-        ss << "    device size (bytes): " << status.device_size << std::endl;
-        ss << "    snapshot size (bytes): " << status.snapshot_size << std::endl;
-        ss << "    cow partition size (bytes): " << status.cow_partition_size << std::endl;
-        ss << "    cow file size (bytes): " << status.cow_file_size << std::endl;
-        ss << "    allocated sectors: " << status.sectors_allocated << std::endl;
-        ss << "    metadata sectors: " << status.metadata_sectors << std::endl;
+        ss << "    state: " << SnapshotState_Name(status.state()) << std::endl;
+        ss << "    device size (bytes): " << status.device_size() << std::endl;
+        ss << "    snapshot size (bytes): " << status.snapshot_size() << std::endl;
+        ss << "    cow partition size (bytes): " << status.cow_partition_size() << std::endl;
+        ss << "    cow file size (bytes): " << status.cow_file_size() << std::endl;
+        ss << "    allocated sectors: " << status.sectors_allocated() << std::endl;
+        ss << "    metadata sectors: " << status.metadata_sectors() << std::endl;
     }
     os << ss.rdbuf();
     return ok;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index f3994c1..fd7754e 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -33,6 +33,7 @@
 #include <liblp/builder.h>
 #include <storage_literals/storage_literals.h>
 
+#include <android/snapshot/snapshot.pb.h>
 #include "test_helpers.h"
 #include "utility.h"
 
@@ -272,10 +273,12 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kDeviceSize,
-                                    .cow_file_size = kDeviceSize}));
+    SnapshotStatus status;
+    status.set_name("test-snapshot");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kDeviceSize);
+    status.set_cow_file_size(kDeviceSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::vector<std::string> snapshots;
@@ -285,11 +288,11 @@
 
     // Scope so delete can re-acquire the snapshot file lock.
     {
-        SnapshotManager::SnapshotStatus status;
+        SnapshotStatus status;
         ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status));
-        ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created);
-        ASSERT_EQ(status.device_size, kDeviceSize);
-        ASSERT_EQ(status.snapshot_size, kDeviceSize);
+        ASSERT_EQ(status.state(), SnapshotState::CREATED);
+        ASSERT_EQ(status.device_size(), kDeviceSize);
+        ASSERT_EQ(status.snapshot_size(), kDeviceSize);
     }
 
     ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
@@ -301,10 +304,12 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kDeviceSize,
-                                    .cow_file_size = kDeviceSize}));
+    SnapshotStatus status;
+    status.set_name("test-snapshot");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kDeviceSize);
+    status.set_cow_file_size(kDeviceSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device;
@@ -324,10 +329,12 @@
 
     static const uint64_t kSnapshotSize = 1024 * 1024;
     static const uint64_t kDeviceSize = 1024 * 1024 * 2;
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kSnapshotSize,
-                                    .cow_file_size = kSnapshotSize}));
+    SnapshotStatus status;
+    status.set_name("test-snapshot");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kSnapshotSize);
+    status.set_cow_file_size(kSnapshotSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device;
@@ -377,10 +384,12 @@
     ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
     ASSERT_TRUE(MapUpdatePartitions());
     ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b-base", &base_device));
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kDeviceSize,
-                                    .cow_file_size = kDeviceSize}));
+    SnapshotStatus status;
+    status.set_name("test_partition_b");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kDeviceSize);
+    status.set_cow_file_size(kDeviceSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test_partition_b"));
     ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device));
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
@@ -436,10 +445,12 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kDeviceSize,
-                                    .cow_file_size = kDeviceSize}));
+    SnapshotStatus status;
+    status.set_name("test-snapshot");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kDeviceSize);
+    status.set_cow_file_size(kDeviceSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device, cow_device, snap_device;
@@ -492,10 +503,12 @@
 
     ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
     ASSERT_TRUE(MapUpdatePartitions());
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kDeviceSize,
-                                    .cow_file_size = kDeviceSize}));
+    SnapshotStatus status;
+    status.set_name("test_partition_b");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kDeviceSize);
+    status.set_cow_file_size(kDeviceSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test_partition_b"));
 
     // Simulate a reboot into the new slot.
@@ -511,9 +524,8 @@
     ASSERT_TRUE(AcquireLock());
 
     // Validate that we have a snapshot device.
-    SnapshotManager::SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
-    ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created);
+    ASSERT_EQ(status.state(), SnapshotState::CREATED);
 
     DeviceMapper::TargetInfo target;
     auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status);
@@ -528,10 +540,12 @@
 
     ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
     ASSERT_TRUE(MapUpdatePartitions());
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kDeviceSize,
-                                    .cow_file_size = kDeviceSize}));
+    SnapshotStatus status;
+    status.set_name("test_partition_b");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kDeviceSize);
+    status.set_cow_file_size(kDeviceSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test_partition_b"));
 
     // Simulate a reboot into the new slot.
@@ -550,7 +564,6 @@
 
     ASSERT_TRUE(AcquireLock());
 
-    SnapshotManager::SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
 
     // We should not get a snapshot device now.
@@ -570,10 +583,12 @@
 
     ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
     ASSERT_TRUE(MapUpdatePartitions());
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
-                                   {.device_size = kDeviceSize,
-                                    .snapshot_size = kDeviceSize,
-                                    .cow_file_size = kDeviceSize}));
+    SnapshotStatus status;
+    status.set_name("test_partition_b");
+    status.set_device_size(kDeviceSize);
+    status.set_snapshot_size(kDeviceSize);
+    status.set_cow_file_size(kDeviceSize);
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
     ASSERT_TRUE(CreateCowImage("test_partition_b"));
 
     // Simulate a reboot into the new slot.
@@ -699,11 +714,11 @@
         }
         auto local_lock = std::move(lock_);
 
-        SnapshotManager::SnapshotStatus status;
+        SnapshotStatus status;
         if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) {
             return std::nullopt;
         }
-        return status.snapshot_size;
+        return status.snapshot_size();
     }
 
     AssertionResult UnmapAll() {
@@ -869,8 +884,9 @@
     {
         ASSERT_TRUE(AcquireLock());
         auto local_lock = std::move(lock_);
-        ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), "sys_b",
-                                            SnapshotManager::SnapshotStatus{}));
+        SnapshotStatus status;
+        status.set_name("sys_b");
+        ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), status));
         ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB,
                                                        IImageManager::CREATE_IMAGE_DEFAULT));
     }
diff --git a/init/Android.mk b/init/Android.mk
index 62e452f..8fc44da 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -113,6 +113,7 @@
     libbacktrace \
     libmodprobe \
     libext2_uuid \
+    libprotobuf-cpp-lite \
     libsnapshot_nobinder \
 
 LOCAL_SANITIZE := signed-integer-overflow
diff --git a/init/property_service.cpp b/init/property_service.cpp
index f5d1143..d7e4021 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -963,6 +963,10 @@
         // Don't check for failure here, so we always have a sane list of properties.
         // E.g. In case of recovery, the vendor partition will not have mounted and we
         // still need the system / platform properties to function.
+        if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
+            LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
+                                     &property_infos);
+        }
         if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                       &property_infos)) {
             // Fallback to nonplat_* if vendor_* doesn't exist.
@@ -980,6 +984,7 @@
         if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
             return;
         }
+        LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
         if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
             // Fallback to nonplat_* if vendor_* doesn't exist.
             LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
diff --git a/liblog/fake_writer.cpp b/liblog/fake_writer.cpp
index 4d07caa..f1ddff1 100644
--- a/liblog/fake_writer.cpp
+++ b/liblog/fake_writer.cpp
@@ -24,6 +24,7 @@
 #include "log_portability.h"
 #include "logger.h"
 
+static int fakeAvailable(log_id_t);
 static int fakeOpen();
 static void fakeClose();
 static int fakeWrite(log_id_t log_id, struct timespec* ts, struct iovec* vec, size_t nr);
@@ -34,12 +35,16 @@
     .name = "fake",
     .logMask = 0,
     .context.priv = &logFds,
-    .available = NULL,
+    .available = fakeAvailable,
     .open = fakeOpen,
     .close = fakeClose,
     .write = fakeWrite,
 };
 
+static int fakeAvailable(log_id_t) {
+  return 0;
+}
+
 static int fakeOpen() {
   int i;
 
diff --git a/liblog/logd_reader.cpp b/liblog/logd_reader.cpp
index e372dce..916a428 100644
--- a/liblog/logd_reader.cpp
+++ b/liblog/logd_reader.cpp
@@ -23,6 +23,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/param.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -38,9 +39,6 @@
 #include "logd_reader.h"
 #include "logger.h"
 
-/* branchless on many architectures. */
-#define min(x, y) ((y) ^ (((x) ^ (y)) & -((x) < (y))))
-
 static int logdAvailable(log_id_t LogId);
 static int logdVersion(struct android_log_logger* logger,
                        struct android_log_transport_context* transp);
@@ -278,13 +276,13 @@
   size_t n;
 
   n = snprintf(cp, remaining, "getStatistics");
-  n = min(n, remaining);
+  n = MIN(n, remaining);
   remaining -= n;
   cp += n;
 
   logger_for_each(logger, logger_list) {
     n = snprintf(cp, remaining, " %d", logger->logId);
-    n = min(n, remaining);
+    n = MIN(n, remaining);
     remaining -= n;
     cp += n;
   }
@@ -361,7 +359,7 @@
   remaining = sizeof(buffer) - (cp - buffer);
   logger_for_each(logger, logger_list) {
     ret = snprintf(cp, remaining, "%c%u", c, logger->logId);
-    ret = min(ret, remaining);
+    ret = MIN(ret, remaining);
     remaining -= ret;
     cp += ret;
     c = ',';
@@ -369,7 +367,7 @@
 
   if (logger_list->tail) {
     ret = snprintf(cp, remaining, " tail=%u", logger_list->tail);
-    ret = min(ret, remaining);
+    ret = MIN(ret, remaining);
     remaining -= ret;
     cp += ret;
   }
@@ -378,20 +376,20 @@
     if (logger_list->mode & ANDROID_LOG_WRAP) {
       // ToDo: alternate API to allow timeout to be adjusted.
       ret = snprintf(cp, remaining, " timeout=%u", ANDROID_LOG_WRAP_DEFAULT_TIMEOUT);
-      ret = min(ret, remaining);
+      ret = MIN(ret, remaining);
       remaining -= ret;
       cp += ret;
     }
     ret = snprintf(cp, remaining, " start=%" PRIu32 ".%09" PRIu32, logger_list->start.tv_sec,
                    logger_list->start.tv_nsec);
-    ret = min(ret, remaining);
+    ret = MIN(ret, remaining);
     remaining -= ret;
     cp += ret;
   }
 
   if (logger_list->pid) {
     ret = snprintf(cp, remaining, " pid=%u", logger_list->pid);
-    ret = min(ret, remaining);
+    ret = MIN(ret, remaining);
     cp += ret;
   }
 
diff --git a/liblog/logd_writer.cpp b/liblog/logd_writer.cpp
index 09aaffb..06a2baf 100644
--- a/liblog/logd_writer.cpp
+++ b/liblog/logd_writer.cpp
@@ -38,9 +38,6 @@
 #include "logger.h"
 #include "uio.h"
 
-/* branchless on many architectures. */
-#define min(x, y) ((y) ^ (((x) ^ (y)) & -((x) < (y))))
-
 static int logdAvailable(log_id_t LogId);
 static int logdOpen();
 static void logdClose();
diff --git a/liblog/logger_write.cpp b/liblog/logger_write.cpp
index abcead7..e1772f1 100644
--- a/liblog/logger_write.cpp
+++ b/liblog/logger_write.cpp
@@ -34,8 +34,18 @@
 
 #define LOG_BUF_SIZE 1024
 
-android_log_transport_write* android_log_write = nullptr;
+#if (FAKE_LOG_DEVICE == 0)
+extern struct android_log_transport_write logdLoggerWrite;
+extern struct android_log_transport_write pmsgLoggerWrite;
+
+android_log_transport_write* android_log_write = &logdLoggerWrite;
+android_log_transport_write* android_log_persist_write = &pmsgLoggerWrite;
+#else
+extern android_log_transport_write fakeLoggerWrite;
+
+android_log_transport_write* android_log_write = &fakeLoggerWrite;
 android_log_transport_write* android_log_persist_write = nullptr;
+#endif
 
 static int __write_to_log_init(log_id_t, struct iovec* vec, size_t nr);
 static int (*write_to_log)(log_id_t, struct iovec* vec, size_t nr) = __write_to_log_init;
@@ -90,9 +100,8 @@
   }
 
   for (i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
-    if (node->write && (i != LOG_ID_KERNEL) &&
-        ((i != LOG_ID_SECURITY) || (check_log_uid_permissions() == 0)) &&
-        (!node->available || ((*node->available)(static_cast<log_id_t>(i)) >= 0))) {
+    if (i != LOG_ID_KERNEL && (i != LOG_ID_SECURITY || check_log_uid_permissions() == 0) &&
+        (*node->available)(static_cast<log_id_t>(i)) >= 0) {
       node->logMask |= 1 << i;
     }
   }
@@ -132,9 +141,6 @@
     android_log_persist_write->close();
   }
 
-  android_log_write = nullptr;
-  android_log_persist_write = nullptr;
-
 #if defined(__ANDROID__)
   /*
    * Additional risk here somewhat mitigated by immediately unlock flushing
@@ -179,26 +185,11 @@
 
 /* log_init_lock assumed */
 static int __write_to_log_initialize() {
-#if (FAKE_LOG_DEVICE == 0)
-  extern struct android_log_transport_write logdLoggerWrite;
-  extern struct android_log_transport_write pmsgLoggerWrite;
-
-  android_log_write = &logdLoggerWrite;
-  android_log_persist_write = &pmsgLoggerWrite;
-#else
-  extern struct android_log_transport_write fakeLoggerWrite;
-
-  android_log_write = &fakeLoggerWrite;
-#endif
-
   if (!transport_initialize(android_log_write)) {
-    android_log_write = nullptr;
     return -ENODEV;
   }
 
-  if (!transport_initialize(android_log_persist_write)) {
-    android_log_persist_write = nullptr;
-  }
+  transport_initialize(android_log_persist_write);
 
   return 1;
 }
@@ -206,14 +197,6 @@
 static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) {
   int ret, save_errno;
   struct timespec ts;
-  size_t len, i;
-
-  for (len = i = 0; i < nr; ++i) {
-    len += vec[i].iov_len;
-  }
-  if (!len) {
-    return -EINVAL;
-  }
 
   save_errno = errno;
 #if defined(__ANDROID__)
@@ -283,10 +266,6 @@
     int prio = *static_cast<int*>(vec[0].iov_base);
     const char* tag = static_cast<const char*>(vec[1].iov_base);
     size_t len = vec[1].iov_len;
-    /* tag must be nul terminated */
-    if (strnlen(tag, len) >= len) {
-      tag = NULL;
-    }
 
     if (!__android_log_is_loggable_len(prio, tag, len - 1, ANDROID_LOG_VERBOSE)) {
       errno = save_errno;
@@ -304,7 +283,7 @@
 #endif
 
   ret = 0;
-  i = 1 << log_id;
+  size_t i = 1 << log_id;
 
   if (android_log_write != nullptr && (android_log_write->logMask & i)) {
     ssize_t retval;
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index 34fb909..d8b0ced 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -2664,8 +2664,7 @@
       print_barrier();
     }
     EXPECT_EQ(0, buffer_to_string);
-    EXPECT_EQ(strlen(expected_string), strlen(msgBuf));
-    EXPECT_EQ(0, strcmp(expected_string, msgBuf));
+    EXPECT_STREQ(expected_string, msgBuf);
   }
 
   EXPECT_EQ(1, count);
@@ -2858,7 +2857,7 @@
   EXPECT_EQ(ANDROID_LOG_VERBOSE, prio);
   EXPECT_FALSE(NULL == strstr(__pmsg_file, filename));
   EXPECT_EQ(len, sizeof(max_payload_buf));
-  EXPECT_EQ(0, strcmp(max_payload_buf, buf));
+  EXPECT_STREQ(max_payload_buf, buf);
 
   ++signaled;
   if ((len != sizeof(max_payload_buf)) || strcmp(max_payload_buf, buf)) {
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index 93df1d0..010e8cc 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -176,6 +176,11 @@
     std::copy(vec.begin(), vec.end(), std::back_inserter(*sonames));
   }
 
+  // If this is for preloading libs, don't remove the libs from APEXes.
+  if (for_preload) {
+    return android::base::Join(*sonames, ':');
+  }
+
   // Remove the public libs in the runtime namespace.
   // These libs are listed in public.android.txt, but we don't want the rest of android
   // in default namespace to dlopen the libs.
diff --git a/libstats/statsd_writer.c b/libstats/statsd_writer.c
index b1c05ea..073b67f 100644
--- a/libstats/statsd_writer.c
+++ b/libstats/statsd_writer.c
@@ -36,25 +36,6 @@
 #include <time.h>
 #include <unistd.h>
 
-/* branchless on many architectures. */
-#define min(x, y) ((y) ^ (((x) ^ (y)) & -((x) < (y))))
-
-#ifndef htole32
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-#define htole32(x) (x)
-#else
-#define htole32(x) __bswap_32(x)
-#endif
-#endif
-
-#ifndef htole64
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-#define htole64(x) (x)
-#else
-#define htole64(x) __bswap_64(x)
-#endif
-#endif
-
 static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
 static atomic_int dropped = 0;
 static atomic_int log_error = 0;
@@ -221,14 +202,14 @@
             android_log_event_long_t buffer;
             header.id = LOG_ID_STATS;
             // store the last log error in the tag field. This tag field is not used by statsd.
-            buffer.header.tag = htole32(atomic_load(&log_error));
+            buffer.header.tag = atomic_load(&log_error);
             buffer.payload.type = EVENT_TYPE_LONG;
             // format:
             // |atom_tag|dropped_count|
             int64_t composed_long = atomic_load(&atom_tag);
             // Send 2 int32's via an int64.
             composed_long = ((composed_long << 32) | ((int64_t)snapshot));
-            buffer.payload.data = htole64(composed_long);
+            buffer.payload.data = composed_long;
 
             newVec[headerLength].iov_base = &buffer;
             newVec[headerLength].iov_len = sizeof(buffer);
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index bdfee01..be1f092 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -187,8 +187,13 @@
     if (!memory->ReadFully(offset, &phdr, sizeof(phdr))) {
       return 0;
     }
-    if (phdr.p_type == PT_LOAD && phdr.p_offset == 0) {
-      return phdr.p_vaddr;
+
+    // Find the first executable load when looking for the load bias.
+    if (phdr.p_type == PT_LOAD && (phdr.p_flags & PF_X)) {
+      if (phdr.p_vaddr > phdr.p_offset) {
+        return phdr.p_vaddr - phdr.p_offset;
+      }
+      break;
     }
   }
   return 0;
diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp
index f9ee9eb..5b2036b 100644
--- a/libunwindstack/tests/ElfInterfaceTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceTest.cpp
@@ -131,6 +131,12 @@
   template <typename Ehdr, typename Shdr, typename Nhdr, typename ElfInterfaceType>
   void BuildIDSectionTooSmallForHeader();
 
+  template <typename Ehdr, typename Phdr, typename ElfInterfaceType>
+  void CheckLoadBiasInFirstPhdr(uint64_t load_bias);
+
+  template <typename Ehdr, typename Phdr, typename ElfInterfaceType>
+  void CheckLoadBiasInFirstExecPhdr(uint64_t offset, uint64_t vaddr, uint64_t load_bias);
+
   MemoryFake memory_;
 };
 
@@ -1495,4 +1501,122 @@
   BuildIDSectionTooSmallForHeader<Elf64_Ehdr, Elf64_Shdr, Elf64_Nhdr, ElfInterface64>();
 }
 
+template <typename Ehdr, typename Phdr, typename ElfInterfaceType>
+void ElfInterfaceTest::CheckLoadBiasInFirstPhdr(uint64_t load_bias) {
+  Ehdr ehdr = {};
+  ehdr.e_phoff = 0x100;
+  ehdr.e_phnum = 2;
+  ehdr.e_phentsize = sizeof(Phdr);
+  memory_.SetMemory(0, &ehdr, sizeof(ehdr));
+
+  Phdr phdr = {};
+  phdr.p_type = PT_LOAD;
+  phdr.p_offset = 0;
+  phdr.p_vaddr = load_bias;
+  phdr.p_memsz = 0x10000;
+  phdr.p_flags = PF_R | PF_X;
+  phdr.p_align = 0x1000;
+  memory_.SetMemory(0x100, &phdr, sizeof(phdr));
+
+  memset(&phdr, 0, sizeof(phdr));
+  phdr.p_type = PT_LOAD;
+  phdr.p_offset = 0x1000;
+  phdr.p_memsz = 0x2000;
+  phdr.p_flags = PF_R | PF_X;
+  phdr.p_align = 0x1000;
+  memory_.SetMemory(0x100 + sizeof(phdr), &phdr, sizeof(phdr));
+
+  uint64_t static_load_bias = ElfInterface::GetLoadBias<Ehdr, Phdr>(&memory_);
+  ASSERT_EQ(load_bias, static_load_bias);
+
+  std::unique_ptr<ElfInterfaceType> elf(new ElfInterfaceType(&memory_));
+  uint64_t init_load_bias = 0;
+  ASSERT_TRUE(elf->Init(&init_load_bias));
+  ASSERT_EQ(init_load_bias, static_load_bias);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_zero_32) {
+  CheckLoadBiasInFirstPhdr<Elf32_Ehdr, Elf32_Phdr, ElfInterface32>(0);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_zero_64) {
+  CheckLoadBiasInFirstPhdr<Elf64_Ehdr, Elf64_Phdr, ElfInterface64>(0);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_non_zero_32) {
+  CheckLoadBiasInFirstPhdr<Elf32_Ehdr, Elf32_Phdr, ElfInterface32>(0x1000);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_non_zero_64) {
+  CheckLoadBiasInFirstPhdr<Elf64_Ehdr, Elf64_Phdr, ElfInterface64>(0x1000);
+}
+
+template <typename Ehdr, typename Phdr, typename ElfInterfaceType>
+void ElfInterfaceTest::CheckLoadBiasInFirstExecPhdr(uint64_t offset, uint64_t vaddr,
+                                                    uint64_t load_bias) {
+  Ehdr ehdr = {};
+  ehdr.e_phoff = 0x100;
+  ehdr.e_phnum = 3;
+  ehdr.e_phentsize = sizeof(Phdr);
+  memory_.SetMemory(0, &ehdr, sizeof(ehdr));
+
+  Phdr phdr = {};
+  phdr.p_type = PT_LOAD;
+  phdr.p_memsz = 0x10000;
+  phdr.p_flags = PF_R;
+  phdr.p_align = 0x1000;
+  memory_.SetMemory(0x100, &phdr, sizeof(phdr));
+
+  memset(&phdr, 0, sizeof(phdr));
+  phdr.p_type = PT_LOAD;
+  phdr.p_offset = offset;
+  phdr.p_vaddr = vaddr;
+  phdr.p_memsz = 0x2000;
+  phdr.p_flags = PF_R | PF_X;
+  phdr.p_align = 0x1000;
+  memory_.SetMemory(0x100 + sizeof(phdr), &phdr, sizeof(phdr));
+
+  // Second executable load should be ignored for load bias computation.
+  memset(&phdr, 0, sizeof(phdr));
+  phdr.p_type = PT_LOAD;
+  phdr.p_offset = 0x1234;
+  phdr.p_vaddr = 0x2000;
+  phdr.p_memsz = 0x2000;
+  phdr.p_flags = PF_R | PF_X;
+  phdr.p_align = 0x1000;
+  memory_.SetMemory(0x200 + sizeof(phdr), &phdr, sizeof(phdr));
+
+  uint64_t static_load_bias = ElfInterface::GetLoadBias<Ehdr, Phdr>(&memory_);
+  ASSERT_EQ(load_bias, static_load_bias);
+
+  std::unique_ptr<ElfInterfaceType> elf(new ElfInterfaceType(&memory_));
+  uint64_t init_load_bias = 0;
+  ASSERT_TRUE(elf->Init(&init_load_bias));
+  ASSERT_EQ(init_load_bias, static_load_bias);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_exec_zero_32) {
+  CheckLoadBiasInFirstExecPhdr<Elf32_Ehdr, Elf32_Phdr, ElfInterface32>(0x1000, 0x1000, 0);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_exec_zero_64) {
+  CheckLoadBiasInFirstExecPhdr<Elf64_Ehdr, Elf64_Phdr, ElfInterface64>(0x1000, 0x1000, 0);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_exec_non_zero_32) {
+  CheckLoadBiasInFirstExecPhdr<Elf32_Ehdr, Elf32_Phdr, ElfInterface32>(0x1000, 0x4000, 0x3000);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_exec_non_zero_64) {
+  CheckLoadBiasInFirstExecPhdr<Elf64_Ehdr, Elf64_Phdr, ElfInterface64>(0x1000, 0x4000, 0x3000);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_exec_zero_from_error_32) {
+  CheckLoadBiasInFirstExecPhdr<Elf32_Ehdr, Elf32_Phdr, ElfInterface32>(0x5000, 0x1000, 0);
+}
+
+TEST_F(ElfInterfaceTest, get_load_bias_exec_zero_from_error_64) {
+  CheckLoadBiasInFirstExecPhdr<Elf64_Ehdr, Elf64_Phdr, ElfInterface64>(0x5000, 0x1000, 0);
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
index f5ac6cb..2c98928 100644
--- a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
+++ b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
@@ -141,6 +141,7 @@
   phdr.p_type = PT_NULL;
   memory->SetMemory(offset + 0x5000, &phdr, sizeof(phdr));
   phdr.p_type = PT_LOAD;
+  phdr.p_flags = PF_X;
   phdr.p_offset = 0;
   phdr.p_vaddr = 0xe000;
   memory->SetMemory(offset + 0x5000 + sizeof(phdr), &phdr, sizeof(phdr));
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index e3f2bc2..372e10f 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -926,12 +926,12 @@
 static void cmd_procremove(LMKD_CTRL_PACKET packet) {
     struct lmk_procremove params;
 
+    lmkd_pack_get_procremove(packet, &params);
     if (use_inkernel_interface) {
         stats_remove_taskname(params.pid, kpoll_info.poll_fd);
         return;
     }
 
-    lmkd_pack_get_procremove(packet, &params);
     /*
      * WARNING: After pid_remove() procp is freed and can't be used!
      * Therefore placed at the end of the function.