Merge changes I7bb0b559,If147df2d,I25d7d590

* changes:
  libsnapshot: Skip initializing snapshot if not created.
  libdm: Fix DmTable::num_sectors
  Fix fds libdm_test
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 3dfcfbb..6321fc6 100644
--- a/base/file.cpp
+++ b/base/file.cpp
@@ -49,29 +49,54 @@
 #include "android-base/unique_fd.h"
 #include "android-base/utf8.h"
 
+namespace {
+
 #ifdef _WIN32
-int mkstemp(char* template_name) {
-  if (_mktemp(template_name) == nullptr) {
+static int mkstemp(char* name_template, size_t size_in_chars) {
+  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;
   }
+
   // Use open() to match the close() that TemporaryFile's destructor does.
   // Use O_BINARY to match base file APIs.
-  return open(template_name, 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;
 }
 
-char* mkdtemp(char* template_name) {
-  if (_mktemp(template_name) == nullptr) {
+static char* mkdtemp(char* name_template, size_t size_in_chars) {
+  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;
   }
-  if (_mkdir(template_name) == -1) {
+
+  if (_wmkdir(path.c_str()) != 0) {
     return nullptr;
   }
-  return template_name;
+
+  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
 
-namespace {
-
 std::string GetSystemTempDir() {
 #if defined(__ANDROID__)
   const auto* tmpdir = getenv("TMPDIR");
@@ -83,15 +108,20 @@
   // so try current directory if /data/local/tmp is not accessible.
   return ".";
 #elif defined(_WIN32)
-  char tmp_dir[MAX_PATH];
-  DWORD result = GetTempPathA(sizeof(tmp_dir), tmp_dir);  // checks TMP env
-  CHECK_NE(result, 0ul) << "GetTempPathA failed, error: " << GetLastError();
-  CHECK_LT(result, sizeof(tmp_dir)) << "path truncated to: " << result;
+  wchar_t tmp_dir_w[MAX_PATH];
+  DWORD result = GetTempPathW(std::size(tmp_dir_w), tmp_dir_w);  // checks TMP env
+  CHECK_NE(result, 0ul) << "GetTempPathW failed, error: " << GetLastError();
+  CHECK_LT(result, std::size(tmp_dir_w)) << "path truncated to: " << result;
 
   // GetTempPath() returns a path with a trailing slash, but init()
   // does not expect that, so remove it.
-  CHECK_EQ(tmp_dir[result - 1], '\\');
-  tmp_dir[result - 1] = '\0';
+  if (tmp_dir_w[result - 1] == L'\\') {
+    tmp_dir_w[result - 1] = L'\0';
+  }
+
+  std::string tmp_dir;
+  CHECK(android::base::WideToUTF8(tmp_dir_w, &tmp_dir)) << "path can't be converted to utf8";
+
   return tmp_dir;
 #else
   const auto* tmpdir = getenv("TMPDIR");
@@ -127,7 +157,11 @@
 
 void TemporaryFile::init(const std::string& tmp_dir) {
   snprintf(path, sizeof(path), "%s%cTemporaryFile-XXXXXX", tmp_dir.c_str(), OS_PATH_SEPARATOR);
+#if defined(_WIN32)
+  fd = mkstemp(path, sizeof(path));
+#else
   fd = mkstemp(path);
+#endif
 }
 
 TemporaryDir::TemporaryDir() {
@@ -167,7 +201,11 @@
 
 bool TemporaryDir::init(const std::string& tmp_dir) {
   snprintf(path, sizeof(path), "%s%cTemporaryDir-XXXXXX", tmp_dir.c_str(), OS_PATH_SEPARATOR);
+#if defined(_WIN32)
+  return (mkdtemp(path, sizeof(path)) != nullptr);
+#else
   return (mkdtemp(path) != nullptr);
+#endif
 }
 
 namespace android {
diff --git a/base/file_test.cpp b/base/file_test.cpp
index f64e81c..120228d 100644
--- a/base/file_test.cpp
+++ b/base/file_test.cpp
@@ -16,18 +16,25 @@
 
 #include "android-base/file.h"
 
+#include "android-base/utf8.h"
+
 #include <gtest/gtest.h>
 
 #include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
+#include <wchar.h>
 
 #include <string>
 
 #if !defined(_WIN32)
 #include <pwd.h>
+#else
+#include <windows.h>
 #endif
 
+#include "android-base/logging.h"  // and must be after windows.h for ERROR
+
 TEST(file, ReadFileToString_ENOENT) {
   std::string s("hello");
   errno = 0;
@@ -38,7 +45,7 @@
 
 TEST(file, ReadFileToString_WriteStringToFile) {
   TemporaryFile tf;
-  ASSERT_TRUE(tf.fd != -1);
+  ASSERT_NE(tf.fd, -1) << tf.path;
   ASSERT_TRUE(android::base::WriteStringToFile("abc", tf.path))
     << strerror(errno);
   std::string s;
@@ -70,7 +77,7 @@
 #if !defined(_WIN32)
 TEST(file, WriteStringToFile2) {
   TemporaryFile tf;
-  ASSERT_TRUE(tf.fd != -1);
+  ASSERT_NE(tf.fd, -1) << tf.path;
   ASSERT_TRUE(android::base::WriteStringToFile("abc", tf.path, 0660,
                                                getuid(), getgid()))
       << strerror(errno);
@@ -86,9 +93,92 @@
 }
 #endif
 
+#if defined(_WIN32)
+TEST(file, NonUnicodeCharsWindows) {
+  constexpr auto kMaxEnvVariableValueSize = 32767;
+  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'\\');
+  }
+
+  {
+    auto path(new_tmp + L"锦绣成都\\");
+    _wmkdir(path.c_str());
+    ASSERT_TRUE(SetEnvironmentVariableW(L"TMP", path.c_str()));
+
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1) << tf.path;
+    ASSERT_TRUE(android::base::WriteStringToFd("abc", tf.fd));
+
+    ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
+
+    std::string s;
+    ASSERT_TRUE(android::base::ReadFdToString(tf.fd, &s)) << strerror(errno);
+    EXPECT_EQ("abc", s);
+  }
+  {
+    auto path(new_tmp + L"директория с длинным именем\\");
+    _wmkdir(path.c_str());
+    ASSERT_TRUE(SetEnvironmentVariableW(L"TMP", path.c_str()));
+
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1) << tf.path;
+    ASSERT_TRUE(android::base::WriteStringToFd("abc", tf.fd));
+
+    ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
+
+    std::string s;
+    ASSERT_TRUE(android::base::ReadFdToString(tf.fd, &s)) << strerror(errno);
+    EXPECT_EQ("abc", s);
+  }
+  {
+    auto path(new_tmp + L"äüöß weiß\\");
+    _wmkdir(path.c_str());
+    ASSERT_TRUE(SetEnvironmentVariableW(L"TMP", path.c_str()));
+
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1) << tf.path;
+    ASSERT_TRUE(android::base::WriteStringToFd("abc", tf.fd));
+
+    ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
+
+    std::string s;
+    ASSERT_TRUE(android::base::ReadFdToString(tf.fd, &s)) << strerror(errno);
+    EXPECT_EQ("abc", s);
+  }
+
+  SetEnvironmentVariableW(L"TMP", old_tmp.c_str());
+}
+
+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", tmp_is_empty ? nullptr : old_tmp.c_str());
+}
+#endif
+
 TEST(file, WriteStringToFd) {
   TemporaryFile tf;
-  ASSERT_TRUE(tf.fd != -1);
+  ASSERT_NE(tf.fd, -1) << tf.path;
   ASSERT_TRUE(android::base::WriteStringToFd("abc", tf.fd));
 
   ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
@@ -100,7 +190,7 @@
 
 TEST(file, WriteFully) {
   TemporaryFile tf;
-  ASSERT_TRUE(tf.fd != -1);
+  ASSERT_NE(tf.fd, -1) << tf.path;
   ASSERT_TRUE(android::base::WriteFully(tf.fd, "abc", 3));
 
   ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
@@ -119,7 +209,7 @@
 
 TEST(file, RemoveFileIfExists) {
   TemporaryFile tf;
-  ASSERT_TRUE(tf.fd != -1);
+  ASSERT_NE(tf.fd, -1) << tf.path;
   close(tf.fd);
   tf.fd = -1;
   std::string err;
@@ -253,7 +343,7 @@
 
 TEST(file, ReadFileToString_capacity) {
   TemporaryFile tf;
-  ASSERT_TRUE(tf.fd != -1);
+  ASSERT_NE(tf.fd, -1) << tf.path;
 
   // For a huge file, the overhead should still be small.
   std::string s;
@@ -280,7 +370,7 @@
 
 TEST(file, ReadFileToString_capacity_0) {
   TemporaryFile tf;
-  ASSERT_TRUE(tf.fd != -1);
+  ASSERT_NE(tf.fd, -1) << tf.path;
 
   // Because /proc reports its files as zero-length, we don't actually trust
   // any file that claims to be zero-length. Rather than add increasingly
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 354aafa..02c7de6 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -1307,8 +1307,8 @@
     // device itself. This device consists of the real blocks in the super
     // partition that this logical partition occupies.
     auto& dm = DeviceMapper::Instance();
-    std::string ignore_path;
-    if (!CreateLogicalPartition(params, &ignore_path)) {
+    std::string base_path;
+    if (!CreateLogicalPartition(params, &base_path)) {
         LOG(ERROR) << "Could not create logical partition " << params.GetPartitionName()
                    << " as device " << params.GetDeviceName();
         return false;
@@ -1316,6 +1316,7 @@
     created_devices.EmplaceBack<AutoUnmapDevice>(&dm, params.GetDeviceName());
 
     if (!live_snapshot_status.has_value()) {
+        *path = base_path;
         created_devices.Release();
         return true;
     }
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/libkeyutils/Android.bp b/libkeyutils/Android.bp
index dda491a..b388e95 100644
--- a/libkeyutils/Android.bp
+++ b/libkeyutils/Android.bp
@@ -16,17 +16,3 @@
     srcs: ["keyutils_test.cpp"],
     test_suites: ["device-tests"],
 }
-
-cc_binary {
-    name: "mini-keyctl",
-    srcs: [
-        "mini_keyctl.cpp",
-        "mini_keyctl_utils.cpp"
-    ],
-    shared_libs: [
-        "libbase",
-        "libkeyutils",
-        "liblog",
-    ],
-    cflags: ["-Werror", "-Wall", "-Wextra", "-fexceptions"],
-}
diff --git a/libkeyutils/mini_keyctl.cpp b/libkeyutils/mini_keyctl.cpp
deleted file mode 100644
index fe89e62..0000000
--- a/libkeyutils/mini_keyctl.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * A tool loads keys to keyring.
- */
-
-#include "mini_keyctl_utils.h"
-
-#include <error.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include <android-base/parseint.h>
-
-static void Usage(int exit_code) {
-  fprintf(stderr, "usage: mini-keyctl <action> [args,]\n");
-  fprintf(stderr, "       mini-keyctl add <type> <desc> <data> <keyring>\n");
-  fprintf(stderr, "       mini-keyctl padd <type> <desc> <keyring>\n");
-  fprintf(stderr, "       mini-keyctl unlink <key> <keyring>\n");
-  fprintf(stderr, "       mini-keyctl restrict_keyring <keyring>\n");
-  fprintf(stderr, "       mini-keyctl security <key>\n");
-  _exit(exit_code);
-}
-
-static key_serial_t parseKeyOrDie(const char* str) {
-  key_serial_t key;
-  if (!android::base::ParseInt(str, &key)) {
-    error(1 /* exit code */, 0 /* errno */, "Unparsable key: '%s'\n", str);
-  }
-  return key;
-}
-
-int main(int argc, const char** argv) {
-  if (argc < 2) Usage(1);
-  const std::string action = argv[1];
-
-  if (action == "add") {
-    if (argc != 6) Usage(1);
-    std::string type = argv[2];
-    std::string desc = argv[3];
-    std::string data = argv[4];
-    std::string keyring = argv[5];
-    return Add(type, desc, data, keyring);
-  } else if (action == "padd") {
-    if (argc != 5) Usage(1);
-    std::string type = argv[2];
-    std::string desc = argv[3];
-    std::string keyring = argv[4];
-    return Padd(type, desc, keyring);
-  } else if (action == "restrict_keyring") {
-    if (argc != 3) Usage(1);
-    std::string keyring = argv[2];
-    return RestrictKeyring(keyring);
-  } else if (action == "unlink") {
-    if (argc != 4) Usage(1);
-    key_serial_t key = parseKeyOrDie(argv[2]);
-    const std::string keyring = argv[3];
-    return Unlink(key, keyring);
-  } else if (action == "security") {
-    if (argc != 3) Usage(1);
-    const char* key_str = argv[2];
-    key_serial_t key = parseKeyOrDie(key_str);
-    std::string context = RetrieveSecurityContext(key);
-    if (context.empty()) {
-      perror(key_str);
-      return 1;
-    }
-    fprintf(stderr, "%s\n", context.c_str());
-    return 0;
-  } else {
-    fprintf(stderr, "Unrecognized action: %s\n", action.c_str());
-    Usage(1);
-  }
-
-  return 0;
-}
diff --git a/libkeyutils/mini_keyctl/Android.bp b/libkeyutils/mini_keyctl/Android.bp
new file mode 100644
index 0000000..a04a3db
--- /dev/null
+++ b/libkeyutils/mini_keyctl/Android.bp
@@ -0,0 +1,27 @@
+cc_library_static {
+    name: "libmini_keyctl_static",
+    srcs: [
+        "mini_keyctl_utils.cpp"
+    ],
+    shared_libs: [
+        "libbase",
+        "libkeyutils",
+    ],
+    cflags: ["-Werror", "-Wall", "-Wextra"],
+    export_include_dirs: ["."],
+}
+
+cc_binary {
+    name: "mini-keyctl",
+    srcs: [
+        "mini_keyctl.cpp",
+    ],
+    static_libs: [
+        "libmini_keyctl_static",
+    ],
+    shared_libs: [
+        "libbase",
+        "libkeyutils",
+    ],
+    cflags: ["-Werror", "-Wall", "-Wextra"],
+}
diff --git a/libkeyutils/mini_keyctl/mini_keyctl.cpp b/libkeyutils/mini_keyctl/mini_keyctl.cpp
new file mode 100644
index 0000000..8aace9a
--- /dev/null
+++ b/libkeyutils/mini_keyctl/mini_keyctl.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+/*
+ * A tool loads keys to keyring.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <error.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <iterator>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <keyutils.h>
+#include <mini_keyctl_utils.h>
+
+constexpr int kMaxCertSize = 4096;
+
+static void Usage(int exit_code) {
+  fprintf(stderr, "usage: mini-keyctl <action> [args,]\n");
+  fprintf(stderr, "       mini-keyctl add <type> <desc> <data> <keyring>\n");
+  fprintf(stderr, "       mini-keyctl padd <type> <desc> <keyring>\n");
+  fprintf(stderr, "       mini-keyctl unlink <key> <keyring>\n");
+  fprintf(stderr, "       mini-keyctl restrict_keyring <keyring>\n");
+  fprintf(stderr, "       mini-keyctl security <key>\n");
+  _exit(exit_code);
+}
+
+static key_serial_t parseKeyOrDie(const char* str) {
+  key_serial_t key;
+  if (!android::base::ParseInt(str, &key)) {
+    error(1 /* exit code */, 0 /* errno */, "Unparsable key: '%s'\n", str);
+  }
+  return key;
+}
+
+int Unlink(key_serial_t key, const std::string& keyring) {
+  key_serial_t keyring_id = android::GetKeyringId(keyring);
+  if (keyctl_unlink(key, keyring_id) < 0) {
+    error(1, errno, "Failed to unlink key %x from keyring %s", key, keyring.c_str());
+    return 1;
+  }
+  return 0;
+}
+
+int Add(const std::string& type, const std::string& desc, const std::string& data,
+        const std::string& keyring) {
+  if (data.size() > kMaxCertSize) {
+    error(1, 0, "Certificate too large");
+    return 1;
+  }
+
+  key_serial_t keyring_id = android::GetKeyringId(keyring);
+  key_serial_t key = add_key(type.c_str(), desc.c_str(), data.c_str(), data.size(), keyring_id);
+
+  if (key < 0) {
+    error(1, errno, "Failed to add key");
+    return 1;
+  }
+
+  std::cout << key << std::endl;
+  return 0;
+}
+
+int Padd(const std::string& type, const std::string& desc, const std::string& keyring) {
+  key_serial_t keyring_id = android::GetKeyringId(keyring);
+
+  // read from stdin to get the certificates
+  std::istreambuf_iterator<char> begin(std::cin), end;
+  std::string data(begin, end);
+
+  if (data.size() > kMaxCertSize) {
+    error(1, 0, "Certificate too large");
+    return 1;
+  }
+
+  key_serial_t key = add_key(type.c_str(), desc.c_str(), data.c_str(), data.size(), keyring_id);
+
+  if (key < 0) {
+    error(1, errno, "Failed to add key");
+    return 1;
+  }
+
+  std::cout << key << std::endl;
+  return 0;
+}
+
+int RestrictKeyring(const std::string& keyring) {
+  key_serial_t keyring_id = android::GetKeyringId(keyring);
+  if (keyctl_restrict_keyring(keyring_id, nullptr, nullptr) < 0) {
+    error(1, errno, "Cannot restrict keyring '%s'", keyring.c_str());
+    return 1;
+  }
+  return 0;
+}
+
+std::string RetrieveSecurityContext(key_serial_t key) {
+  // Simply assume this size is enough in practice.
+  const int kMaxSupportedSize = 256;
+  std::string context;
+  context.resize(kMaxSupportedSize);
+  long retval = keyctl_get_security(key, context.data(), kMaxSupportedSize);
+  if (retval < 0) {
+    error(1, errno, "Cannot get security context of key %x", key);
+    return std::string();
+  }
+  if (retval > kMaxSupportedSize) {
+    error(1, 0, "The key has unexpectedly long security context than %d", kMaxSupportedSize);
+    return std::string();
+  }
+  context.resize(retval);
+  return context;
+}
+
+int main(int argc, const char** argv) {
+  if (argc < 2) Usage(1);
+  const std::string action = argv[1];
+
+  if (action == "add") {
+    if (argc != 6) Usage(1);
+    std::string type = argv[2];
+    std::string desc = argv[3];
+    std::string data = argv[4];
+    std::string keyring = argv[5];
+    return Add(type, desc, data, keyring);
+  } else if (action == "padd") {
+    if (argc != 5) Usage(1);
+    std::string type = argv[2];
+    std::string desc = argv[3];
+    std::string keyring = argv[4];
+    return Padd(type, desc, keyring);
+  } else if (action == "restrict_keyring") {
+    if (argc != 3) Usage(1);
+    std::string keyring = argv[2];
+    return RestrictKeyring(keyring);
+  } else if (action == "unlink") {
+    if (argc != 4) Usage(1);
+    key_serial_t key = parseKeyOrDie(argv[2]);
+    const std::string keyring = argv[3];
+    return Unlink(key, keyring);
+  } else if (action == "security") {
+    if (argc != 3) Usage(1);
+    const char* key_str = argv[2];
+    key_serial_t key = parseKeyOrDie(key_str);
+    std::string context = RetrieveSecurityContext(key);
+    if (context.empty()) {
+      perror(key_str);
+      return 1;
+    }
+    fprintf(stderr, "%s\n", context.c_str());
+    return 0;
+  } else {
+    fprintf(stderr, "Unrecognized action: %s\n", action.c_str());
+    Usage(1);
+  }
+
+  return 0;
+}
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp b/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp
new file mode 100644
index 0000000..fb9503f
--- /dev/null
+++ b/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 <mini_keyctl_utils.h>
+
+#include <fstream>
+#include <iterator>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+
+namespace android {
+
+namespace {
+
+std::vector<std::string> SplitBySpace(const std::string& s) {
+  std::istringstream iss(s);
+  return std::vector<std::string>{std::istream_iterator<std::string>{iss},
+                                  std::istream_iterator<std::string>{}};
+}
+
+}  // namespace
+
+// Find the keyring id. request_key(2) only finds keys in the process, session or thread keyring
+// hierarchy, but not internal keyring of a kernel subsystem (e.g. .fs-verity). To support all
+// cases, this function looks up a keyring's ID by parsing /proc/keys. The keyring description may
+// contain other information in the descritption section depending on the key type, only the first
+// word in the keyring description is used for searching.
+key_serial_t GetKeyringId(const std::string& keyring_desc) {
+  // If the keyring id is already a hex number, directly convert it to keyring id
+  key_serial_t keyring_id;
+  if (android::base::ParseInt(keyring_desc.c_str(), &keyring_id)) {
+    return keyring_id;
+  }
+
+  // Only keys allowed by SELinux rules will be shown here.
+  std::ifstream proc_keys_file("/proc/keys");
+  if (!proc_keys_file.is_open()) {
+    PLOG(ERROR) << "Failed to open /proc/keys";
+    return -1;
+  }
+
+  std::string line;
+  while (getline(proc_keys_file, line)) {
+    std::vector<std::string> tokens = SplitBySpace(line);
+    if (tokens.size() < 9) {
+      continue;
+    }
+    std::string key_id = "0x" + tokens[0];
+    std::string key_type = tokens[7];
+    // The key description may contain space.
+    std::string key_desc_prefix = tokens[8];
+    // The prefix has a ":" at the end
+    std::string key_desc_pattern = keyring_desc + ":";
+    if (key_type != "keyring" || key_desc_prefix != key_desc_pattern) {
+      continue;
+    }
+    if (!android::base::ParseInt(key_id.c_str(), &keyring_id)) {
+      LOG(ERROR) << "Unexpected key format in /proc/keys: " << key_id;
+      return -1;
+    }
+    return keyring_id;
+  }
+  return -1;
+}
+
+}  // namespace android
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.h b/libkeyutils/mini_keyctl/mini_keyctl_utils.h
new file mode 100644
index 0000000..cc31d29
--- /dev/null
+++ b/libkeyutils/mini_keyctl/mini_keyctl_utils.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#ifndef _MINI_KEYCTL_MINI_KEYCTL_UTILS_H_
+#define _MINI_KEYCTL_MINI_KEYCTL_UTILS_H_
+
+#include <string>
+
+#include <keyutils.h>
+
+namespace android {
+key_serial_t GetKeyringId(const std::string& keyring_desc);
+}  // namespace android
+
+#endif  // _MINI_KEYCTL_MINI_KEYCTL_UTILS_H_
diff --git a/libkeyutils/mini_keyctl_utils.cpp b/libkeyutils/mini_keyctl_utils.cpp
deleted file mode 100644
index 79b4680..0000000
--- a/libkeyutils/mini_keyctl_utils.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * 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 <mini_keyctl_utils.h>
-
-#include <dirent.h>
-#include <errno.h>
-#include <error.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <fstream>
-#include <iostream>
-#include <iterator>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/parseint.h>
-#include <android-base/properties.h>
-#include <android-base/strings.h>
-#include <keyutils.h>
-
-static constexpr int kMaxCertSize = 4096;
-
-static std::vector<std::string> SplitBySpace(const std::string& s) {
-  std::istringstream iss(s);
-  return std::vector<std::string>{std::istream_iterator<std::string>{iss},
-                                  std::istream_iterator<std::string>{}};
-}
-
-// Find the keyring id. Because request_key(2) syscall is not available or the key is
-// kernel keyring, the id is looked up from /proc/keys. The keyring description may contain other
-// information in the descritption section depending on the key type, only the first word in the
-// keyring description is used for searching.
-static key_serial_t GetKeyringIdOrDie(const std::string& keyring_desc) {
-  // If the keyring id is already a hex number, directly convert it to keyring id
-  key_serial_t keyring_id;
-  if (android::base::ParseInt(keyring_desc.c_str(), &keyring_id)) {
-    return keyring_id;
-  }
-
-  // Only keys allowed by SELinux rules will be shown here.
-  std::ifstream proc_keys_file("/proc/keys");
-  if (!proc_keys_file.is_open()) {
-    error(1, errno, "Failed to open /proc/keys");
-    return -1;
-  }
-
-  std::string line;
-  while (getline(proc_keys_file, line)) {
-    std::vector<std::string> tokens = SplitBySpace(line);
-    if (tokens.size() < 9) {
-      continue;
-    }
-    std::string key_id = "0x" + tokens[0];
-    std::string key_type = tokens[7];
-    // The key description may contain space.
-    std::string key_desc_prefix = tokens[8];
-    // The prefix has a ":" at the end
-    std::string key_desc_pattern = keyring_desc + ":";
-    if (key_type != "keyring" || key_desc_prefix != key_desc_pattern) {
-      continue;
-    }
-    if (!android::base::ParseInt(key_id.c_str(), &keyring_id)) {
-      error(1, 0, "Unexpected key format in /proc/keys: %s", key_id.c_str());
-      return -1;
-    }
-    return keyring_id;
-  }
-  return -1;
-}
-
-int Unlink(key_serial_t key, const std::string& keyring) {
-  key_serial_t keyring_id = GetKeyringIdOrDie(keyring);
-  if (keyctl_unlink(key, keyring_id) < 0) {
-    error(1, errno, "Failed to unlink key %x from keyring %s", key, keyring.c_str());
-    return 1;
-  }
-  return 0;
-}
-
-int Add(const std::string& type, const std::string& desc, const std::string& data,
-        const std::string& keyring) {
-  if (data.size() > kMaxCertSize) {
-    error(1, 0, "Certificate too large");
-    return 1;
-  }
-
-  key_serial_t keyring_id = GetKeyringIdOrDie(keyring);
-  key_serial_t key = add_key(type.c_str(), desc.c_str(), data.c_str(), data.size(), keyring_id);
-
-  if (key < 0) {
-    error(1, errno, "Failed to add key");
-    return 1;
-  }
-
-  std::cout << key << std::endl;
-  return 0;
-}
-
-int Padd(const std::string& type, const std::string& desc, const std::string& keyring) {
-  key_serial_t keyring_id = GetKeyringIdOrDie(keyring);
-
-  // read from stdin to get the certificates
-  std::istreambuf_iterator<char> begin(std::cin), end;
-  std::string data(begin, end);
-
-  if (data.size() > kMaxCertSize) {
-    error(1, 0, "Certificate too large");
-    return 1;
-  }
-
-  key_serial_t key = add_key(type.c_str(), desc.c_str(), data.c_str(), data.size(), keyring_id);
-
-  if (key < 0) {
-    error(1, errno, "Failed to add key");
-    return 1;
-  }
-
-  std::cout << key << std::endl;
-  return 0;
-}
-
-int RestrictKeyring(const std::string& keyring) {
-  key_serial_t keyring_id = GetKeyringIdOrDie(keyring);
-  if (keyctl_restrict_keyring(keyring_id, nullptr, nullptr) < 0) {
-    error(1, errno, "Cannot restrict keyring '%s'", keyring.c_str());
-    return 1;
-  }
-  return 0;
-}
-
-std::string RetrieveSecurityContext(key_serial_t key) {
-  // Simply assume this size is enough in practice.
-  const int kMaxSupportedSize = 256;
-  std::string context;
-  context.resize(kMaxSupportedSize);
-  long retval = keyctl_get_security(key, context.data(), kMaxSupportedSize);
-  if (retval < 0) {
-    error(1, errno, "Cannot get security context of key %x", key);
-    return std::string();
-  }
-  if (retval > kMaxSupportedSize) {
-    error(1, 0, "The key has unexpectedly long security context than %d", kMaxSupportedSize);
-    return std::string();
-  }
-  context.resize(retval);
-  return context;
-}
diff --git a/libkeyutils/mini_keyctl_utils.h b/libkeyutils/mini_keyctl_utils.h
deleted file mode 100644
index 3616831..0000000
--- a/libkeyutils/mini_keyctl_utils.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 "include/keyutils.h"
-
-#include <string>
-
-// Add key to a keyring. Returns non-zero if error happens.
-int Add(const std::string& type, const std::string& desc, const std::string& data,
-        const std::string& keyring);
-
-// Add key from stdin to a keyring. Returns non-zero if error happens.
-int Padd(const std::string& type, const std::string& desc, const std::string& keyring);
-
-// Removes the link from a keyring to a key if exists. Return non-zero if error happens.
-int Unlink(key_serial_t key, const std::string& keyring);
-
-// Apply key-linking to a keyring. Return non-zero if error happens.
-int RestrictKeyring(const std::string& keyring);
-
-// Retrieves a key's security context. Return the context string, or empty string on error.
-std::string RetrieveSecurityContext(key_serial_t key);
diff --git a/liblog/Android.bp b/liblog/Android.bp
index 8a63007..c40c5ef 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -15,7 +15,6 @@
 //
 
 liblog_sources = [
-    "config_write.cpp",
     "log_event_list.cpp",
     "log_event_write.cpp",
     "logger_lock.cpp",
diff --git a/liblog/README.md b/liblog/README.md
index 98bee9f..871399a 100644
--- a/liblog/README.md
+++ b/liblog/README.md
@@ -96,11 +96,6 @@
 
     int android_log_destroy(android_log_context *ctx)
 
-    #include <log/log_transport.h>
-
-    int android_set_log_transport(int transport_flag)
-    int android_get_log_transport()
-
 Description
 -----------
 
@@ -144,11 +139,6 @@
 that was used when opening the sub-log.  It is recommended to open the log `ANDROID_LOG_RDONLY` in
 these cases.
 
-`android_set_log_transport()` selects transport filters.  Argument is either `LOGGER_DEFAULT`,
-`LOGGER_LOGD`, or `LOGGER_NULL`. Log to logger daemon for default or logd, or drop contents on floor
-respectively.  `Both android_set_log_transport()` and `android_get_log_transport()` return the
-current transport mask, or a negative errno for any problems.
-
 Errors
 ------
 
diff --git a/liblog/config_write.cpp b/liblog/config_write.cpp
deleted file mode 100644
index 6ed893d..0000000
--- a/liblog/config_write.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <log/log_transport.h>
-
-#include "config_write.h"
-#include "logger.h"
-
-struct listnode __android_log_transport_write = {&__android_log_transport_write,
-                                                 &__android_log_transport_write};
-struct listnode __android_log_persist_write = {&__android_log_persist_write,
-                                               &__android_log_persist_write};
-
-static void __android_log_add_transport(struct listnode* list,
-                                        struct android_log_transport_write* transport) {
-  uint32_t i;
-
-  /* Try to keep one functioning transport for each log buffer id */
-  for (i = LOG_ID_MIN; i < LOG_ID_MAX; i++) {
-    struct android_log_transport_write* transp;
-
-    if (list_empty(list)) {
-      if (!transport->available || ((*transport->available)(static_cast<log_id_t>(i)) >= 0)) {
-        list_add_tail(list, &transport->node);
-        return;
-      }
-    } else {
-      write_transport_for_each(transp, list) {
-        if (!transp->available) {
-          return;
-        }
-        if (((*transp->available)(static_cast<log_id_t>(i)) < 0) &&
-            (!transport->available || ((*transport->available)(static_cast<log_id_t>(i)) >= 0))) {
-          list_add_tail(list, &transport->node);
-          return;
-        }
-      }
-    }
-  }
-}
-
-void __android_log_config_write() {
-  if ((__android_log_transport == LOGGER_DEFAULT) || (__android_log_transport & LOGGER_LOGD)) {
-#if (FAKE_LOG_DEVICE == 0)
-    extern struct android_log_transport_write logdLoggerWrite;
-    extern struct android_log_transport_write pmsgLoggerWrite;
-
-    __android_log_add_transport(&__android_log_transport_write, &logdLoggerWrite);
-    __android_log_add_transport(&__android_log_persist_write, &pmsgLoggerWrite);
-#else
-    extern struct android_log_transport_write fakeLoggerWrite;
-
-    __android_log_add_transport(&__android_log_transport_write, &fakeLoggerWrite);
-#endif
-  }
-}
-
-void __android_log_config_write_close() {
-  struct android_log_transport_write* transport;
-  struct listnode* n;
-
-  write_transport_for_each_safe(transport, n, &__android_log_transport_write) {
-    transport->logMask = 0;
-    list_remove(&transport->node);
-  }
-  write_transport_for_each_safe(transport, n, &__android_log_persist_write) {
-    transport->logMask = 0;
-    list_remove(&transport->node);
-  }
-}
diff --git a/liblog/config_write.h b/liblog/config_write.h
deleted file mode 100644
index a901f13..0000000
--- a/liblog/config_write.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <cutils/list.h>
-
-#include "log_portability.h"
-
-__BEGIN_DECLS
-
-extern struct listnode __android_log_transport_write;
-extern struct listnode __android_log_persist_write;
-
-#define write_transport_for_each(transp, transports)                           \
-  for ((transp) = node_to_item((transports)->next,                             \
-                               struct android_log_transport_write, node);      \
-       ((transp) != node_to_item((transports),                                 \
-                                 struct android_log_transport_write, node)) && \
-       ((transp) != node_to_item((transp)->node.next,                          \
-                                 struct android_log_transport_write, node));   \
-       (transp) = node_to_item((transp)->node.next,                            \
-                               struct android_log_transport_write, node))
-
-#define write_transport_for_each_safe(transp, n, transports)                   \
-  for ((transp) = node_to_item((transports)->next,                             \
-                               struct android_log_transport_write, node),      \
-      (n) = (transp)->node.next;                                               \
-       ((transp) != node_to_item((transports),                                 \
-                                 struct android_log_transport_write, node)) && \
-       ((transp) !=                                                            \
-        node_to_item((n), struct android_log_transport_write, node));          \
-       (transp) = node_to_item((n), struct android_log_transport_write, node), \
-      (n) = (transp)->node.next)
-
-void __android_log_config_write();
-void __android_log_config_write_close();
-
-__END_DECLS
diff --git a/liblog/fake_writer.cpp b/liblog/fake_writer.cpp
index c0b0e69..f1ddff1 100644
--- a/liblog/fake_writer.cpp
+++ b/liblog/fake_writer.cpp
@@ -20,11 +20,11 @@
 
 #include <log/log.h>
 
-#include "config_write.h"
 #include "fake_log_device.h"
 #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);
@@ -32,15 +32,19 @@
 static int logFds[(int)LOG_ID_MAX] = {-1, -1, -1, -1, -1, -1};
 
 struct android_log_transport_write fakeLoggerWrite = {
-    .node = {&fakeLoggerWrite.node, &fakeLoggerWrite.node},
-    .context.priv = &logFds,
     .name = "fake",
-    .available = NULL,
+    .logMask = 0,
+    .context.priv = &logFds,
+    .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/include/log/log_transport.h b/liblog/include/log/log_transport.h
deleted file mode 100644
index bda7c25..0000000
--- a/liblog/include/log/log_transport.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-**
-** Copyright 2017, The Android Open Source Project
-**
-** This file is dual licensed.  It may be redistributed and/or modified
-** under the terms of the Apache 2.0 License OR version 2 of the GNU
-** General Public License.
-*/
-
-#pragma once
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Logging transports, bit mask to select features. Function returns selection.
- */
-/* clang-format off */
-#define LOGGER_DEFAULT 0x00
-#define LOGGER_LOGD    0x01
-#define LOGGER_KERNEL  0x02 /* Reserved/Deprecated */
-#define LOGGER_NULL    0x04 /* Does not release resources of other selections */
-#define LOGGER_RESERVED 0x08 /* Reserved, previously for logging to local memory */
-#define LOGGER_RESERVED2  0x10 /* Reserved, previously for logs sent to stderr */
-/* clang-format on */
-
-/* Both return the selected transport flag mask, or negative errno */
-int android_set_log_transport(int transport_flag);
-int android_get_log_transport();
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/liblog/log_event_list.cpp b/liblog/log_event_list.cpp
index f148ef3..18ea930 100644
--- a/liblog/log_event_list.cpp
+++ b/liblog/log_event_list.cpp
@@ -176,13 +176,6 @@
   return 0;
 }
 
-static inline void copy4LE(uint8_t* buf, uint32_t val) {
-  buf[0] = val & 0xFF;
-  buf[1] = (val >> 8) & 0xFF;
-  buf[2] = (val >> 16) & 0xFF;
-  buf[3] = (val >> 24) & 0xFF;
-}
-
 int android_log_write_int32(android_log_context ctx, int32_t value) {
   size_t needed;
   android_log_context_internal* context;
@@ -201,22 +194,11 @@
   }
   context->count[context->list_nest_depth]++;
   context->storage[context->pos + 0] = EVENT_TYPE_INT;
-  copy4LE(&context->storage[context->pos + 1], value);
+  *reinterpret_cast<int32_t*>(&context->storage[context->pos + 1]) = value;
   context->pos += needed;
   return 0;
 }
 
-static inline void copy8LE(uint8_t* buf, uint64_t val) {
-  buf[0] = val & 0xFF;
-  buf[1] = (val >> 8) & 0xFF;
-  buf[2] = (val >> 16) & 0xFF;
-  buf[3] = (val >> 24) & 0xFF;
-  buf[4] = (val >> 32) & 0xFF;
-  buf[5] = (val >> 40) & 0xFF;
-  buf[6] = (val >> 48) & 0xFF;
-  buf[7] = (val >> 56) & 0xFF;
-}
-
 int android_log_write_int64(android_log_context ctx, int64_t value) {
   size_t needed;
   android_log_context_internal* context;
@@ -235,7 +217,7 @@
   }
   context->count[context->list_nest_depth]++;
   context->storage[context->pos + 0] = EVENT_TYPE_LONG;
-  copy8LE(&context->storage[context->pos + 1], value);
+  *reinterpret_cast<int64_t*>(&context->storage[context->pos + 1]) = value;
   context->pos += needed;
   return 0;
 }
@@ -267,7 +249,7 @@
   }
   context->count[context->list_nest_depth]++;
   context->storage[context->pos + 0] = EVENT_TYPE_STRING;
-  copy4LE(&context->storage[context->pos + 1], len);
+  *reinterpret_cast<ssize_t*>(&context->storage[context->pos + 1]) = len;
   if (len) {
     memcpy(&context->storage[context->pos + 5], value, len);
   }
@@ -299,7 +281,7 @@
   ivalue = *(uint32_t*)&value;
   context->count[context->list_nest_depth]++;
   context->storage[context->pos + 0] = EVENT_TYPE_FLOAT;
-  copy4LE(&context->storage[context->pos + 1], ivalue);
+  *reinterpret_cast<uint32_t*>(&context->storage[context->pos + 1]) = ivalue;
   context->pos += needed;
   return 0;
 }
diff --git a/liblog/logd_reader.cpp b/liblog/logd_reader.cpp
index d5bf844..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);
@@ -66,7 +64,6 @@
                             struct android_log_transport_context* transp, char* buf, size_t len);
 
 struct android_log_transport_read logdLoggerRead = {
-    .node = {&logdLoggerRead.node, &logdLoggerRead.node},
     .name = "logd",
     .available = logdAvailable,
     .version = logdVersion,
@@ -279,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;
   }
@@ -362,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 = ',';
@@ -370,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;
   }
@@ -379,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 2c64b0b..06a2baf 100644
--- a/liblog/logd_writer.cpp
+++ b/liblog/logd_writer.cpp
@@ -34,23 +34,19 @@
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
-#include "config_write.h"
 #include "log_portability.h"
 #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();
 static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
 
 struct android_log_transport_write logdLoggerWrite = {
-    .node = {&logdLoggerWrite.node, &logdLoggerWrite.node},
-    .context.sock = -EBADF,
     .name = "logd",
+    .logMask = 0,
+    .context.sock = -EBADF,
     .available = logdAvailable,
     .open = logdOpen,
     .close = logdClose,
diff --git a/liblog/logger.h b/liblog/logger.h
index 4b4ef5f..8cae66c 100644
--- a/liblog/logger.h
+++ b/liblog/logger.h
@@ -31,12 +31,9 @@
   void* priv;
   atomic_int sock;
   atomic_int fd;
-  struct listnode* node;
-  atomic_uintptr_t atomic_pointer;
 };
 
 struct android_log_transport_write {
-  struct listnode node;
   const char* name;                  /* human name to describe the transport */
   unsigned logMask;                  /* mask cache of available() success */
   union android_log_context_union context; /* Initialized by static allocation */
@@ -54,7 +51,6 @@
 struct android_log_logger;
 
 struct android_log_transport_read {
-  struct listnode node;
   const char* name; /* human name to describe the transport */
 
   /* Does not cause resources to be taken */
@@ -149,6 +145,4 @@
 int __android_log_trylock();
 void __android_log_unlock();
 
-extern int __android_log_transport;
-
 __END_DECLS
diff --git a/liblog/logger_write.cpp b/liblog/logger_write.cpp
index 7fc8747..e1772f1 100644
--- a/liblog/logger_write.cpp
+++ b/liblog/logger_write.cpp
@@ -25,17 +25,28 @@
 #endif
 
 #include <log/event_tag_map.h>
-#include <log/log_transport.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
-#include "config_write.h"
 #include "log_portability.h"
 #include "logger.h"
 #include "uio.h"
 
 #define LOG_BUF_SIZE 1024
 
+#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;
 
@@ -89,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;
     }
   }
@@ -105,7 +115,6 @@
  * Release any logger resources. A new log write will immediately re-acquire.
  */
 void __android_log_close() {
-  struct android_log_transport_write* transport;
 #if defined(__ANDROID__)
   EventTagMap* m;
 #endif
@@ -124,20 +133,14 @@
    * disengenuous use of this function.
    */
 
-  write_transport_for_each(transport, &__android_log_persist_write) {
-    if (transport->close) {
-      (*transport->close)();
-    }
+  if (android_log_write != nullptr) {
+    android_log_write->close();
   }
 
-  write_transport_for_each(transport, &__android_log_transport_write) {
-    if (transport->close) {
-      (*transport->close)();
-    }
+  if (android_log_persist_write != nullptr) {
+    android_log_persist_write->close();
   }
 
-  __android_log_config_write_close();
-
 #if defined(__ANDROID__)
   /*
    * Additional risk here somewhat mitigated by immediately unlock flushing
@@ -161,62 +164,39 @@
 #endif
 }
 
+static bool transport_initialize(android_log_transport_write* transport) {
+  if (transport == nullptr) {
+    return false;
+  }
+
+  __android_log_cache_available(transport);
+  if (!transport->logMask) {
+    return false;
+  }
+
+  // TODO: Do we actually need to call close() if open() fails?
+  if (transport->open() < 0) {
+    transport->close();
+    return false;
+  }
+
+  return true;
+}
+
 /* log_init_lock assumed */
 static int __write_to_log_initialize() {
-  struct android_log_transport_write* transport;
-  struct listnode* n;
-  int i = 0, ret = 0;
-
-  __android_log_config_write();
-  write_transport_for_each_safe(transport, n, &__android_log_transport_write) {
-    __android_log_cache_available(transport);
-    if (!transport->logMask) {
-      list_remove(&transport->node);
-      continue;
-    }
-    if (!transport->open || ((*transport->open)() < 0)) {
-      if (transport->close) {
-        (*transport->close)();
-      }
-      list_remove(&transport->node);
-      continue;
-    }
-    ++ret;
-  }
-  write_transport_for_each_safe(transport, n, &__android_log_persist_write) {
-    __android_log_cache_available(transport);
-    if (!transport->logMask) {
-      list_remove(&transport->node);
-      continue;
-    }
-    if (!transport->open || ((*transport->open)() < 0)) {
-      if (transport->close) {
-        (*transport->close)();
-      }
-      list_remove(&transport->node);
-      continue;
-    }
-    ++i;
-  }
-  if (!ret && !i) {
+  if (!transport_initialize(android_log_write)) {
     return -ENODEV;
   }
 
-  return ret;
+  transport_initialize(android_log_persist_write);
+
+  return 1;
 }
 
 static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) {
-  struct android_log_transport_write* node;
   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,30 +263,9 @@
       return -EPERM;
     }
   } else {
-    /* Validate the incoming tag, tag content can not split across iovec */
-    char prio = ANDROID_LOG_VERBOSE;
-    const char* tag = static_cast<const char*>(vec[0].iov_base);
-    size_t len = vec[0].iov_len;
-    if (!tag) {
-      len = 0;
-    }
-    if (len > 0) {
-      prio = *tag;
-      if (len > 1) {
-        --len;
-        ++tag;
-      } else {
-        len = vec[1].iov_len;
-        tag = ((const char*)vec[1].iov_base);
-        if (!tag) {
-          len = 0;
-        }
-      }
-    }
-    /* tag must be nul terminated */
-    if (tag && strnlen(tag, len) >= len) {
-      tag = NULL;
-    }
+    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;
 
     if (!__android_log_is_loggable_len(prio, tag, len - 1, ANDROID_LOG_VERBOSE)) {
       errno = save_errno;
@@ -324,21 +283,18 @@
 #endif
 
   ret = 0;
-  i = 1 << log_id;
-  write_transport_for_each(node, &__android_log_transport_write) {
-    if (node->logMask & i) {
-      ssize_t retval;
-      retval = (*node->write)(log_id, &ts, vec, nr);
-      if (ret >= 0) {
-        ret = retval;
-      }
+  size_t i = 1 << log_id;
+
+  if (android_log_write != nullptr && (android_log_write->logMask & i)) {
+    ssize_t retval;
+    retval = android_log_write->write(log_id, &ts, vec, nr);
+    if (ret >= 0) {
+      ret = retval;
     }
   }
 
-  write_transport_for_each(node, &__android_log_persist_write) {
-    if (node->logMask & i) {
-      (void)(*node->write)(log_id, &ts, vec, nr);
-    }
+  if (android_log_persist_write != nullptr && (android_log_persist_write->logMask & i)) {
+    android_log_persist_write->write(log_id, &ts, vec, nr);
   }
 
   errno = save_errno;
@@ -354,9 +310,6 @@
     ret = __write_to_log_initialize();
     if (ret < 0) {
       __android_log_unlock();
-      if (!list_empty(&__android_log_persist_write)) {
-        __write_to_log_daemon(log_id, vec, nr);
-      }
       errno = save_errno;
       return ret;
     }
@@ -546,81 +499,3 @@
 
   return write_to_log(LOG_ID_SECURITY, vec, 4);
 }
-
-static int __write_to_log_null(log_id_t log_id, struct iovec* vec, size_t nr) {
-  size_t len, i;
-
-  if ((log_id < LOG_ID_MIN) || (log_id >= LOG_ID_MAX)) {
-    return -EINVAL;
-  }
-
-  for (len = i = 0; i < nr; ++i) {
-    len += vec[i].iov_len;
-  }
-  if (!len) {
-    return -EINVAL;
-  }
-  return len;
-}
-
-/* Following functions need access to our internal write_to_log status */
-
-int __android_log_transport;
-
-int android_set_log_transport(int transport_flag) {
-  int retval;
-
-  if (transport_flag < 0) {
-    return -EINVAL;
-  }
-
-  retval = LOGGER_NULL;
-
-  __android_log_lock();
-
-  if (transport_flag & LOGGER_NULL) {
-    write_to_log = __write_to_log_null;
-
-    __android_log_unlock();
-
-    return retval;
-  }
-
-  __android_log_transport &= LOGGER_LOGD;
-
-  transport_flag &= LOGGER_LOGD;
-
-  if (__android_log_transport != transport_flag) {
-    __android_log_transport = transport_flag;
-    __android_log_config_write_close();
-
-    write_to_log = __write_to_log_init;
-    /* generically we only expect these two values for write_to_log */
-  } else if ((write_to_log != __write_to_log_init) && (write_to_log != __write_to_log_daemon)) {
-    write_to_log = __write_to_log_init;
-  }
-
-  retval = __android_log_transport;
-
-  __android_log_unlock();
-
-  return retval;
-}
-
-int android_get_log_transport() {
-  int ret = LOGGER_DEFAULT;
-
-  __android_log_lock();
-  if (write_to_log == __write_to_log_null) {
-    ret = LOGGER_NULL;
-  } else {
-    __android_log_transport &= LOGGER_LOGD;
-    ret = __android_log_transport;
-    if ((write_to_log != __write_to_log_init) && (write_to_log != __write_to_log_daemon)) {
-      ret = -EINVAL;
-    }
-  }
-  __android_log_unlock();
-
-  return ret;
-}
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
index 005fec8..2db45a1 100644
--- a/liblog/pmsg_reader.cpp
+++ b/liblog/pmsg_reader.cpp
@@ -37,7 +37,6 @@
                      struct android_log_transport_context* transp);
 
 struct android_log_transport_read pmsgLoggerRead = {
-    .node = {&pmsgLoggerRead.node, &pmsgLoggerRead.node},
     .name = "pmsg",
     .available = pmsgAvailable,
     .version = pmsgVersion,
diff --git a/liblog/pmsg_writer.cpp b/liblog/pmsg_writer.cpp
index 69720ed..54980d9 100644
--- a/liblog/pmsg_writer.cpp
+++ b/liblog/pmsg_writer.cpp
@@ -29,7 +29,6 @@
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
-#include "config_write.h"
 #include "log_portability.h"
 #include "logger.h"
 #include "uio.h"
@@ -40,9 +39,9 @@
 static int pmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
 
 struct android_log_transport_write pmsgLoggerWrite = {
-    .node = {&pmsgLoggerWrite.node, &pmsgLoggerWrite.node},
-    .context.fd = -1,
     .name = "pmsg",
+    .logMask = 0,
+    .context.fd = -1,
     .available = pmsgAvailable,
     .open = pmsgOpen,
     .close = pmsgClose,
@@ -100,7 +99,7 @@
       return -EINVAL;
     }
 
-    if (SNET_EVENT_LOG_TAG != *static_cast<uint8_t*>(vec[0].iov_base)) {
+    if (SNET_EVENT_LOG_TAG != *static_cast<uint32_t*>(vec[0].iov_base)) {
       return -EPERM;
     }
   }
diff --git a/liblog/tests/liblog_benchmark.cpp b/liblog/tests/liblog_benchmark.cpp
index 7163743..4642b9b 100644
--- a/liblog/tests/liblog_benchmark.cpp
+++ b/liblog/tests/liblog_benchmark.cpp
@@ -29,7 +29,6 @@
 #include <benchmark/benchmark.h>
 #include <cutils/sockets.h>
 #include <log/event_tag_map.h>
-#include <log/log_transport.h>
 #include <private/android_logger.h>
 
 BENCHMARK_MAIN();
@@ -73,21 +72,6 @@
 }
 BENCHMARK(BM_log_maximum);
 
-static void set_log_null() {
-  android_set_log_transport(LOGGER_NULL);
-}
-
-static void set_log_default() {
-  android_set_log_transport(LOGGER_DEFAULT);
-}
-
-static void BM_log_maximum_null(benchmark::State& state) {
-  set_log_null();
-  BM_log_maximum(state);
-  set_log_default();
-}
-BENCHMARK(BM_log_maximum_null);
-
 /*
  *	Measure the time it takes to collect the time using
  * discrete acquisition (state.PauseTiming() to state.ResumeTiming())
@@ -618,13 +602,6 @@
 }
 BENCHMARK(BM_log_event_overhead_42);
 
-static void BM_log_event_overhead_null(benchmark::State& state) {
-  set_log_null();
-  BM_log_event_overhead(state);
-  set_log_default();
-}
-BENCHMARK(BM_log_event_overhead_null);
-
 /*
  *	Measure the time it takes to submit the android event logging call
  * using discrete acquisition under very-light load (<1% CPU utilization).
@@ -639,15 +616,6 @@
 }
 BENCHMARK(BM_log_light_overhead);
 
-static void BM_log_light_overhead_null(benchmark::State& state) {
-  set_log_null();
-  BM_log_light_overhead(state);
-  set_log_default();
-}
-// Default gets out of hand for this test, so we set a reasonable number of
-// iterations for a timely result.
-BENCHMARK(BM_log_light_overhead_null)->Iterations(500);
-
 static void caught_latency(int /*signum*/) {
   unsigned long long v = 0xDEADBEEFA55A5AA5ULL;
 
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/liblog/tests/log_wrap_test.cpp b/liblog/tests/log_wrap_test.cpp
index ebf0b15..c7dd8e8 100644
--- a/liblog/tests/log_wrap_test.cpp
+++ b/liblog/tests/log_wrap_test.cpp
@@ -27,12 +27,9 @@
 #include <log/log_properties.h>
 #include <log/log_read.h>
 #include <log/log_time.h>
-#include <log/log_transport.h>
 
 #ifdef __ANDROID__
 static void read_with_wrap() {
-  android_set_log_transport(LOGGER_LOGD);
-
   // Read the last line in the log to get a starting timestamp. We're assuming
   // the log is not empty.
   const int mode = ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
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/event.logtags b/lmkd/event.logtags
index 065c6db..452f411 100644
--- a/lmkd/event.logtags
+++ b/lmkd/event.logtags
@@ -17,8 +17,8 @@
 # Multiple values are separated by commas.
 #
 # The data type is a number from the following values:
-# 1: int
-# 2: long
+# 1: int32_t
+# 2: int64_t
 # 3: string
 # 4: list
 #
@@ -34,5 +34,5 @@
 #
 # TODO: generate ".java" and ".h" files with integer constants from this file.
 
-# for meminfo logs
-10195355 meminfo (MemFree|1),(Cached|1),(SwapCached|1),(Buffers|1),(Shmem|1),(Unevictable|1),(SwapTotal|1),(SwapFree|1),(ActiveAnon|1),(InactiveAnon|1),(ActiveFile|1),(InactiveFile|1),(SReclaimable|1),(SUnreclaim|1),(KernelStack|1),(PageTables|1),(ION_heap|1),(ION_heap_pool|1),(CmaFree|1)
+# for killinfo logs
+10195355 killinfo (Pid|1|5),(Uid|1|5),(OomAdj|1),(MinOomAdj|1),(TaskSize|1),(enum kill_reasons|1|5),(MemFree|1),(Cached|1),(SwapCached|1),(Buffers|1),(Shmem|1),(Unevictable|1),(SwapTotal|1),(SwapFree|1),(ActiveAnon|1),(InactiveAnon|1),(ActiveFile|1),(InactiveFile|1),(SReclaimable|1),(SUnreclaim|1),(KernelStack|1),(PageTables|1),(IonHeap|1),(IonHeapPool|1),(CmaFree|1)
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index 449088a..372e10f 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -84,7 +84,7 @@
 #define PERCEPTIBLE_APP_ADJ 200
 
 /* Android Logger event logtags (see event.logtags) */
-#define MEMINFO_LOG_TAG 10195355
+#define KILLINFO_LOG_TAG 10195355
 
 /* gid containing AID_SYSTEM required */
 #define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
@@ -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.
@@ -1394,7 +1394,7 @@
     char *buf;
     char *save_ptr;
     char *line;
-    char zone_name[LINE_MAX];
+    char zone_name[LINE_MAX + 1];
     struct zoneinfo_node *node = NULL;
     int node_idx = 0;
     int zone_idx = 0;
@@ -1566,7 +1566,17 @@
     return 0;
 }
 
-static void meminfo_log(union meminfo *mi) {
+static void killinfo_log(struct proc* procp, int min_oom_score, int tasksize,
+                         int kill_reason, union meminfo *mi) {
+    /* log process information */
+    android_log_write_int32(ctx, procp->pid);
+    android_log_write_int32(ctx, procp->uid);
+    android_log_write_int32(ctx, procp->oomadj);
+    android_log_write_int32(ctx, min_oom_score);
+    android_log_write_int32(ctx, (int32_t)min(tasksize * page_k, INT32_MAX));
+    android_log_write_int32(ctx, kill_reason);
+
+    /* log meminfo fields */
     for (int field_idx = 0; field_idx < MI_FIELD_COUNT; field_idx++) {
         android_log_write_int32(ctx, (int32_t)min(mi->arr[field_idx] * page_k, INT32_MAX));
     }
@@ -1640,7 +1650,8 @@
 static int last_killed_pid = -1;
 
 /* Kill one process specified by procp.  Returns the size of the process killed */
-static int kill_one_process(struct proc* procp, int min_oom_score, const char *reason) {
+static int kill_one_process(struct proc* procp, int min_oom_score, int kill_reason,
+                            const char *kill_desc, union meminfo *mi) {
     int pid = procp->pid;
     uid_t uid = procp->uid;
     int tgid;
@@ -1684,9 +1695,12 @@
     set_process_group_and_prio(pid, SP_FOREGROUND, ANDROID_PRIORITY_HIGHEST);
 
     inc_killcnt(procp->oomadj);
-    if (reason) {
+
+    killinfo_log(procp, min_oom_score, tasksize, kill_reason, mi);
+
+    if (kill_desc) {
         ALOGI("Kill '%s' (%d), uid %d, oom_adj %d to free %ldkB; reason: %s", taskname, pid,
-              uid, procp->oomadj, tasksize * page_k, reason);
+              uid, procp->oomadj, tasksize * page_k, kill_desc);
     } else {
         ALOGI("Kill '%s' (%d), uid %d, oom_adj %d to free %ldkB", taskname, pid,
               uid, procp->oomadj, tasksize * page_k);
@@ -1712,7 +1726,8 @@
  * Find one process to kill at or above the given oom_adj level.
  * Returns size of the killed process.
  */
-static int find_and_kill_process(int min_score_adj, const char *reason) {
+static int find_and_kill_process(int min_score_adj, int kill_reason, const char *kill_desc,
+                                 union meminfo *mi) {
     int i;
     int killed_size = 0;
     bool lmk_state_change_start = false;
@@ -1727,7 +1742,7 @@
             if (!procp)
                 break;
 
-            killed_size = kill_one_process(procp, min_score_adj, reason);
+            killed_size = kill_one_process(procp, min_score_adj, kill_reason, kill_desc, mi);
             if (killed_size >= 0) {
                 if (!lmk_state_change_start) {
                     lmk_state_change_start = true;
@@ -2051,7 +2066,7 @@
 
     /* Kill a process if necessary */
     if (kill_reason != NONE) {
-        int pages_freed = find_and_kill_process(min_score_adj, kill_desc);
+        int pages_freed = find_and_kill_process(min_score_adj, kill_reason, kill_desc, &mi);
         if (pages_freed > 0) {
             killing = true;
             if (cut_thrashing_limit) {
@@ -2062,7 +2077,6 @@
                 thrashing_limit = (thrashing_limit * (100 - thrashing_limit_decay_pct)) / 100;
             }
         }
-        meminfo_log(&mi);
     }
 
 no_kill:
@@ -2251,12 +2265,10 @@
 do_kill:
     if (low_ram_device) {
         /* For Go devices kill only one task */
-        if (find_and_kill_process(level_oomadj[level], NULL) == 0) {
+        if (find_and_kill_process(level_oomadj[level], -1, NULL, &mi) == 0) {
             if (debug_process_killing) {
                 ALOGI("Nothing to kill");
             }
-        } else {
-            meminfo_log(&mi);
         }
     } else {
         int pages_freed;
@@ -2276,7 +2288,7 @@
             min_score_adj = level_oomadj[level];
         }
 
-        pages_freed = find_and_kill_process(min_score_adj, NULL);
+        pages_freed = find_and_kill_process(min_score_adj, -1, NULL, &mi);
 
         if (pages_freed == 0) {
             /* Rate limit kill reports when nothing was reclaimed */
@@ -2289,9 +2301,7 @@
             last_kill_tm = curr_tm;
         }
 
-        /* Log meminfo whenever we kill or when report rate limit allows */
-        meminfo_log(&mi);
-
+        /* Log whenever we kill or when report rate limit allows */
         if (use_minfree_levels) {
             ALOGI("Reclaimed %ldkB, cache(%ldkB) and "
                 "free(%" PRId64 "kB)-reserved(%" PRId64 "kB) below min(%ldkB) for oom_adj %d",
@@ -2714,7 +2724,7 @@
     thrashing_limit_decay_pct = clamp(0, 100, property_get_int32("ro.lmk.thrashing_limit_decay",
         low_ram_device ? DEF_THRASHING_DECAY_LOWRAM : DEF_THRASHING_DECAY));
 
-    ctx = create_android_logger(MEMINFO_LOG_TAG);
+    ctx = create_android_logger(KILLINFO_LOG_TAG);
 
     statslog_init();
 
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index a0059db..c8f0a8b 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -58,15 +58,6 @@
 endif
 
 #######################################
-# fsverity_init
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:= fsverity_init
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_SRC_FILES := fsverity_init.sh
-include $(BUILD_PREBUILT)
-
-#######################################
 # init.environ.rc
 
 include $(CLEAR_VARS)
diff --git a/rootdir/fsverity_init.sh b/rootdir/fsverity_init.sh
deleted file mode 100644
index 4fee15f..0000000
--- a/rootdir/fsverity_init.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/system/bin/sh
-#
-# 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.
-#
-
-# Enforce fsverity signature checking
-echo 1 > /proc/sys/fs/verity/require_signatures
-
-# Load all keys
-for cert in /product/etc/security/fsverity/*.der; do
-  /system/bin/mini-keyctl padd asymmetric fsv_product .fs-verity < "$cert" ||
-    log -p e -t fsverity_init "Failed to load $cert"
-done
-
-DEBUGGABLE=$(getprop ro.debuggable)
-if [ $DEBUGGABLE != "1" ]; then
-  # Prevent future key links to .fs-verity keyring
-  /system/bin/mini-keyctl restrict_keyring .fs-verity ||
-    log -p e -t fsverity_init "Failed to restrict .fs-verity keyring"
-fi
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 9183901..bad0642 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -489,10 +489,6 @@
     mkdir /data/bootchart 0755 shell shell
     bootchart start
 
-    # Load fsverity keys. This needs to happen before apexd, as post-install of
-    # APEXes may rely on keys.
-    exec -- /system/bin/fsverity_init
-
     # Make sure that apexd is started in the default namespace
     enter_default_mount_ns
 
@@ -823,6 +819,9 @@
 
     class_start core
 
+    # Requires keystore (currently a core service) to be ready first.
+    exec -- /system/bin/fsverity_init
+
 on nonencrypted
     class_start main
     class_start late_start