Merge "Integrate adb with fastdeploy"

This commit is contained in:
Treehugger Robot 2018-08-21 06:28:49 +00:00 committed by Gerrit Code Review
commit 15eb065bbb
18 changed files with 1991 additions and 309 deletions

View file

@ -138,6 +138,8 @@ cc_library_host_static {
"client/usb_libusb.cpp",
"client/usb_dispatch.cpp",
"client/transport_mdns.cpp",
"client/fastdeploy.cpp",
"client/fastdeploycallbacks.cpp",
],
target: {
@ -170,6 +172,12 @@ cc_library_host_static {
"libdiagnose_usb",
"libmdnssd",
"libusb",
"libandroidfw",
"libziparchive",
"libz",
"libutils",
"liblog",
"libcutils",
],
}
@ -237,6 +245,7 @@ cc_binary_host {
"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 @@ cc_binary_host {
"liblog",
"libmdnssd",
"libusb",
"libandroidfw",
"libziparchive",
"libz",
"libutils",
"liblog",
"libcutils",
],
stl: "libc++_static",

View file

@ -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 @@ std::string format_host_command(const char* _Nonnull command);
// Get the feature set of the current preferred transport.
bool adb_get_feature_set(FeatureSet* _Nonnull feature_set, std::string* _Nonnull error);
#endif

578
adb/client/adb_install.cpp Normal file
View file

@ -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
}

29
adb/client/adb_install.h Normal file
View file

@ -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

View file

@ -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 @@ static void help() {
" -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 @@ int read_and_dump(int fd, bool use_shell_protocol = false,
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 @@ static void stdinout_raw_epilogue(int inFd, int outFd, int old_stdin_mode, int o
#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 @@ static bool _is_valid_ack_reply_fd(const int ack_reply_fd) {
#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) {
bool no_daemon = false;
bool is_daemon = false;
@ -1706,9 +1688,6 @@ int adb_commandline(int argc, const char** argv) {
}
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 @@ int adb_commandline(int argc, const char** argv) {
}
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 @@ int adb_commandline(int argc, const char** argv) {
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;
}

View file

@ -96,6 +96,8 @@ extern DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK;
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.

365
adb/client/fastdeploy.cpp Normal file
View file

@ -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);
}

37
adb/client/fastdeploy.h Normal file
View file

@ -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);

View file

@ -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;
}

View file

@ -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);

43
adb/fastdeploy/Android.bp Normal file
View file

@ -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",
}
}

View file

@ -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 "$@"

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1 @@
Main-Class: com.android.fastdeploy.DeployPatchGenerator

View file

@ -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();
}
}

View file

@ -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;
}