Merge "Integrate adb with fastdeploy"
This commit is contained in:
commit
15eb065bbb
18 changed files with 1991 additions and 309 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
578
adb/client/adb_install.cpp
Normal 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
29
adb/client/adb_install.h
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
365
adb/client/fastdeploy.cpp
Normal 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
37
adb/client/fastdeploy.h
Normal 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);
|
||||
118
adb/client/fastdeploycallbacks.cpp
Normal file
118
adb/client/fastdeploycallbacks.cpp
Normal 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;
|
||||
}
|
||||
40
adb/client/fastdeploycallbacks.h
Normal file
40
adb/client/fastdeploycallbacks.h
Normal 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
43
adb/fastdeploy/Android.bp
Normal 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",
|
||||
}
|
||||
}
|
||||
7
adb/fastdeploy/deployagent/deployagent.sh
Executable file
7
adb/fastdeploy/deployagent/deployagent.sh
Executable 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 "$@"
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
adb/fastdeploy/deploypatchgenerator/manifest.txt
Normal file
1
adb/fastdeploy/deploypatchgenerator/manifest.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Main-Class: com.android.fastdeploy.DeployPatchGenerator
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
18
adb/fastdeploy/proto/ApkEntry.proto
Normal file
18
adb/fastdeploy/proto/ApkEntry.proto
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue