Integrate adb with fastdeploy

Test: lunch sdk-eng && make sdk -j44
Test: lunch aosp_walleye-eng && cd system/core/adb && mm
Test: adb install -r -f --force-agent --local-agent ~/example_apks/example.apk
Test: adb install -r -f --no-streaming --force-agent --local-agent ~/example_apks/example.apk

Change-Id: Ia1c2160f87ea584656f8fdd67e314a260d39d607
diff --git a/adb/Android.bp b/adb/Android.bp
index 07f052f..bd87365 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -138,6 +138,8 @@
         "client/usb_libusb.cpp",
         "client/usb_dispatch.cpp",
         "client/transport_mdns.cpp",
+        "client/fastdeploy.cpp",
+        "client/fastdeploycallbacks.cpp",
     ],
 
     target: {
@@ -170,6 +172,12 @@
         "libdiagnose_usb",
         "libmdnssd",
         "libusb",
+        "libandroidfw",
+        "libziparchive",
+        "libz",
+        "libutils",
+        "liblog",
+        "libcutils",
     ],
 }
 
@@ -237,6 +245,7 @@
         "client/file_sync_client.cpp",
         "client/main.cpp",
         "client/console.cpp",
+        "client/adb_install.cpp",
         "client/line_printer.cpp",
         "shell_service_protocol.cpp",
     ],
@@ -251,6 +260,12 @@
         "liblog",
         "libmdnssd",
         "libusb",
+        "libandroidfw",
+        "libziparchive",
+        "libz",
+        "libutils",
+        "liblog",
+        "libcutils",
     ],
 
     stl: "libc++_static",
diff --git a/adb/client/adb_client.h b/adb/client/adb_client.h
index fca435e..d467539 100644
--- a/adb/client/adb_client.h
+++ b/adb/client/adb_client.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef _ADB_CLIENT_H_
-#define _ADB_CLIENT_H_
+#pragma once
 
 #include "adb.h"
 #include "sysdeps.h"
@@ -63,5 +62,3 @@
 
 // Get the feature set of the current preferred transport.
 bool adb_get_feature_set(FeatureSet* _Nonnull feature_set, std::string* _Nonnull error);
-
-#endif
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
new file mode 100644
index 0000000..d965a57
--- /dev/null
+++ b/adb/client/adb_install.cpp
@@ -0,0 +1,578 @@
+/*
+ * 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.
+ */
+
+#define TRACE_TAG ADB
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "adb.h"
+#include "adb_client.h"
+#include "adb_install.h"
+#include "adb_utils.h"
+#include "client/file_sync_client.h"
+#include "commandline.h"
+#include "fastdeploy.h"
+#include "sysdeps.h"
+
+#include <algorithm>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+static bool _use_legacy_install() {
+    FeatureSet features;
+    std::string error;
+    if (!adb_get_feature_set(&features, &error)) {
+        fprintf(stderr, "error: %s\n", error.c_str());
+        return true;
+    }
+    return !CanUseFeature(features, kFeatureCmd);
+}
+
+static int pm_command(int argc, const char** argv) {
+    std::string cmd = "pm";
+
+    while (argc-- > 0) {
+        cmd += " " + escape_arg(*argv++);
+    }
+
+    return send_shell_command(cmd);
+}
+
+static int uninstall_app_streamed(int argc, const char** argv) {
+    // 'adb uninstall' takes the same arguments as 'cmd package uninstall' on device
+    std::string cmd = "cmd package";
+    while (argc-- > 0) {
+        // deny the '-k' option until the remaining data/cache can be removed with adb/UI
+        if (strcmp(*argv, "-k") == 0) {
+            printf("The -k option uninstalls the application while retaining the "
+                   "data/cache.\n"
+                   "At the moment, there is no way to remove the remaining data.\n"
+                   "You will have to reinstall the application with the same "
+                   "signature, and fully "
+                   "uninstall it.\n"
+                   "If you truly wish to continue, execute 'adb shell cmd package "
+                   "uninstall -k'.\n");
+            return EXIT_FAILURE;
+        }
+        cmd += " " + escape_arg(*argv++);
+    }
+
+    return send_shell_command(cmd);
+}
+
+static int uninstall_app_legacy(int argc, const char** argv) {
+    /* if the user choose the -k option, we refuse to do it until devices are
+       out with the option to uninstall the remaining data somehow (adb/ui) */
+    for (int i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "-k")) {
+            printf("The -k option uninstalls the application while retaining the "
+                   "data/cache.\n"
+                   "At the moment, there is no way to remove the remaining data.\n"
+                   "You will have to reinstall the application with the same "
+                   "signature, and fully "
+                   "uninstall it.\n"
+                   "If you truly wish to continue, execute 'adb shell pm uninstall "
+                   "-k'\n.");
+            return EXIT_FAILURE;
+        }
+    }
+
+    /* 'adb uninstall' takes the same arguments as 'pm uninstall' on device */
+    return pm_command(argc, argv);
+}
+
+int uninstall_app(int argc, const char** argv) {
+    if (_use_legacy_install()) {
+        return uninstall_app_legacy(argc, argv);
+    }
+    return uninstall_app_streamed(argc, argv);
+}
+
+static void read_status_line(int fd, char* buf, size_t count) {
+    count--;
+    while (count > 0) {
+        int len = adb_read(fd, buf, count);
+        if (len <= 0) {
+            break;
+        }
+
+        buf += len;
+        count -= len;
+    }
+    *buf = '\0';
+}
+
+static int delete_device_patch_file(const char* apkPath) {
+    std::string patchDevicePath = get_patch_path(apkPath);
+    return delete_device_file(patchDevicePath);
+}
+
+std::string get_temp_host_filename() {
+#ifdef _WIN32
+    CHAR temp_path[MAX_PATH];
+    CHAR temp_file_path[MAX_PATH];
+    DWORD temp_path_result = GetTempPathA(MAX_PATH, temp_path);
+    if (temp_path_result == 0) {
+        printf("Error determining temp path\n");
+        return std::string("");
+    } else {
+        DWORD temp_file_name_result = GetTempFileNameA(temp_path, "", 0, temp_file_path);
+        if (temp_file_name_result == 0) {
+            printf("Error determining temp filename\n");
+            return std::string("");
+        }
+        return std::string(temp_file_path);
+    }
+#else
+    return std::tmpnam(nullptr);
+#endif
+}
+
+static int install_app_streamed(int argc, const char** argv, bool use_fastdeploy,
+                                bool use_localagent, const char* adb_path) {
+    printf("Performing Streamed Install\n");
+
+    // The last argument must be the APK file
+    const char* file = argv[argc - 1];
+    if (!android::base::EndsWithIgnoreCase(file, ".apk")) {
+        return syntax_error("filename doesn't end .apk: %s", file);
+    }
+
+    if (use_fastdeploy == true) {
+        std::string metadataTmpPath = get_temp_host_filename();
+        std::string patchTmpPath = get_temp_host_filename();
+
+        FILE* metadataFile = fopen(metadataTmpPath.c_str(), "wb");
+        int metadata_len = extract_metadata(file, metadataFile);
+        fclose(metadataFile);
+
+        int result = -1;
+        if (metadata_len <= 0) {
+            printf("failed to extract metadata %d\n", metadata_len);
+            return 1;
+        } else {
+            int create_patch_result = create_patch(file, metadataTmpPath.c_str(),
+                                                   patchTmpPath.c_str(), use_localagent, adb_path);
+            if (create_patch_result != 0) {
+                printf("Patch creation failure, error code: %d\n", create_patch_result);
+                result = create_patch_result;
+                goto cleanup_streamed_apk;
+            } else {
+                std::vector<const char*> pm_args;
+                // pass all but 1st (command) and last (apk path) parameters through to pm for
+                // session creation
+                for (int i = 1; i < argc - 1; i++) {
+                    pm_args.push_back(argv[i]);
+                }
+                int apply_patch_result =
+                        install_patch(file, patchTmpPath.c_str(), pm_args.size(), pm_args.data());
+                if (apply_patch_result != 0) {
+                    printf("Patch application failure, error code: %d\n", apply_patch_result);
+                    result = apply_patch_result;
+                    goto cleanup_streamed_apk;
+                }
+            }
+        }
+
+    cleanup_streamed_apk:
+        delete_device_patch_file(file);
+        delete_host_file(metadataTmpPath);
+        delete_host_file(patchTmpPath);
+        return result;
+    } else {
+        struct stat sb;
+        if (stat(file, &sb) == -1) {
+            fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
+            return 1;
+        }
+
+        int localFd = adb_open(file, O_RDONLY);
+        if (localFd < 0) {
+            fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
+            return 1;
+        }
+
+        std::string error;
+        std::string cmd = "exec:cmd package";
+
+        // don't copy the APK name, but, copy the rest of the arguments as-is
+        while (argc-- > 1) {
+            cmd += " " + escape_arg(std::string(*argv++));
+        }
+
+        // add size parameter [required for streaming installs]
+        // do last to override any user specified value
+        cmd += " " + android::base::StringPrintf("-S %" PRIu64, static_cast<uint64_t>(sb.st_size));
+
+        int remoteFd = adb_connect(cmd, &error);
+        if (remoteFd < 0) {
+            fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
+            adb_close(localFd);
+            return 1;
+        }
+
+        char buf[BUFSIZ];
+        copy_to_file(localFd, remoteFd);
+        read_status_line(remoteFd, buf, sizeof(buf));
+
+        adb_close(localFd);
+        adb_close(remoteFd);
+
+        if (!strncmp("Success", buf, 7)) {
+            fputs(buf, stdout);
+            return 0;
+        }
+        fprintf(stderr, "adb: failed to install %s: %s", file, buf);
+        return 1;
+    }
+}
+
+static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy, bool use_localagent,
+                              const char* adb_path) {
+    static const char* const DATA_DEST = "/data/local/tmp/%s";
+    static const char* const SD_DEST = "/sdcard/tmp/%s";
+    const char* where = DATA_DEST;
+
+    printf("Performing Push Install\n");
+
+    for (int i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "-s")) {
+            where = SD_DEST;
+        }
+    }
+
+    // Find last APK argument.
+    // All other arguments passed through verbatim.
+    int last_apk = -1;
+    for (int i = argc - 1; i >= 0; i--) {
+        if (android::base::EndsWithIgnoreCase(argv[i], ".apk")) {
+            last_apk = i;
+            break;
+        }
+    }
+
+    if (last_apk == -1) return syntax_error("need APK file on command line");
+
+    int result = -1;
+    std::vector<const char*> apk_file = {argv[last_apk]};
+    std::string apk_dest =
+            android::base::StringPrintf(where, android::base::Basename(argv[last_apk]).c_str());
+
+    std::string metadataTmpPath = get_temp_host_filename();
+    std::string patchTmpPath = get_temp_host_filename();
+
+    if (use_fastdeploy == true) {
+        FILE* metadataFile = fopen(metadataTmpPath.c_str(), "wb");
+        int metadata_len = extract_metadata(apk_file[0], metadataFile);
+        fclose(metadataFile);
+
+        if (metadata_len <= 0) {
+            printf("failed to extract metadata %d\n", metadata_len);
+            return 1;
+        } else {
+            int create_patch_result = create_patch(apk_file[0], metadataTmpPath.c_str(),
+                                                   patchTmpPath.c_str(), use_localagent, adb_path);
+            if (create_patch_result != 0) {
+                printf("Patch creation failure, error code: %d\n", create_patch_result);
+                result = create_patch_result;
+                goto cleanup_apk;
+            } else {
+                int apply_patch_result =
+                        apply_patch_on_device(apk_file[0], patchTmpPath.c_str(), apk_dest.c_str());
+                if (apply_patch_result != 0) {
+                    printf("Patch application failure, error code: %d\n", apply_patch_result);
+                    result = apply_patch_result;
+                    goto cleanup_apk;
+                }
+            }
+        }
+    } 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:
+    delete_device_patch_file(apk_file[0]);
+    delete_device_file(apk_dest);
+    delete_host_file(metadataTmpPath);
+    delete_host_file(patchTmpPath);
+    return result;
+}
+
+int install_app(int argc, const char** argv) {
+    std::vector<int> processedArgIndicies;
+    enum installMode {
+        INSTALL_DEFAULT,
+        INSTALL_PUSH,
+        INSTALL_STREAM
+    } 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++) {
+        if (!strcmp(argv[i], "--streaming")) {
+            processedArgIndicies.push_back(i);
+            installMode = INSTALL_STREAM;
+        } else if (!strcmp(argv[i], "--no-streaming")) {
+            processedArgIndicies.push_back(i);
+            installMode = INSTALL_PUSH;
+        } else if (!strcmp(argv[i], "-r")) {
+            // Note that this argument is not added to processedArgIndicies because it
+            // must be passed through to pm
+            is_reinstall = true;
+        } else if (!strcmp(argv[i], "--fastdeploy")) {
+            processedArgIndicies.push_back(i);
+            use_fastdeploy = true;
+        } else if (!strcmp(argv[i], "--no-fastdeploy")) {
+            processedArgIndicies.push_back(i);
+            use_fastdeploy = false;
+        } else if (!strcmp(argv[i], "-f")) {
+            processedArgIndicies.push_back(i);
+            use_fastdeploy = true;
+        } else if (!strcmp(argv[i], "--force-agent")) {
+            processedArgIndicies.push_back(i);
+            agent_update_strategy = FastDeploy_AgentUpdateAlways;
+        } else if (!strcmp(argv[i], "--date-check-agent")) {
+            processedArgIndicies.push_back(i);
+            agent_update_strategy = FastDeploy_AgentUpdateNewerTimeStamp;
+        } 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
+        }
+        // TODO: --installlog <filename>
+    }
+
+    if (installMode == INSTALL_DEFAULT) {
+        if (_use_legacy_install()) {
+            installMode = INSTALL_PUSH;
+        } else {
+            installMode = INSTALL_STREAM;
+        }
+    }
+
+    if (installMode == INSTALL_STREAM && _use_legacy_install() == true) {
+        return syntax_error("Attempting to use streaming install on unsupported deivce.");
+    }
+
+    if (use_fastdeploy == true && is_reinstall == false) {
+        printf("Fast Deploy is only available with -r.\n");
+        use_fastdeploy = false;
+    }
+
+    if (use_fastdeploy == true && 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;
+    }
+
+    std::vector<const char*> passthrough_argv;
+    for (int i = 0; i < argc; i++) {
+        if (std::find(processedArgIndicies.begin(), processedArgIndicies.end(), i) ==
+            processedArgIndicies.end()) {
+            passthrough_argv.push_back(argv[i]);
+        }
+    }
+
+    std::string adb_path = android::base::GetExecutablePath();
+
+    if (adb_path.length() == 0) {
+        return 1;
+    }
+    if (use_fastdeploy == true) {
+        bool agent_up_to_date =
+                update_agent(agent_update_strategy, use_localagent, adb_path.c_str());
+        if (agent_up_to_date == false) {
+            printf("Failed to update agent, exiting\n");
+            return 1;
+        }
+    }
+
+    switch (installMode) {
+        case INSTALL_PUSH:
+            return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(),
+                                      use_fastdeploy, use_localagent, adb_path.c_str());
+        case INSTALL_STREAM:
+            return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(),
+                                        use_fastdeploy, use_localagent, adb_path.c_str());
+        case INSTALL_DEFAULT:
+        default:
+            return 1;
+    }
+}
+
+int install_multiple_app(int argc, const char** argv) {
+    // Find all APK arguments starting at end.
+    // All other arguments passed through verbatim.
+    int first_apk = -1;
+    uint64_t total_size = 0;
+    for (int i = argc - 1; i >= 0; i--) {
+        const char* file = argv[i];
+
+        if (android::base::EndsWithIgnoreCase(file, ".apk")) {
+            struct stat sb;
+            if (stat(file, &sb) != -1) total_size += sb.st_size;
+            first_apk = i;
+        } else {
+            break;
+        }
+    }
+
+    if (first_apk == -1) return syntax_error("need APK file on command line");
+
+    std::string install_cmd;
+    if (_use_legacy_install()) {
+        install_cmd = "exec:pm";
+    } else {
+        install_cmd = "exec:cmd package";
+    }
+
+    std::string cmd = android::base::StringPrintf("%s install-create -S %" PRIu64,
+                                                  install_cmd.c_str(), total_size);
+    for (int i = 1; i < first_apk; i++) {
+        cmd += " " + escape_arg(argv[i]);
+    }
+
+    // Create install session
+    std::string error;
+    int fd = adb_connect(cmd, &error);
+    if (fd < 0) {
+        fprintf(stderr, "adb: connect error for create: %s\n", error.c_str());
+        return EXIT_FAILURE;
+    }
+    char buf[BUFSIZ];
+    read_status_line(fd, buf, sizeof(buf));
+    adb_close(fd);
+
+    int session_id = -1;
+    if (!strncmp("Success", buf, 7)) {
+        char* start = strrchr(buf, '[');
+        char* end = strrchr(buf, ']');
+        if (start && end) {
+            *end = '\0';
+            session_id = strtol(start + 1, nullptr, 10);
+        }
+    }
+    if (session_id < 0) {
+        fprintf(stderr, "adb: failed to create session\n");
+        fputs(buf, stderr);
+        return EXIT_FAILURE;
+    }
+
+    // Valid session, now stream the APKs
+    int success = 1;
+    for (int i = first_apk; i < argc; i++) {
+        const char* file = argv[i];
+        struct stat sb;
+        if (stat(file, &sb) == -1) {
+            fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
+            success = 0;
+            goto finalize_session;
+        }
+
+        std::string cmd =
+                android::base::StringPrintf("%s install-write -S %" PRIu64 " %d %d_%s -",
+                                            install_cmd.c_str(), static_cast<uint64_t>(sb.st_size),
+                                            session_id, i, android::base::Basename(file).c_str());
+
+        int localFd = adb_open(file, O_RDONLY);
+        if (localFd < 0) {
+            fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
+            success = 0;
+            goto finalize_session;
+        }
+
+        std::string error;
+        int remoteFd = adb_connect(cmd, &error);
+        if (remoteFd < 0) {
+            fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
+            adb_close(localFd);
+            success = 0;
+            goto finalize_session;
+        }
+
+        copy_to_file(localFd, remoteFd);
+        read_status_line(remoteFd, buf, sizeof(buf));
+
+        adb_close(localFd);
+        adb_close(remoteFd);
+
+        if (strncmp("Success", buf, 7)) {
+            fprintf(stderr, "adb: failed to write %s\n", file);
+            fputs(buf, stderr);
+            success = 0;
+            goto finalize_session;
+        }
+    }
+
+finalize_session:
+    // Commit session if we streamed everything okay; otherwise abandon
+    std::string service = android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
+                                                      success ? "commit" : "abandon", session_id);
+    fd = adb_connect(service, &error);
+    if (fd < 0) {
+        fprintf(stderr, "adb: connect error for finalize: %s\n", error.c_str());
+        return EXIT_FAILURE;
+    }
+    read_status_line(fd, buf, sizeof(buf));
+    adb_close(fd);
+
+    if (!strncmp("Success", buf, 7)) {
+        fputs(buf, stdout);
+        return 0;
+    }
+    fprintf(stderr, "adb: failed to finalize session\n");
+    fputs(buf, stderr);
+    return EXIT_FAILURE;
+}
+
+int delete_device_file(const std::string& filename) {
+    std::string cmd = "rm -f " + escape_arg(filename);
+    return send_shell_command(cmd);
+}
+
+int delete_host_file(const std::string& filename) {
+#ifdef _WIN32
+    BOOL delete_return = DeleteFileA(filename.c_str());
+    if (delete_return != 0) {
+        return 0;
+    } else {
+        DWORD last_error = GetLastError();
+        printf("Error [%ld] deleting: %s\n", last_error, filename.c_str());
+        return delete_return;
+    }
+#else
+    std::string cmd = "rm -f " + escape_arg(filename);
+    return system(cmd.c_str());
+#endif
+}
diff --git a/adb/client/adb_install.h b/adb/client/adb_install.h
new file mode 100644
index 0000000..e9410a9
--- /dev/null
+++ b/adb/client/adb_install.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ADB_INSTALL_H
+#define ADB_INSTALL_H
+
+#include "fastdeploy.h"
+
+int install_app(int argc, const char** argv);
+int install_multiple_app(int argc, const char** argv);
+int uninstall_app(int argc, const char** argv);
+
+int delete_device_file(const std::string& filename);
+int delete_host_file(const std::string& filename);
+
+#endif
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index da273fd..6ed5a50 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -52,23 +52,19 @@
 #include "adb.h"
 #include "adb_auth.h"
 #include "adb_client.h"
+#include "adb_install.h"
 #include "adb_io.h"
 #include "adb_unique_fd.h"
 #include "adb_utils.h"
 #include "bugreport.h"
 #include "client/file_sync_client.h"
 #include "commandline.h"
+#include "fastdeploy.h"
 #include "services.h"
 #include "shell_protocol.h"
 #include "sysdeps/chrono.h"
 #include "sysdeps/memory.h"
 
-static int install_app(int argc, const char** argv);
-static int install_multiple_app(int argc, const char** argv);
-static int uninstall_app(int argc, const char** argv);
-static int install_app_legacy(int argc, const char** argv);
-static int uninstall_app_legacy(int argc, const char** argv);
-
 extern int gListenAll;
 
 DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK(nullptr, nullptr);
@@ -160,6 +156,17 @@
         "     -p: partial application install (install-multiple only)\n"
         "     -g: grant all runtime permissions\n"
         "     --instant: cause the app to be installed as an ephemeral install app\n"
+        "     --no-streaming: always push APK to device and invoke Package Manager as separate steps\n"
+        "     --streaming: force streaming APK directly into Package Manager\n"
+        "     -f/--fastdeploy: use fast deploy (only valid with -r)\n"
+        "     --no-fastdeploy: prevent use of fast deploy (only valid with -r)\n"
+        "     --force-agent: force update of deployment agent when using fast deploy\n"
+        "     --date-check-agent: update deployment agent when local version is newer and using fast deploy\n"
+        "     --version-check-agent: update deployment agent when local version has different version code and using fast deploy\n"
+#ifndef _WIN32
+        "     --local-agent: locate agent files from local source build (instead of SDK location)\n"
+#endif
+        //TODO--installlog <filename>
         " uninstall [-k] PACKAGE\n"
         "     remove this app package from the device\n"
         "     '-k': keep the data and cache directories\n"
@@ -306,21 +313,6 @@
     return callback->Done(exit_code);
 }
 
-static void read_status_line(int fd, char* buf, size_t count)
-{
-    count--;
-    while (count > 0) {
-        int len = adb_read(fd, buf, count);
-        if (len <= 0) {
-            break;
-        }
-
-        buf += len;
-        count -= len;
-    }
-    *buf = '\0';
-}
-
 static void stdinout_raw_prologue(int inFd, int outFd, int& old_stdin_mode, int& old_stdout_mode) {
     if (inFd == STDIN_FILENO) {
         stdin_raw_init();
@@ -361,7 +353,7 @@
 #endif
 }
 
-static void copy_to_file(int inFd, int outFd) {
+void copy_to_file(int inFd, int outFd) {
     constexpr size_t BUFSIZE = 32 * 1024;
     std::vector<char> buf(BUFSIZE);
     int len;
@@ -1315,16 +1307,6 @@
 #endif
 }
 
-static bool _use_legacy_install() {
-    FeatureSet features;
-    std::string error;
-    if (!adb_get_feature_set(&features, &error)) {
-        fprintf(stderr, "error: %s\n", error.c_str());
-        return true;
-    }
-    return !CanUseFeature(features, kFeatureCmd);
-}
-
 int adb_commandline(int argc, const char** argv) {
     int no_daemon = 0;
     int is_daemon = 0;
@@ -1706,9 +1688,6 @@
     }
     else if (!strcmp(argv[0], "install")) {
         if (argc < 2) return syntax_error("install requires an argument");
-        if (_use_legacy_install()) {
-            return install_app_legacy(argc, argv);
-        }
         return install_app(argc, argv);
     }
     else if (!strcmp(argv[0], "install-multiple")) {
@@ -1717,9 +1696,6 @@
     }
     else if (!strcmp(argv[0], "uninstall")) {
         if (argc < 2) return syntax_error("uninstall requires an argument");
-        if (_use_legacy_install()) {
-            return uninstall_app_legacy(argc, argv);
-        }
         return uninstall_app(argc, argv);
     }
     else if (!strcmp(argv[0], "sync")) {
@@ -1844,270 +1820,3 @@
     syntax_error("unknown command %s", argv[0]);
     return 1;
 }
-
-static int uninstall_app(int argc, const char** argv) {
-    // 'adb uninstall' takes the same arguments as 'cmd package uninstall' on device
-    std::string cmd = "cmd package";
-    while (argc-- > 0) {
-        // deny the '-k' option until the remaining data/cache can be removed with adb/UI
-        if (strcmp(*argv, "-k") == 0) {
-            printf(
-                "The -k option uninstalls the application while retaining the data/cache.\n"
-                "At the moment, there is no way to remove the remaining data.\n"
-                "You will have to reinstall the application with the same signature, and fully uninstall it.\n"
-                "If you truly wish to continue, execute 'adb shell cmd package uninstall -k'.\n");
-            return EXIT_FAILURE;
-        }
-        cmd += " " + escape_arg(*argv++);
-    }
-
-    return send_shell_command(cmd);
-}
-
-static int install_app(int argc, const char** argv) {
-    // The last argument must be the APK file
-    const char* file = argv[argc - 1];
-    if (!android::base::EndsWithIgnoreCase(file, ".apk")) {
-        return syntax_error("filename doesn't end .apk: %s", file);
-    }
-
-    struct stat sb;
-    if (stat(file, &sb) == -1) {
-        fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
-        return 1;
-    }
-
-    int localFd = adb_open(file, O_RDONLY);
-    if (localFd < 0) {
-        fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
-        return 1;
-    }
-
-    std::string error;
-    std::string cmd = "exec:cmd package";
-
-    // don't copy the APK name, but, copy the rest of the arguments as-is
-    while (argc-- > 1) {
-        cmd += " " + escape_arg(std::string(*argv++));
-    }
-
-    // add size parameter [required for streaming installs]
-    // do last to override any user specified value
-    cmd += " " + android::base::StringPrintf("-S %" PRIu64, static_cast<uint64_t>(sb.st_size));
-
-    int remoteFd = adb_connect(cmd, &error);
-    if (remoteFd < 0) {
-        fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
-        adb_close(localFd);
-        return 1;
-    }
-
-    char buf[BUFSIZ];
-    copy_to_file(localFd, remoteFd);
-    read_status_line(remoteFd, buf, sizeof(buf));
-
-    adb_close(localFd);
-    adb_close(remoteFd);
-
-    if (!strncmp("Success", buf, 7)) {
-        fputs(buf, stdout);
-        return 0;
-    }
-    fprintf(stderr, "adb: failed to install %s: %s", file, buf);
-    return 1;
-}
-
-static int install_multiple_app(int argc, const char** argv) {
-    // Find all APK arguments starting at end.
-    // All other arguments passed through verbatim.
-    int first_apk = -1;
-    uint64_t total_size = 0;
-    for (int i = argc - 1; i >= 0; i--) {
-        const char* file = argv[i];
-
-        if (android::base::EndsWithIgnoreCase(file, ".apk") ||
-            android::base::EndsWithIgnoreCase(file, ".dm")) {
-            struct stat sb;
-            if (stat(file, &sb) != -1) total_size += sb.st_size;
-            first_apk = i;
-        } else {
-            break;
-        }
-    }
-
-    if (first_apk == -1) return syntax_error("need APK file on command line");
-
-    std::string install_cmd;
-    if (_use_legacy_install()) {
-        install_cmd = "exec:pm";
-    } else {
-        install_cmd = "exec:cmd package";
-    }
-
-    std::string cmd = android::base::StringPrintf("%s install-create -S %" PRIu64, install_cmd.c_str(), total_size);
-    for (int i = 1; i < first_apk; i++) {
-        cmd += " " + escape_arg(argv[i]);
-    }
-
-    // Create install session
-    std::string error;
-    int fd = adb_connect(cmd, &error);
-    if (fd < 0) {
-        fprintf(stderr, "adb: connect error for create: %s\n", error.c_str());
-        return EXIT_FAILURE;
-    }
-    char buf[BUFSIZ];
-    read_status_line(fd, buf, sizeof(buf));
-    adb_close(fd);
-
-    int session_id = -1;
-    if (!strncmp("Success", buf, 7)) {
-        char* start = strrchr(buf, '[');
-        char* end = strrchr(buf, ']');
-        if (start && end) {
-            *end = '\0';
-            session_id = strtol(start + 1, nullptr, 10);
-        }
-    }
-    if (session_id < 0) {
-        fprintf(stderr, "adb: failed to create session\n");
-        fputs(buf, stderr);
-        return EXIT_FAILURE;
-    }
-
-    // Valid session, now stream the APKs
-    int success = 1;
-    for (int i = first_apk; i < argc; i++) {
-        const char* file = argv[i];
-        struct stat sb;
-        if (stat(file, &sb) == -1) {
-            fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
-            success = 0;
-            goto finalize_session;
-        }
-
-        std::string cmd = android::base::StringPrintf(
-            "%s install-write -S %" PRIu64 " %d %s -", install_cmd.c_str(),
-            static_cast<uint64_t>(sb.st_size), session_id, android::base::Basename(file).c_str());
-
-        int localFd = adb_open(file, O_RDONLY);
-        if (localFd < 0) {
-            fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
-            success = 0;
-            goto finalize_session;
-        }
-
-        std::string error;
-        int remoteFd = adb_connect(cmd, &error);
-        if (remoteFd < 0) {
-            fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
-            adb_close(localFd);
-            success = 0;
-            goto finalize_session;
-        }
-
-        copy_to_file(localFd, remoteFd);
-        read_status_line(remoteFd, buf, sizeof(buf));
-
-        adb_close(localFd);
-        adb_close(remoteFd);
-
-        if (strncmp("Success", buf, 7)) {
-            fprintf(stderr, "adb: failed to write %s\n", file);
-            fputs(buf, stderr);
-            success = 0;
-            goto finalize_session;
-        }
-    }
-
-finalize_session:
-    // Commit session if we streamed everything okay; otherwise abandon
-    std::string service =
-            android::base::StringPrintf("%s install-%s %d",
-                                        install_cmd.c_str(), success ? "commit" : "abandon", session_id);
-    fd = adb_connect(service, &error);
-    if (fd < 0) {
-        fprintf(stderr, "adb: connect error for finalize: %s\n", error.c_str());
-        return EXIT_FAILURE;
-    }
-    read_status_line(fd, buf, sizeof(buf));
-    adb_close(fd);
-
-    if (!strncmp("Success", buf, 7)) {
-        fputs(buf, stdout);
-        return 0;
-    }
-    fprintf(stderr, "adb: failed to finalize session\n");
-    fputs(buf, stderr);
-    return EXIT_FAILURE;
-}
-
-static int pm_command(int argc, const char** argv) {
-    std::string cmd = "pm";
-
-    while (argc-- > 0) {
-        cmd += " " + escape_arg(*argv++);
-    }
-
-    return send_shell_command(cmd);
-}
-
-static int uninstall_app_legacy(int argc, const char** argv) {
-    /* if the user choose the -k option, we refuse to do it until devices are
-       out with the option to uninstall the remaining data somehow (adb/ui) */
-    int i;
-    for (i = 1; i < argc; i++) {
-        if (!strcmp(argv[i], "-k")) {
-            printf(
-                "The -k option uninstalls the application while retaining the data/cache.\n"
-                "At the moment, there is no way to remove the remaining data.\n"
-                "You will have to reinstall the application with the same signature, and fully uninstall it.\n"
-                "If you truly wish to continue, execute 'adb shell pm uninstall -k'\n.");
-            return EXIT_FAILURE;
-        }
-    }
-
-    /* 'adb uninstall' takes the same arguments as 'pm uninstall' on device */
-    return pm_command(argc, argv);
-}
-
-static int delete_file(const std::string& filename) {
-    std::string cmd = "rm -f " + escape_arg(filename);
-    return send_shell_command(cmd);
-}
-
-static int install_app_legacy(int argc, const char** argv) {
-    static const char *const DATA_DEST = "/data/local/tmp/%s";
-    static const char *const SD_DEST = "/sdcard/tmp/%s";
-    const char* where = DATA_DEST;
-
-    for (int i = 1; i < argc; i++) {
-        if (!strcmp(argv[i], "-s")) {
-            where = SD_DEST;
-        }
-    }
-
-    // Find last APK argument.
-    // All other arguments passed through verbatim.
-    int last_apk = -1;
-    for (int i = argc - 1; i >= 0; i--) {
-        if (android::base::EndsWithIgnoreCase(argv[i], ".apk")) {
-            last_apk = i;
-            break;
-        }
-    }
-
-    if (last_apk == -1) return syntax_error("need APK file on command line");
-
-    int result = -1;
-    std::vector<const char*> apk_file = {argv[last_apk]};
-    std::string apk_dest = android::base::StringPrintf(
-        where, android::base::Basename(argv[last_apk]).c_str());
-    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:
-    delete_file(apk_dest);
-    return result;
-}
diff --git a/adb/client/commandline.h b/adb/client/commandline.h
index 3aa03a7..6cfd4f7 100644
--- a/adb/client/commandline.h
+++ b/adb/client/commandline.h
@@ -96,6 +96,8 @@
 
 int adb_commandline(int argc, const char** argv);
 
+void copy_to_file(int inFd, int outFd);
+
 // Connects to the device "shell" service with |command| and prints the
 // resulting output.
 // if |callback| is non-null, stdout/stderr output will be handled by it.
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
new file mode 100644
index 0000000..cd42b56
--- /dev/null
+++ b/adb/client/fastdeploy.cpp
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/ZipFileRO.h>
+#include <libgen.h>
+#include <algorithm>
+
+#include "client/file_sync_client.h"
+#include "commandline.h"
+#include "fastdeploy.h"
+#include "fastdeploycallbacks.h"
+#include "utils/String16.h"
+
+const long kRequiredAgentVersion = 0x00000001;
+
+const char* kDeviceAgentPath = "/data/local/tmp/";
+
+long get_agent_version() {
+    std::vector<char> versionOutputBuffer;
+    std::vector<char> versionErrorBuffer;
+
+    int statusCode = capture_shell_command("/data/local/tmp/deployagent.sh version",
+                                           &versionOutputBuffer, &versionErrorBuffer);
+    long version = -1;
+
+    if (statusCode == 0 && versionOutputBuffer.size() > 0) {
+        version = strtol((char*)versionOutputBuffer.data(), NULL, 16);
+    }
+
+    return version;
+}
+
+int get_device_api_level() {
+    std::vector<char> sdkVersionOutputBuffer;
+    std::vector<char> sdkVersionErrorBuffer;
+    int api_level = -1;
+
+    int statusCode = capture_shell_command("getprop ro.build.version.sdk", &sdkVersionOutputBuffer,
+                                           &sdkVersionErrorBuffer);
+    if (statusCode == 0 && statusCode == 0 && sdkVersionOutputBuffer.size() > 0) {
+        api_level = strtol((char*)sdkVersionOutputBuffer.data(), NULL, 10);
+    }
+
+    return api_level;
+}
+
+// local_path - must start with a '/' and be relative to $ANDROID_PRODUCT_OUT
+static bool get_agent_component_host_path(bool use_localagent, const char* adb_path,
+                                          const char* local_path, const char* sdk_path,
+                                          std::string* output_path) {
+    std::string mutable_adb_path = adb_path;
+    const char* adb_dir = dirname(&mutable_adb_path[0]);
+    if (adb_dir == nullptr) {
+        return false;
+    }
+
+    if (use_localagent) {
+        const char* product_out = getenv("ANDROID_PRODUCT_OUT");
+        if (product_out == nullptr) {
+            return false;
+        }
+        *output_path = android::base::StringPrintf("%s%s", product_out, local_path);
+        return true;
+    } else {
+        *output_path = android::base::StringPrintf("%s%s", adb_dir, sdk_path);
+        return true;
+    }
+    return false;
+}
+
+static bool deploy_agent(bool checkTimeStamps, bool use_localagent, const char* adb_path) {
+    std::vector<const char*> srcs;
+
+    std::string agent_jar_path;
+    if (get_agent_component_host_path(use_localagent, adb_path, "/system/framework/deployagent.jar",
+                                      "/deployagent.jar", &agent_jar_path)) {
+        srcs.push_back(agent_jar_path.c_str());
+    } else {
+        return false;
+    }
+
+    std::string agent_sh_path;
+    if (get_agent_component_host_path(use_localagent, adb_path, "/system/bin/deployagent.sh",
+                                      "/deployagent.sh", &agent_sh_path)) {
+        srcs.push_back(agent_sh_path.c_str());
+    } else {
+        return false;
+    }
+
+    if (do_sync_push(srcs, kDeviceAgentPath, checkTimeStamps)) {
+        // on windows the shell script might have lost execute permission
+        // so need to set this explicitly
+        const char* kChmodCommandPattern = "chmod 777 %sdeployagent.sh";
+        std::string chmodCommand =
+                android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentPath);
+        int ret = send_shell_command(chmodCommand.c_str());
+        return (ret == 0);
+    } else {
+        return false;
+    }
+}
+
+bool update_agent(FastDeploy_AgentUpdateStrategy agentUpdateStrategy, bool use_localagent,
+                  const char* adb_path) {
+    long agent_version = get_agent_version();
+    switch (agentUpdateStrategy) {
+        case FastDeploy_AgentUpdateAlways:
+            if (deploy_agent(false, use_localagent, adb_path) == false) {
+                return false;
+            }
+            break;
+        case FastDeploy_AgentUpdateNewerTimeStamp:
+            if (deploy_agent(true, use_localagent, adb_path) == false) {
+                return false;
+            }
+            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);
+                }
+                if (deploy_agent(false, use_localagent, adb_path) == false) {
+                    return false;
+                }
+            }
+            break;
+    }
+
+    agent_version = get_agent_version();
+    return (agent_version == kRequiredAgentVersion);
+}
+
+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) {
+        return {};
+    }
+
+    std::string utf8;
+    utf8.resize(utf8_length);
+    utf16_to_utf8(input, input_len, &*utf8.begin(), utf8_length + 1);
+    return utf8;
+}
+
+// output is required to point to a valid output string (non-null)
+static bool get_packagename_from_apk(const char* apkPath, std::string* output) {
+    using namespace android;
+
+    ZipFileRO* zipFile = ZipFileRO::open(apkPath);
+    if (zipFile == nullptr) {
+        return false;
+    }
+
+    ZipEntryRO entry = zipFile->findEntryByName("AndroidManifest.xml");
+    if (entry == nullptr) {
+        return false;
+    }
+
+    uint32_t manifest_len = 0;
+    if (!zipFile->getEntryInfo(entry, NULL, &manifest_len, NULL, NULL, NULL, NULL)) {
+        return false;
+    }
+
+    std::vector<char> manifest_data(manifest_len);
+    if (!zipFile->uncompressEntry(entry, manifest_data.data(), manifest_len)) {
+        return false;
+    }
+
+    ResXMLTree tree;
+    status_t setto_status = tree.setTo(manifest_data.data(), manifest_len, true);
+    if (setto_status != NO_ERROR) {
+        return false;
+    }
+
+    ResXMLParser::event_code_t code;
+    while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT &&
+           code != ResXMLParser::END_DOCUMENT) {
+        switch (code) {
+            case ResXMLParser::START_TAG: {
+                size_t element_name_length;
+                const char16_t* element_name = tree.getElementName(&element_name_length);
+                if (element_name == nullptr) {
+                    continue;
+                }
+
+                std::u16string element_name_string(element_name, element_name_length);
+                if (element_name_string == u"manifest") {
+                    for (int i = 0; i < (int)tree.getAttributeCount(); i++) {
+                        size_t attribute_name_length;
+                        const char16_t* attribute_name_text =
+                                tree.getAttributeName(i, &attribute_name_length);
+                        if (attribute_name_text == nullptr) {
+                            continue;
+                        }
+                        std::u16string attribute_name_string(attribute_name_text,
+                                                             attribute_name_length);
+
+                        if (attribute_name_string == u"package") {
+                            size_t attribute_value_length;
+                            const char16_t* attribute_value_text =
+                                    tree.getAttributeStringValue(i, &attribute_value_length);
+                            if (attribute_value_text == nullptr) {
+                                continue;
+                            }
+                            *output = get_string_from_utf16(attribute_value_text,
+                                                            attribute_value_length);
+                            return true;
+                        }
+                    }
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+
+    return false;
+}
+
+int extract_metadata(const char* apkPath, FILE* outputFp) {
+    std::string packageName;
+    if (get_packagename_from_apk(apkPath, &packageName) == false) {
+        return -1;
+    }
+
+    const char* kAgentExtractCommandPattern = "/data/local/tmp/deployagent.sh extract %s";
+    std::string extractCommand =
+            android::base::StringPrintf(kAgentExtractCommandPattern, packageName.c_str());
+
+    std::vector<char> extractErrorBuffer;
+    int statusCode;
+    DeployAgentFileCallback cb(outputFp, &extractErrorBuffer, &statusCode);
+    int ret = send_shell_command(extractCommand.c_str(), false, &cb);
+
+    if (ret == 0) {
+        return cb.getBytesWritten();
+    }
+
+    return ret;
+}
+
+// output is required to point to a valid output string (non-null)
+static bool patch_generator_command(bool use_localagent, const char* adb_path,
+                                    std::string* output) {
+    if (use_localagent) {
+        // This should never happen on a Windows machine
+        const char* kGeneratorCommandPattern = "java -jar %s/framework/deploypatchgenerator.jar";
+        const char* host_out = getenv("ANDROID_HOST_OUT");
+        if (host_out == nullptr) {
+            return false;
+        }
+        *output = android::base::StringPrintf(kGeneratorCommandPattern, host_out, host_out);
+        return true;
+    } else {
+        const char* kGeneratorCommandPattern = R"(java -jar "%s/deploypatchgenerator.jar")";
+        std::string mutable_adb_path = adb_path;
+        const char* adb_dir = dirname(&mutable_adb_path[0]);
+        if (adb_dir == nullptr) {
+            return false;
+        }
+
+        *output = android::base::StringPrintf(kGeneratorCommandPattern, adb_dir, adb_dir);
+        return true;
+    }
+    return false;
+}
+
+int create_patch(const char* apkPath, const char* metadataPath, const char* patchPath,
+                 bool use_localagent, const char* adb_path) {
+    const char* kGeneratePatchCommandPattern = R"(%s "%s" "%s" > "%s")";
+    std::string patch_generator_command_string;
+    if (patch_generator_command(use_localagent, adb_path, &patch_generator_command_string) ==
+        false) {
+        return 1;
+    }
+    std::string generatePatchCommand = android::base::StringPrintf(
+            kGeneratePatchCommandPattern, patch_generator_command_string.c_str(), apkPath,
+            metadataPath, patchPath);
+    return system(generatePatchCommand.c_str());
+}
+
+std::string get_patch_path(const char* apkPath) {
+    std::string packageName;
+    if (get_packagename_from_apk(apkPath, &packageName) == false) {
+        return "";
+    }
+    std::string patchDevicePath =
+            android::base::StringPrintf("%s%s.patch", kDeviceAgentPath, packageName.c_str());
+    return patchDevicePath;
+}
+
+int apply_patch_on_device(const char* apkPath, const char* patchPath, const char* outputPath) {
+    const std::string kAgentApplyCommandPattern =
+            "/data/local/tmp/deployagent.sh apply %s %s -o %s";
+
+    std::string packageName;
+    if (get_packagename_from_apk(apkPath, &packageName) == false) {
+        return -1;
+    }
+    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) {
+        return -1;
+    }
+
+    std::string applyPatchCommand =
+            android::base::StringPrintf(kAgentApplyCommandPattern.c_str(), packageName.c_str(),
+                                        patchDevicePath.c_str(), outputPath);
+    return send_shell_command(applyPatchCommand);
+}
+
+int install_patch(const char* apkPath, const char* patchPath, int argc, const char** argv) {
+    const std::string kAgentApplyCommandPattern =
+            "/data/local/tmp/deployagent.sh apply %s %s -pm %s";
+
+    std::string packageName;
+    if (get_packagename_from_apk(apkPath, &packageName) == false) {
+        return -1;
+    }
+
+    std::vector<const char*> srcs;
+    std::string patchDevicePath =
+            android::base::StringPrintf("%s%s.patch", kDeviceAgentPath, packageName.c_str());
+    srcs.push_back(patchPath);
+    bool push_ok = do_sync_push(srcs, patchDevicePath.c_str(), false);
+
+    if (!push_ok) {
+        return -1;
+    }
+
+    std::vector<unsigned char> applyOutputBuffer;
+    std::vector<unsigned char> applyErrorBuffer;
+    std::string argsString;
+
+    for (int i = 0; i < argc; i++) {
+        argsString.append(argv[i]);
+        argsString.append(" ");
+    }
+
+    std::string applyPatchCommand =
+            android::base::StringPrintf(kAgentApplyCommandPattern.c_str(), packageName.c_str(),
+                                        patchDevicePath.c_str(), argsString.c_str());
+    return send_shell_command(applyPatchCommand);
+}
diff --git a/adb/client/fastdeploy.h b/adb/client/fastdeploy.h
new file mode 100644
index 0000000..d8acd30
--- /dev/null
+++ b/adb/client/fastdeploy.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "adb.h"
+
+typedef enum EFastDeploy_AgentUpdateStrategy {
+    FastDeploy_AgentUpdateAlways,
+    FastDeploy_AgentUpdateNewerTimeStamp,
+    FastDeploy_AgentUpdateDifferentVersion
+} FastDeploy_AgentUpdateStrategy;
+
+static constexpr int kFastDeployMinApi = 24;
+
+int get_device_api_level();
+bool update_agent(FastDeploy_AgentUpdateStrategy agentUpdateStrategy, bool use_localagent,
+                  const char* adb_path);
+int extract_metadata(const char* apkPath, FILE* outputFp);
+int create_patch(const char* apkPath, const char* metadataPath, const char* patchPath,
+                 bool use_localagent, const char* adb_path);
+int apply_patch_on_device(const char* apkPath, const char* patchPath, const char* outputPath);
+int install_patch(const char* apkPath, const char* patchPath, int argc, const char** argv);
+std::string get_patch_path(const char* apkPath);
diff --git a/adb/client/fastdeploycallbacks.cpp b/adb/client/fastdeploycallbacks.cpp
new file mode 100644
index 0000000..6c9a21f
--- /dev/null
+++ b/adb/client/fastdeploycallbacks.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#define TRACE_TAG ADB
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "client/file_sync_client.h"
+#include "commandline.h"
+#include "sysdeps.h"
+
+#include "fastdeploycallbacks.h"
+
+static void appendBuffer(std::vector<char>* buffer, const char* input, int length) {
+    if (buffer != NULL) {
+        buffer->insert(buffer->end(), input, input + length);
+    }
+}
+
+class DeployAgentBufferCallback : public StandardStreamsCallbackInterface {
+  public:
+    DeployAgentBufferCallback(std::vector<char>* outBuffer, std::vector<char>* errBuffer,
+                              int* statusCode);
+
+    virtual void OnStdout(const char* buffer, int length);
+    virtual void OnStderr(const char* buffer, int length);
+    virtual int Done(int status);
+
+  private:
+    std::vector<char>* mpOutBuffer;
+    std::vector<char>* mpErrBuffer;
+    int* mpStatusCode;
+};
+
+int capture_shell_command(const char* command, std::vector<char>* outBuffer,
+                          std::vector<char>* errBuffer) {
+    int statusCode;
+    DeployAgentBufferCallback cb(outBuffer, errBuffer, &statusCode);
+    int ret = send_shell_command(command, false, &cb);
+
+    if (ret == 0) {
+        return statusCode;
+    } else {
+        return ret;
+    }
+}
+
+DeployAgentFileCallback::DeployAgentFileCallback(FILE* outputFile, std::vector<char>* errBuffer,
+                                                 int* statusCode) {
+    mpOutFile = outputFile;
+    mpErrBuffer = errBuffer;
+    mpStatusCode = statusCode;
+    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) {
+    if (mpStatusCode != NULL) {
+        *mpStatusCode = status;
+    }
+    return 0;
+}
+
+int DeployAgentFileCallback::getBytesWritten() {
+    return mBytesWritten;
+}
+
+DeployAgentBufferCallback::DeployAgentBufferCallback(std::vector<char>* outBuffer,
+                                                     std::vector<char>* errBuffer,
+                                                     int* statusCode) {
+    mpOutBuffer = outBuffer;
+    mpErrBuffer = errBuffer;
+    mpStatusCode = statusCode;
+}
+
+void DeployAgentBufferCallback::OnStdout(const char* buffer, int length) {
+    appendBuffer(mpOutBuffer, buffer, length);
+}
+
+void DeployAgentBufferCallback::OnStderr(const char* buffer, int length) {
+    appendBuffer(mpErrBuffer, buffer, length);
+}
+
+int DeployAgentBufferCallback::Done(int status) {
+    if (mpStatusCode != NULL) {
+        *mpStatusCode = status;
+    }
+    return 0;
+}
diff --git a/adb/client/fastdeploycallbacks.h b/adb/client/fastdeploycallbacks.h
new file mode 100644
index 0000000..b428b50
--- /dev/null
+++ b/adb/client/fastdeploycallbacks.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <vector>
+#include "commandline.h"
+
+class DeployAgentFileCallback : public StandardStreamsCallbackInterface {
+  public:
+    DeployAgentFileCallback(FILE* outputFile, std::vector<char>* errBuffer, int* statusCode);
+
+    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* mpStatusCode;
+};
+
+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
new file mode 100644
index 0000000..30f4730
--- /dev/null
+++ b/adb/fastdeploy/Android.bp
@@ -0,0 +1,43 @@
+//
+// 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.
+//
+
+java_library {
+    name: "deployagent",
+    sdk_version: "24",
+    srcs: ["deployagent/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
+    static_libs: ["apkzlib_zip"],
+    proto: {
+        type: "lite",
+    }
+}
+
+cc_prebuilt_binary {
+    name: "deployagent.sh",
+
+    srcs: ["deployagent/deployagent.sh"],
+    required: ["deployagent"],
+    device_supported: true,
+}
+
+java_binary_host {
+    name: "deploypatchgenerator",
+    srcs: ["deploypatchgenerator/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
+    static_libs: ["apkzlib"],
+    manifest: "deploypatchgenerator/manifest.txt",
+    proto: {
+        type: "full",
+    }
+}
diff --git a/adb/fastdeploy/deployagent/deployagent.sh b/adb/fastdeploy/deployagent/deployagent.sh
new file mode 100755
index 0000000..4f17eb7
--- /dev/null
+++ b/adb/fastdeploy/deployagent/deployagent.sh
@@ -0,0 +1,7 @@
+# Script to start "deployagent" on the device, which has a very rudimentary
+# shell.
+#
+base=/data/local/tmp
+export CLASSPATH=$base/deployagent.jar
+exec app_process $base com.android.fastdeploy.DeployAgent "$@"
+
diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
new file mode 100644
index 0000000..cd6f168
--- /dev/null
+++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
@@ -0,0 +1,295 @@
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+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 com.android.fastdeploy.APKMetaData;
+import com.android.fastdeploy.PatchUtils;
+
+public final class DeployAgent {
+    private static final int BUFFER_SIZE = 128 * 1024;
+    private static final int AGENT_VERSION = 0x00000001;
+
+    public static void main(String[] args) {
+        int exitCode = 0;
+        try {
+            if (args.length < 1) {
+                showUsage(0);
+            }
+
+            String commandString = args[0];
+
+            if (commandString.equals("extract")) {
+                if (args.length != 2) {
+                    showUsage(1);
+                }
+
+                String packageName = args[1];
+                extractMetaData(packageName);
+            } 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);
+                        }
+                    }
+                    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);
+                }
+            } else if (commandString.equals("version")) {
+                System.out.printf("0x%08X\n", AGENT_VERSION);
+            } else {
+                showUsage(1);
+            }
+        } catch (Exception e) {
+            System.err.println("Error: " + e);
+            e.printStackTrace();
+            System.exit(2);
+        }
+        System.exit(exitCode);
+    }
+
+    private static void showUsage(int exitCode) {
+        System.err.println(
+            "usage: deployagent <command> [<args>]\n\n" +
+            "commands:\n" +
+            "version                             get the version\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"
+            );
+
+        System.exit(exitCode);
+    }
+
+    private static Process executeCommand(String command) throws IOException {
+        try {
+            Process p;
+            p = Runtime.getRuntime().exec(command);
+            p.waitFor();
+            return p;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    private static File getFileFromPackageName(String packageName) throws IOException {
+        StringBuilder commandBuilder = new StringBuilder();
+        commandBuilder.append("pm list packages -f " + packageName);
+
+        Process p = executeCommand(commandBuilder.toString());
+        BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+        String packagePrefix = "package:";
+        String line = "";
+        while ((line = reader.readLine()) != null) {
+            int packageIndex = line.indexOf(packagePrefix);
+            int equalsIndex = line.indexOf("=" + packageName);
+            return new File(line.substring(packageIndex + packagePrefix.length(), equalsIndex));
+        }
+
+        return null;
+    }
+
+    private static void extractMetaData(String packageName) throws IOException {
+        File apkFile = getFileFromPackageName(packageName);
+        APKMetaData apkMetaData = PatchUtils.getAPKMetaData(apkFile);
+        apkMetaData.writeDelimitedTo(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++) {
+            commandBuilder.append(args[i] + " ");
+        }
+
+        Process p = executeCommand(commandBuilder.toString());
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+        String line = "";
+        String successLineStart = "Success: created install session [";
+        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 -1;
+    }
+
+    private static int commitInstallSession(int sessionId) throws IOException {
+        StringBuilder commandBuilder = new StringBuilder();
+        commandBuilder.append(String.format("pm install-commit %d -- - ", sessionId));
+        Process p = executeCommand(commandBuilder.toString());
+        return p.exitValue();
+    }
+
+    private static int applyPatch(String packageName, 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);
+
+        if (writeExitCode == 0) {
+            return commitInstallSession(sessionId);
+        } else {
+            return -1;
+        }
+    }
+
+    private static long writePatchToStream(RandomAccessFile oldData, InputStream patchData,
+        OutputStream outputStream) throws IOException, PatchFormatException {
+        long newSize = readPatchHeader(patchData);
+        long bytesWritten = writePatchedDataToStream(oldData, newSize, patchData, outputStream);
+        outputStream.flush();
+        if (bytesWritten != newSize) {
+            throw new PatchFormatException(String.format(
+                "output size mismatch (expected %ld but wrote %ld)", newSize, bytesWritten));
+        }
+        return bytesWritten;
+    }
+
+    private static long readPatchHeader(InputStream patchData)
+        throws IOException, PatchFormatException {
+        byte[] signatureBuffer = new byte[PatchUtils.SIGNATURE.length()];
+        try {
+            PatchUtils.readFully(patchData, signatureBuffer, 0, signatureBuffer.length);
+        } catch (IOException e) {
+            throw new PatchFormatException("truncated signature");
+        }
+
+        String signature = new String(signatureBuffer, 0, signatureBuffer.length, "US-ASCII");
+        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");
+        }
+
+        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 {
+        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 oldDataOffset = PatchUtils.readFormattedLong(patchData);
+            long oldDataLen = PatchUtils.readFormattedLong(patchData);
+            oldData.seek(oldDataOffset);
+            if (oldDataLen > 0) {
+                PatchUtils.pipe(oldData, outputStream, buffer, (int) oldDataLen);
+            }
+
+            newDataBytesWritten += copyLen + oldDataLen;
+        }
+
+        return newDataBytesWritten;
+    }
+
+    private static int writePatchedDataToSession(RandomAccessFile oldData, 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();
+            p = Runtime.getRuntime().exec(command);
+
+            OutputStream sessionOutputStream = p.getOutputStream();
+            long bytesWritten = writePatchedDataToStream(oldData, newSize, patchData, sessionOutputStream);
+            sessionOutputStream.flush();
+            p.waitFor();
+            if (bytesWritten != newSize) {
+                throw new PatchFormatException(
+                        String.format("output size mismatch (expected %d but wrote %)", newSize, bytesWritten));
+            }
+            return p.exitValue();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        return -1;
+    }
+}
diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java
new file mode 100644
index 0000000..f0655f3
--- /dev/null
+++ b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+class PatchFormatException extends Exception {
+    /**
+     * Constructs a new exception with the specified message.
+     * @param message the message
+     */
+    public PatchFormatException(String message) { super(message); }
+
+    /**
+     * Constructs a new exception with the specified message and cause.
+     * @param message the message
+     * @param cause the cause of the error
+     */
+    public PatchFormatException(String message, Throwable cause) {
+        super(message);
+        initCause(cause);
+    }
+}
diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
new file mode 100644
index 0000000..f0f00e1
--- /dev/null
+++ b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
@@ -0,0 +1,186 @@
+/*
+ * 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 = "HAMADI/IHD";
+
+    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/manifest.txt b/adb/fastdeploy/deploypatchgenerator/manifest.txt
new file mode 100644
index 0000000..5c00505
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.fastdeploy.DeployPatchGenerator
diff --git a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java b/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
new file mode 100644
index 0000000..5577364
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
@@ -0,0 +1,207 @@
+/*
+ * 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.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.StringBuilder;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ArrayList;
+
+import java.nio.charset.StandardCharsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.AbstractMap.SimpleEntry;
+
+import com.android.fastdeploy.APKMetaData;
+import com.android.fastdeploy.APKEntry;
+
+public final class DeployPatchGenerator {
+    private static final int BUFFER_SIZE = 128 * 1024;
+
+    public static void main(String[] args) {
+        try {
+            if (args.length < 2) {
+                showUsage(0);
+            }
+
+            boolean verbose = false;
+            if (args.length > 2) {
+                String verboseFlag = args[2];
+                if (verboseFlag.compareTo("--verbose") == 0) {
+                    verbose = true;
+                }
+            }
+
+            StringBuilder sb = null;
+            String apkPath = args[0];
+            String deviceMetadataPath = args[1];
+            File hostFile = new File(apkPath);
+
+            List<APKEntry> deviceZipEntries = getMetadataFromFile(deviceMetadataPath);
+            if (verbose) {
+                sb = new StringBuilder();
+                for (APKEntry entry : deviceZipEntries) {
+                    APKEntryToString(entry, sb);
+                }
+                System.err.println("Device Entries (" + deviceZipEntries.size() + ")");
+                System.err.println(sb.toString());
+            }
+
+            List<APKEntry> hostFileEntries = PatchUtils.getAPKMetaData(hostFile).getEntriesList();
+            if (verbose) {
+                sb = new StringBuilder();
+                for (APKEntry entry : hostFileEntries) {
+                    APKEntryToString(entry, sb);
+                }
+                System.err.println("Host Entries (" + hostFileEntries.size() + ")");
+                System.err.println(sb.toString());
+            }
+
+            List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet =
+                getIdenticalContents(deviceZipEntries, hostFileEntries);
+            reportIdenticalContents(identicalContentsEntrySet, hostFile);
+
+            if (verbose) {
+                sb = new StringBuilder();
+                for (SimpleEntry<APKEntry, APKEntry> identicalEntry : identicalContentsEntrySet) {
+                    APKEntry entry = identicalEntry.getValue();
+                    APKEntryToString(entry, sb);
+                }
+                System.err.println("Identical Entries (" + identicalContentsEntrySet.size() + ")");
+                System.err.println(sb.toString());
+            }
+
+            createPatch(identicalContentsEntrySet, hostFile, System.out);
+        } catch (Exception e) {
+            System.err.println("Error: " + e);
+            e.printStackTrace();
+            System.exit(2);
+        }
+        System.exit(0);
+    }
+
+    private static void showUsage(int exitCode) {
+        System.err.println("usage: deploypatchgenerator <apkpath> <deviceapkmetadata> [--verbose]");
+        System.err.println("");
+        System.exit(exitCode);
+    }
+
+    private static void APKEntryToString(APKEntry entry, StringBuilder outputString) {
+        outputString.append(String.format("Filename: %s\n", entry.getFileName()));
+        outputString.append(String.format("CRC32: 0x%08X\n", entry.getCrc32()));
+        outputString.append(String.format("Data Offset: %d\n", entry.getDataOffset()));
+        outputString.append(String.format("Compressed Size: %d\n", entry.getCompressedSize()));
+        outputString.append(String.format("Uncompressed Size: %d\n", entry.getUncompressedSize()));
+    }
+
+    private static List<APKEntry> getMetadataFromFile(String deviceMetadataPath) throws IOException {
+        InputStream is = new FileInputStream(new File(deviceMetadataPath));
+        APKMetaData apkMetaData = APKMetaData.parseDelimitedFrom(is);
+        return apkMetaData.getEntriesList();
+    }
+
+    private static List<SimpleEntry<APKEntry, APKEntry>> getIdenticalContents(
+        List<APKEntry> deviceZipEntries, List<APKEntry> hostZipEntries) throws IOException {
+        List<SimpleEntry<APKEntry, APKEntry>> identicalContents =
+            new ArrayList<SimpleEntry<APKEntry, APKEntry>>();
+
+        for (APKEntry deviceZipEntry : deviceZipEntries) {
+            for (APKEntry hostZipEntry : hostZipEntries) {
+                if (deviceZipEntry.getCrc32() == hostZipEntry.getCrc32()) {
+                    identicalContents.add(new SimpleEntry(deviceZipEntry, hostZipEntry));
+                }
+            }
+        }
+
+        Collections.sort(identicalContents, new Comparator<SimpleEntry<APKEntry, APKEntry>>() {
+            @Override
+            public int compare(
+                SimpleEntry<APKEntry, APKEntry> p1, SimpleEntry<APKEntry, APKEntry> p2) {
+                return Long.compare(p1.getValue().getDataOffset(), p2.getValue().getDataOffset());
+            }
+        });
+
+        return identicalContents;
+    }
+
+    private static void reportIdenticalContents(
+        List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet, File hostFile)
+        throws IOException {
+        long totalEqualBytes = 0;
+        int totalEqualFiles = 0;
+        for (SimpleEntry<APKEntry, APKEntry> entries : identicalContentsEntrySet) {
+            APKEntry hostAPKEntry = entries.getValue();
+            totalEqualBytes += hostAPKEntry.getCompressedSize();
+            totalEqualFiles++;
+        }
+
+        float savingPercent = (float) (totalEqualBytes * 100) / hostFile.length();
+
+        System.err.println("Detected " + totalEqualFiles + " equal APK entries");
+        System.err.println(totalEqualBytes + " bytes are equal out of " + hostFile.length() + " ("
+            + savingPercent + "%)");
+    }
+
+    static void createPatch(List<SimpleEntry<APKEntry, APKEntry>> zipEntrySimpleEntrys,
+        File hostFile, OutputStream patchStream) throws IOException, PatchFormatException {
+        FileInputStream hostFileInputStream = new FileInputStream(hostFile);
+
+        patchStream.write(PatchUtils.SIGNATURE.getBytes(StandardCharsets.US_ASCII));
+        PatchUtils.writeFormattedLong(hostFile.length(), patchStream);
+
+        byte[] buffer = new byte[BUFFER_SIZE];
+        long totalBytesWritten = 0;
+        Iterator<SimpleEntry<APKEntry, APKEntry>> entrySimpleEntryIterator =
+            zipEntrySimpleEntrys.iterator();
+        while (entrySimpleEntryIterator.hasNext()) {
+            SimpleEntry<APKEntry, APKEntry> entrySimpleEntry = entrySimpleEntryIterator.next();
+            APKEntry deviceAPKEntry = entrySimpleEntry.getKey();
+            APKEntry hostAPKEntry = entrySimpleEntry.getValue();
+
+            long newDataLen = hostAPKEntry.getDataOffset() - totalBytesWritten;
+            long oldDataOffset = deviceAPKEntry.getDataOffset();
+            long oldDataLen = deviceAPKEntry.getCompressedSize();
+
+            PatchUtils.writeFormattedLong(newDataLen, patchStream);
+            PatchUtils.pipe(hostFileInputStream, patchStream, buffer, newDataLen);
+            PatchUtils.writeFormattedLong(oldDataOffset, patchStream);
+            PatchUtils.writeFormattedLong(oldDataLen, patchStream);
+
+            long skip = hostFileInputStream.skip(oldDataLen);
+            if (skip != oldDataLen) {
+                throw new PatchFormatException("skip error: attempted to skip " + oldDataLen
+                    + " bytes but return code was " + skip);
+            }
+            totalBytesWritten += oldDataLen + newDataLen;
+        }
+        long remainderLen = hostFile.length() - totalBytesWritten;
+        PatchUtils.writeFormattedLong(remainderLen, patchStream);
+        PatchUtils.pipe(hostFileInputStream, patchStream, buffer, remainderLen);
+        PatchUtils.writeFormattedLong(0, patchStream);
+        PatchUtils.writeFormattedLong(0, patchStream);
+        patchStream.flush();
+    }
+}
diff --git a/adb/fastdeploy/proto/ApkEntry.proto b/adb/fastdeploy/proto/ApkEntry.proto
new file mode 100644
index 0000000..9460d15
--- /dev/null
+++ b/adb/fastdeploy/proto/ApkEntry.proto
@@ -0,0 +1,18 @@
+syntax = "proto2";
+
+package com.android.fastdeploy;
+
+option java_package = "com.android.fastdeploy";
+option java_multiple_files = true;
+
+message APKEntry {
+    required int64 crc32 = 1;
+    required string fileName = 2;
+    required int64 dataOffset = 3;
+    required int64 compressedSize = 4;
+    required int64 uncompressedSize = 5;
+}
+
+message APKMetaData {
+    repeated APKEntry entries = 1;
+}