Merge "remove legacy adb data loader" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-02-29 06:11:24 +00:00
committed by Android (Google) Code Review
9 changed files with 0 additions and 1000 deletions

View File

@@ -1,22 +0,0 @@
// Copyright 2019, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
android_app {
name: "NativeAdbDataLoaderService",
srcs: ["src/**/*.java"],
jni_libs: [ "libnativeadbdataloaderservice_jni"],
privileged: true,
certificate: "platform",
platform_apis: true,
}

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2019, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
coreApp="true"
package="com.android.incremental.nativeadb"
android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.INTERNET" />
<application android:label="@string/app_name"
android:directBootAware="true">
<service android:enabled="true"
android:name="com.android.incremental.nativeadb.NativeAdbDataLoaderService"
android:label="@string/app_name"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.LOAD_DATA" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -1,37 +0,0 @@
// Copyright 2019, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
cc_library_shared {
name: "libnativeadbdataloaderservice_jni",
cpp_std: "c++2a",
cflags: [
"-Wall",
"-Werror",
"-Wunused",
"-Wunreachable-code",
"-Wno-unused-parameter",
],
srcs: ["com_android_incremental_nativeadb_DataLoaderService.cpp"],
shared_libs: [
"libbase",
"libcutils",
"libincfs",
"libdataloader",
"liblog",
"libnativehelper",
"libutils",
],
}

View File

@@ -1,525 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG ATRACE_TAG_ADB
#define LOG_TAG "NativeAdbDataLoaderService"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
#include <android-base/unique_fd.h>
#include <cutils/trace.h>
#include <fcntl.h>
#include <sys/eventfd.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utils/Log.h>
#include <charconv>
#include <span>
#include <string>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include "dataloader.h"
#ifndef _WIN32
#include <endian.h>
#include <sys/stat.h>
#include <unistd.h>
#else
#define be32toh(x) _byteswap_ulong(x)
#define be16toh(x) _byteswap_ushort(x)
#endif
namespace {
using android::base::unique_fd;
using namespace std::literals;
using BlockSize = int16_t;
using FileId = int16_t;
using BlockIdx = int32_t;
using NumBlocks = int32_t;
using CompressionType = int16_t;
using RequestType = int16_t;
static constexpr int COMMAND_SIZE = 2 + 2 + 4; // bytes
static constexpr int HEADER_SIZE = 2 + 2 + 4 + 2; // bytes
static constexpr std::string_view OKAY = "OKAY"sv;
static constexpr auto PollTimeoutMs = 5000;
static constexpr auto ReadLogBufferSize = 128 * 1024 * 1024;
static constexpr auto ReadLogMaxEntrySize = 128;
struct BlockHeader {
FileId fileId = -1;
CompressionType compressionType = -1;
BlockIdx blockIdx = -1;
BlockSize blockSize = -1;
} __attribute__((packed));
static_assert(sizeof(BlockHeader) == HEADER_SIZE);
static constexpr RequestType EXIT = 0;
static constexpr RequestType BLOCK_MISSING = 1;
static constexpr RequestType PREFETCH = 2;
struct RequestCommand {
RequestType requestType;
FileId fileId;
BlockIdx blockIdx;
} __attribute__((packed));
static_assert(COMMAND_SIZE == sizeof(RequestCommand));
static bool sendRequest(int fd, RequestType requestType, FileId fileId = -1,
BlockIdx blockIdx = -1) {
const RequestCommand command{.requestType = static_cast<int16_t>(be16toh(requestType)),
.fileId = static_cast<int16_t>(be16toh(fileId)),
.blockIdx = static_cast<int32_t>(be32toh(blockIdx))};
return android::base::WriteFully(fd, &command, sizeof(command));
}
static int waitForDataOrSignal(int fd, int event_fd) {
struct pollfd pfds[2] = {{fd, POLLIN, 0}, {event_fd, POLLIN, 0}};
// Wait indefinitely until either data is ready or stop signal is received
int res = poll(pfds, 2, PollTimeoutMs);
if (res <= 0) {
return res;
}
// First check if there is a stop signal
if (pfds[1].revents == POLLIN) {
return event_fd;
}
// Otherwise check if incoming data is ready
if (pfds[0].revents == POLLIN) {
return fd;
}
return -1;
}
static bool readChunk(int fd, std::vector<uint8_t>& data) {
int32_t size;
if (!android::base::ReadFully(fd, &size, sizeof(size))) {
return false;
}
size = int32_t(be32toh(size));
if (size <= 0) {
return false;
}
data.resize(size);
return android::base::ReadFully(fd, data.data(), data.size());
}
static BlockHeader readHeader(std::span<uint8_t>& data) {
BlockHeader header;
if (data.size() < sizeof(header)) {
return header;
}
header.fileId = static_cast<FileId>(be16toh(*reinterpret_cast<uint16_t*>(&data[0])));
header.compressionType =
static_cast<CompressionType>(be16toh(*reinterpret_cast<uint16_t*>(&data[2])));
header.blockIdx = static_cast<BlockIdx>(be32toh(*reinterpret_cast<uint32_t*>(&data[4])));
header.blockSize = static_cast<BlockSize>(be16toh(*reinterpret_cast<uint16_t*>(&data[8])));
data = data.subspan(sizeof(header));
return header;
}
static std::string extractPackageName(const std::string& staticArgs) {
static constexpr auto kPrefix = "package="sv;
static constexpr auto kSuffix = "&"sv;
const auto startPos = staticArgs.find(kPrefix);
if (startPos == staticArgs.npos || startPos + kPrefix.size() >= staticArgs.size()) {
return {};
}
const auto endPos = staticArgs.find(kSuffix, startPos + kPrefix.size());
return staticArgs.substr(startPos + kPrefix.size(),
endPos == staticArgs.npos ? staticArgs.npos
: (endPos - (startPos + kPrefix.size())));
}
class AdbDataLoader : public android::dataloader::DataLoader {
private:
// Lifecycle.
bool onCreate(const android::dataloader::DataLoaderParams& params,
android::dataloader::FilesystemConnectorPtr ifs,
android::dataloader::StatusListenerPtr statusListener,
android::dataloader::ServiceConnectorPtr,
android::dataloader::ServiceParamsPtr) final {
CHECK(ifs) << "ifs can't be null";
CHECK(statusListener) << "statusListener can't be null";
ALOGE("[AdbDataLoader] onCreate: %d/%s/%s/%s/%d", params.type(),
params.packageName().c_str(), params.className().c_str(), params.arguments().c_str(),
(int)params.dynamicArgs().size());
if (params.dynamicArgs().empty()) {
ALOGE("[AdbDataLoader] Invalid DataLoaderParams. Need in/out FDs.");
return false;
}
for (auto const& namedFd : params.dynamicArgs()) {
if (namedFd.name == "inFd") {
mInFd.reset(dup(namedFd.fd));
}
if (namedFd.name == "outFd") {
mOutFd.reset(dup(namedFd.fd));
}
}
if (mInFd < 0 || mOutFd < 0) {
ALOGE("[AdbDataLoader] Failed to dup FDs.");
return false;
}
mEventFd.reset(eventfd(0, EFD_CLOEXEC));
if (mEventFd < 0) {
ALOGE("[AdbDataLoader] Failed to create eventfd.");
return false;
}
std::string logFile;
if (const auto packageName = extractPackageName(params.arguments()); !packageName.empty()) {
logFile = android::base::GetProperty("adb.readlog." + packageName, "");
}
if (logFile.empty()) {
logFile = android::base::GetProperty("adb.readlog", "");
}
if (!logFile.empty()) {
int flags = O_WRONLY | O_CREAT | O_CLOEXEC;
mReadLogFd.reset(TEMP_FAILURE_RETRY(open(logFile.c_str(), flags, 0666)));
}
mIfs = ifs;
mStatusListener = statusListener;
ALOGE("[AdbDataLoader] Successfully created data loader.");
return true;
}
bool onStart() final {
char okay_buf[OKAY.size()];
if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) {
ALOGE("[AdbDataLoader] Failed to receive OKAY. Abort.");
return false;
}
if (std::string_view(okay_buf, OKAY.size()) != OKAY) {
ALOGE("[AdbDataLoader] Received '%.*s', expecting '%.*s'", (int)OKAY.size(), okay_buf,
(int)OKAY.size(), OKAY.data());
return false;
}
mReceiverThread = std::thread([this]() { receiver(); });
ALOGI("[AdbDataLoader] started loading...");
return true;
}
void onStop() final {
mStopReceiving = true;
eventfd_write(mEventFd, 1);
if (mReceiverThread.joinable()) {
mReceiverThread.join();
}
}
void onDestroy() final {
ALOGE("[AdbDataLoader] Sending EXIT to server.");
sendRequest(mOutFd, EXIT);
// Make sure the receiver thread was stopped
CHECK(!mReceiverThread.joinable());
mInFd.reset();
mOutFd.reset();
mNodeToMetaMap.clear();
mIdToNodeMap.clear();
flushReadLog();
mReadLogFd.reset();
}
// Installation callback
bool onPrepareImage(const android::dataloader::DataLoaderInstallationFiles& addedFiles) final {
return true;
}
// IFS callbacks.
void onPendingReads(const android::dataloader::PendingReads& pendingReads) final {
std::lock_guard lock{mMapsMutex};
CHECK(mIfs);
for (auto&& pendingRead : pendingReads) {
const android::dataloader::FileId id = pendingRead.id;
const auto blockIdx = static_cast<BlockIdx>(pendingRead.block);
/*
ALOGI("[AdbDataLoader] Missing: %d", (int) blockIdx);
*/
auto fileIdOr = getFileId(id);
if (!fileIdOr) {
ALOGE("[AdbDataLoader] Failed to handle event for fileid=%s. "
"Ignore.",
android::incfs::toString(id).c_str());
continue;
}
const FileId fileId = *fileIdOr;
if (mRequestedFiles.insert(fileId).second) {
if (!sendRequest(mOutFd, PREFETCH, fileId, blockIdx)) {
ALOGE("[AdbDataLoader] Failed to request prefetch for "
"fileid=%s. Ignore.",
android::incfs::toString(id).c_str());
mRequestedFiles.erase(fileId);
mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
}
}
sendRequest(mOutFd, BLOCK_MISSING, fileId, blockIdx);
}
}
struct TracedRead {
uint64_t timestampUs;
android::dataloader::FileId fileId;
uint32_t firstBlockIdx;
uint32_t count;
};
void onPageReads(const android::dataloader::PageReads& pageReads) final {
auto trace = atrace_is_tag_enabled(ATRACE_TAG);
auto log = mReadLogFd != -1;
if (CC_LIKELY(!(trace || log))) {
return;
}
TracedRead last = {};
std::lock_guard lock{mMapsMutex};
for (auto&& read : pageReads) {
if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) {
traceOrLogRead(last, trace, log);
last = {read.bootClockTsUs, read.id, (uint32_t)read.block, 1};
} else {
++last.count;
}
}
traceOrLogRead(last, trace, log);
}
void onFileCreated(android::dataloader::FileId fileid,
const android::dataloader::RawMetadata& metadata) {}
private:
void receiver() {
std::vector<uint8_t> data;
std::vector<IncFsDataBlock> instructions;
std::unordered_map<android::dataloader::FileId, unique_fd> writeFds;
while (!mStopReceiving) {
const int res = waitForDataOrSignal(mInFd, mEventFd);
if (res == 0) {
flushReadLog();
continue;
}
if (res < 0) {
ALOGE("[AdbDataLoader] failed to poll. Abort.");
mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
break;
}
if (res == mEventFd) {
ALOGE("[AdbDataLoader] received stop signal. Exit.");
break;
}
if (!readChunk(mInFd, data)) {
ALOGE("[AdbDataLoader] failed to read a message. Abort.");
mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
break;
}
auto remainingData = std::span(data);
while (!remainingData.empty()) {
auto header = readHeader(remainingData);
if (header.fileId == -1 && header.compressionType == 0 && header.blockIdx == 0 &&
header.blockSize == 0) {
ALOGI("[AdbDataLoader] stop signal received. Sending "
"exit command (remaining bytes: %d).",
int(remainingData.size()));
sendRequest(mOutFd, EXIT);
mStopReceiving = true;
break;
}
if (header.fileId < 0 || header.blockSize <= 0 || header.compressionType < 0 ||
header.blockIdx < 0) {
ALOGE("[AdbDataLoader] invalid header received. Abort.");
mStopReceiving = true;
break;
}
const android::dataloader::FileId id = mIdToNodeMap[header.fileId];
if (!android::incfs::isValidFileId(id)) {
ALOGE("Unknown data destination for file ID %d. "
"Ignore.",
header.fileId);
continue;
}
auto& writeFd = writeFds[id];
if (writeFd < 0) {
writeFd = this->mIfs->openWrite(id);
if (writeFd < 0) {
ALOGE("Failed to open file %d for writing (%d). Aboring.", header.fileId,
-writeFd);
break;
}
}
const auto inst = IncFsDataBlock{
.fileFd = writeFd,
.pageIndex = static_cast<IncFsBlockIndex>(header.blockIdx),
.compression = static_cast<IncFsCompressionKind>(header.compressionType),
.kind = INCFS_BLOCK_KIND_DATA,
.dataSize = static_cast<uint16_t>(header.blockSize),
.data = (const char*)remainingData.data(),
};
instructions.push_back(inst);
remainingData = remainingData.subspan(header.blockSize);
}
writeInstructions(instructions);
}
writeInstructions(instructions);
flushReadLog();
}
void writeInstructions(std::vector<IncFsDataBlock>& instructions) {
auto res = this->mIfs->writeBlocks(instructions);
if (res != instructions.size()) {
ALOGE("[AdbDataLoader] failed to write data to Incfs (res=%d when "
"expecting %d)",
res, int(instructions.size()));
}
instructions.clear();
}
struct MetaPair {
android::dataloader::RawMetadata meta;
FileId fileId;
};
MetaPair* updateMapsForFile(android::dataloader::FileId id) {
android::dataloader::RawMetadata meta = mIfs->getRawMetadata(id);
FileId fileId;
auto res = std::from_chars(meta.data(), meta.data() + meta.size(), fileId);
if (res.ec != std::errc{} || fileId < 0) {
ALOGE("[AdbDataLoader] Invalid metadata for fileid=%s (%s)",
android::incfs::toString(id).c_str(), meta.data());
return nullptr;
}
mIdToNodeMap[fileId] = id;
auto& metaPair = mNodeToMetaMap[id];
metaPair.meta = std::move(meta);
metaPair.fileId = fileId;
return &metaPair;
}
android::dataloader::RawMetadata* getMeta(android::dataloader::FileId id) {
auto it = mNodeToMetaMap.find(id);
if (it != mNodeToMetaMap.end()) {
return &it->second.meta;
}
auto metaPair = updateMapsForFile(id);
if (!metaPair) {
return nullptr;
}
return &metaPair->meta;
}
FileId* getFileId(android::dataloader::FileId id) {
auto it = mNodeToMetaMap.find(id);
if (it != mNodeToMetaMap.end()) {
return &it->second.fileId;
}
auto* metaPair = updateMapsForFile(id);
if (!metaPair) {
return nullptr;
}
return &metaPair->fileId;
}
void traceOrLogRead(const TracedRead& read, bool trace, bool log) {
if (!read.count) {
return;
}
if (trace) {
auto* meta = getMeta(read.fileId);
auto str = android::base::StringPrintf("page_read: index=%lld count=%lld meta=%.*s",
static_cast<long long>(read.firstBlockIdx),
static_cast<long long>(read.count),
meta ? int(meta->size()) : 0,
meta ? meta->data() : "");
ATRACE_BEGIN(str.c_str());
ATRACE_END();
}
if (log) {
mReadLog.reserve(ReadLogBufferSize);
auto fileId = getFileId(read.fileId);
android::base::StringAppendF(&mReadLog, "%lld:%lld:%lld:%lld\n",
static_cast<long long>(read.timestampUs),
static_cast<long long>(fileId ? *fileId : -1),
static_cast<long long>(read.firstBlockIdx),
static_cast<long long>(read.count));
if (mReadLog.size() >= mReadLog.capacity() - ReadLogMaxEntrySize) {
flushReadLog();
}
}
}
void flushReadLog() {
if (mReadLog.empty() || mReadLogFd == -1) {
return;
}
android::base::WriteStringToFd(mReadLog, mReadLogFd);
mReadLog.clear();
}
private:
android::dataloader::FilesystemConnectorPtr mIfs = nullptr;
android::dataloader::StatusListenerPtr mStatusListener = nullptr;
android::base::unique_fd mInFd;
android::base::unique_fd mOutFd;
android::base::unique_fd mEventFd;
android::base::unique_fd mReadLogFd;
std::string mReadLog;
std::thread mReceiverThread;
std::mutex mMapsMutex;
std::unordered_map<android::dataloader::FileId, MetaPair> mNodeToMetaMap GUARDED_BY(mMapsMutex);
std::unordered_map<FileId, android::dataloader::FileId> mIdToNodeMap GUARDED_BY(mMapsMutex);
/** Tracks which files have been requested */
std::unordered_set<FileId> mRequestedFiles;
std::atomic<bool> mStopReceiving = false;
};
} // namespace
int JNI_OnLoad(JavaVM* jvm, void* /* reserved */) {
android::dataloader::DataLoader::initialize(
[](auto, auto) { return std::make_unique<AdbDataLoader>(); });
return JNI_VERSION_1_6;
}

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Name of the Data Loader Service. [CHAR LIMIT=40] -->
<string name="app_name">Native Adb Data Loader Service</string>
</resources>

View File

@@ -1,34 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.incremental.nativeadb;
import android.annotation.NonNull;
import android.content.pm.DataLoaderParams;
import android.service.dataloader.DataLoaderService;
/** This code is used for testing only. */
public class NativeAdbDataLoaderService extends DataLoaderService {
public static final String TAG = "NativeAdbDataLoaderService";
static {
System.loadLibrary("nativeadbdataloaderservice_jni");
}
@Override
public DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) {
return null;
}
}

View File

@@ -42281,7 +42281,6 @@ Lcom/android/server/incident/RequestQueue$1;
Lcom/android/server/incident/RequestQueue$Rec;
Lcom/android/server/incident/RequestQueue;
Lcom/android/server/incremental/IncrementalManagerService;
Lcom/android/server/incremental/IncrementalManagerShellCommand;
Lcom/android/server/infra/-$$Lambda$AbstractMasterSystemService$1$TLhe3_2yHs5UB69Y7lf2s7OxJCo;
Lcom/android/server/infra/-$$Lambda$AbstractMasterSystemService$_fKw-VUP0pSfcMMlgRqoT4OPhxw;
Lcom/android/server/infra/-$$Lambda$AbstractMasterSystemService$su3lJpEVIbL-C7doP4eboTpqjxU;

View File

@@ -26,9 +26,7 @@ import android.content.pm.IDataLoader;
import android.content.pm.IDataLoaderStatusListener;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.incremental.IIncrementalManager;
import android.util.Slog;
@@ -147,14 +145,6 @@ public class IncrementalManagerService extends IIncrementalManager.Stub {
// TODO(b/136132412): implement this
}
@Override
public void onShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
FileDescriptor err, @NonNull String[] args, ShellCallback callback,
@NonNull ResultReceiver resultReceiver) {
(new IncrementalManagerShellCommand(mContext)).exec(
this, in, out, err, args, callback, resultReceiver);
}
private static native long nativeStartService();
private static native void nativeSystemReady(long nativeInstance);

View File

@@ -1,313 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.incremental;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.ShellCommand;
import android.util.Slog;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Defines actions to handle adb commands like "adb abb incremental ...".
*/
public final class IncrementalManagerShellCommand extends ShellCommand {
private static final String TAG = "IncrementalShellCommand";
// Assuming the adb data loader is always installed on the device
private static final String LOADER_PACKAGE_NAME = "com.android.incremental.nativeadb";
private static final String LOADER_CLASS_NAME =
LOADER_PACKAGE_NAME + ".NativeAdbDataLoaderService";
private final @NonNull Context mContext;
private static final int ERROR_INVALID_ARGUMENTS = -1;
private static final int ERROR_DATA_LOADER_INIT = -2;
private static final int ERROR_COMMAND_EXECUTION = -3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ERROR_INVALID_ARGUMENTS, ERROR_DATA_LOADER_INIT, ERROR_COMMAND_EXECUTION})
public @interface IncrementalShellCommandErrorCode {
}
IncrementalManagerShellCommand(@NonNull Context context) {
mContext = context;
}
@Override
public int onCommand(@Nullable String cmd) {
if (cmd == null) {
return handleDefaultCommands(null);
}
switch (cmd) {
case "install-start":
return runInstallStart();
case "install-finish":
return runInstallFinish();
default:
return handleDefaultCommands(cmd);
}
}
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
pw.println("Incremental Service Commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" install-start");
pw.println(" Opens an installation session");
pw.println(" install-finish SESSION_ID --file NAME:SIZE:INDEX --file NAME:SIZE:INDEX ...");
pw.println(" Commits an installation session specified by session ID for an APK ");
pw.println(" or a bundle of splits. Configures lib dirs or OBB files if specified.");
}
private int runInstallStart() {
final PrintWriter pw = getOutPrintWriter();
final PackageInstaller packageInstaller =
mContext.getPackageManager().getPackageInstaller();
if (packageInstaller == null) {
pw.println("Failed to get PackageInstaller.");
return ERROR_COMMAND_EXECUTION;
}
final Map<String, ParcelFileDescriptor> fds = getShellFileDescriptors();
if (fds == null) {
pw.println("File names and sizes don't match.");
return ERROR_DATA_LOADER_INIT;
}
// dup FDs before closing them
final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
for (Map.Entry<String, ParcelFileDescriptor> nfd : fds.entrySet()) {
try {
dataLoaderDynamicArgs.put(nfd.getKey(), nfd.getValue().dup());
} catch (IOException ignored) {
pw.println("Failed to dup shell file descriptor");
return ERROR_DATA_LOADER_INIT;
} finally {
try {
nfd.getValue().close();
} catch (IOException ignored) {
}
}
}
final DataLoaderParams params = DataLoaderParams.forIncremental(
new ComponentName(LOADER_PACKAGE_NAME, LOADER_CLASS_NAME), "",
dataLoaderDynamicArgs);
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.installFlags |= PackageManager.INSTALL_ALL_USERS;
// Replace existing if same package is already installed
sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
sessionParams.setDataLoaderParams(params);
try {
int sessionId = packageInstaller.createSession(sessionParams);
pw.println("Successfully opened session: sessionId = " + sessionId);
} catch (IOException ex) {
pw.println("Failed to create session.");
return ERROR_COMMAND_EXECUTION;
}
return 0;
}
private int runInstallFinish() {
final PrintWriter pw = getOutPrintWriter();
final int sessionId = parseInt(getNextArgRequired());
final List<InstallationFile> installationFiles = parseFileArgs(pw);
if (installationFiles == null) {
pw.println("Must specify at least one file to install.");
return ERROR_INVALID_ARGUMENTS;
}
final int numFiles = installationFiles.size();
if (numFiles == 0) {
pw.println("Must specify at least one file to install.");
return ERROR_INVALID_ARGUMENTS;
}
final PackageInstaller packageInstaller = mContext.getPackageManager()
.getPackageInstaller();
if (packageInstaller == null) {
pw.println("Failed to get PackageInstaller.");
return ERROR_COMMAND_EXECUTION;
}
final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
boolean success = false;
PackageInstaller.Session session = null;
try {
session = packageInstaller.openSession(sessionId);
for (int i = 0; i < numFiles; i++) {
InstallationFile file = installationFiles.get(i);
session.addFile(file.getLocation(), file.getName(), file.getLengthBytes(),
file.getMetadata(), file.getSignature());
}
session.commit(localReceiver.getIntentSender());
final Intent result = localReceiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
success = true;
pw.println("Success");
return 0;
} else {
pw.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
return ERROR_COMMAND_EXECUTION;
}
} catch (Exception e) {
e.printStackTrace(pw);
return ERROR_COMMAND_EXECUTION;
} finally {
if (!success) {
try {
if (session != null) {
session.abandon();
}
} catch (Exception ignore) {
}
}
}
}
private static class LocalIntentReceiver {
private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission,
Bundle options) {
try {
mResult.offer(intent, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
public Intent getResult() {
try {
return mResult.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/** Helpers. */
private Map<String, ParcelFileDescriptor> getShellFileDescriptors() {
Map<String, ParcelFileDescriptor> fds = new HashMap<>();
final FileDescriptor outFd = getOutFileDescriptor();
final FileDescriptor inFd = getInFileDescriptor();
try {
fds.put("inFd", ParcelFileDescriptor.dup(inFd));
fds.put("outFd", ParcelFileDescriptor.dup(outFd));
return fds;
} catch (Exception ex) {
Slog.e(TAG, "Failed to dup FDs");
return null;
}
}
private long parseLong(String arg) {
long result = -1;
try {
result = Long.parseLong(arg);
} catch (NumberFormatException e) {
}
return result;
}
private int parseInt(String arg) {
int result = -1;
try {
result = Integer.parseInt(arg);
} catch (NumberFormatException e) {
}
return result;
}
private List<InstallationFile> parseFileArgs(PrintWriter pw) {
List<InstallationFile> fileList = new ArrayList<>();
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--file": {
final String fileArgs = getNextArgRequired();
final String[] args = fileArgs.split(":");
if (args.length != 3) {
pw.println("Invalid file args: " + fileArgs);
return null;
}
final String name = args[0];
final long size = parseLong(args[1]);
if (size < 0) {
pw.println("Invalid file size in: " + fileArgs);
return null;
}
final long index = parseLong(args[2]);
if (index < 0) {
pw.println("Invalid file index in: " + fileArgs);
return null;
}
final byte[] metadata = String.valueOf(index).getBytes(
StandardCharsets.UTF_8);
fileList.add(
new InstallationFile(LOCATION_DATA_APP, name, size, metadata, null));
break;
}
default:
break;
}
}
return fileList;
}
}