1042 lines
41 KiB
C++
1042 lines
41 KiB
C++
/*
|
|
* 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 LOG_TAG "incfs-dataloaderconnector"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <fcntl.h>
|
|
#include <nativehelper/JNIPlatformHelp.h>
|
|
#include <nativehelper/scoped_local_ref.h>
|
|
#include <nativehelper/scoped_utf_chars.h>
|
|
#include <sys/stat.h>
|
|
#include <utils/Looper.h>
|
|
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
|
|
#include "JNIHelpers.h"
|
|
#include "ManagedDataLoader.h"
|
|
#include "dataloader.h"
|
|
#include "incfs.h"
|
|
#include "path.h"
|
|
|
|
namespace {
|
|
|
|
using namespace std::literals;
|
|
|
|
using ReadInfo = android::dataloader::ReadInfo;
|
|
using ReadInfoWithUid = android::dataloader::ReadInfoWithUid;
|
|
|
|
using FileId = android::incfs::FileId;
|
|
using RawMetadata = android::incfs::RawMetadata;
|
|
using UniqueControl = android::incfs::UniqueControl;
|
|
|
|
struct JniIds {
|
|
struct {
|
|
jint DATA_LOADER_CREATED;
|
|
jint DATA_LOADER_DESTROYED;
|
|
jint DATA_LOADER_STARTED;
|
|
jint DATA_LOADER_STOPPED;
|
|
jint DATA_LOADER_IMAGE_READY;
|
|
jint DATA_LOADER_IMAGE_NOT_READY;
|
|
jint DATA_LOADER_UNAVAILABLE;
|
|
jint DATA_LOADER_UNRECOVERABLE;
|
|
|
|
jint DATA_LOADER_TYPE_NONE;
|
|
jint DATA_LOADER_TYPE_STREAMING;
|
|
jint DATA_LOADER_TYPE_INCREMENTAL;
|
|
|
|
jint DATA_LOADER_LOCATION_DATA_APP;
|
|
jint DATA_LOADER_LOCATION_MEDIA_OBB;
|
|
jint DATA_LOADER_LOCATION_MEDIA_DATA;
|
|
} constants;
|
|
|
|
jmethodID parcelFileDescriptorGetFileDescriptor;
|
|
|
|
jfieldID incremental;
|
|
jfieldID service;
|
|
jfieldID callback;
|
|
|
|
jfieldID controlCmd;
|
|
jfieldID controlPendingReads;
|
|
jfieldID controlLog;
|
|
jfieldID controlBlocksWritten;
|
|
|
|
jfieldID paramsType;
|
|
jfieldID paramsPackageName;
|
|
jfieldID paramsClassName;
|
|
jfieldID paramsArguments;
|
|
|
|
jclass listener;
|
|
jmethodID listenerOnStatusChanged;
|
|
|
|
jmethodID callbackControlWriteData;
|
|
|
|
jmethodID listGet;
|
|
jmethodID listSize;
|
|
|
|
jfieldID installationFileLocation;
|
|
jfieldID installationFileName;
|
|
jfieldID installationFileLengthBytes;
|
|
jfieldID installationFileMetadata;
|
|
|
|
jmethodID incrementalServiceConnectorSetStorageParams;
|
|
|
|
JniIds(JNIEnv* env) {
|
|
listener = (jclass)env->NewGlobalRef(
|
|
FindClassOrDie(env, "android/content/pm/IDataLoaderStatusListener"));
|
|
listenerOnStatusChanged = GetMethodIDOrDie(env, listener, "onStatusChanged", "(II)V");
|
|
|
|
constants.DATA_LOADER_CREATED =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_CREATED");
|
|
constants.DATA_LOADER_DESTROYED =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_DESTROYED");
|
|
constants.DATA_LOADER_STARTED =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_STARTED");
|
|
constants.DATA_LOADER_STOPPED =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_STOPPED");
|
|
constants.DATA_LOADER_IMAGE_READY =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_IMAGE_READY");
|
|
constants.DATA_LOADER_IMAGE_NOT_READY =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_IMAGE_NOT_READY");
|
|
|
|
constants.DATA_LOADER_UNAVAILABLE =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_UNAVAILABLE");
|
|
constants.DATA_LOADER_UNRECOVERABLE =
|
|
GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_UNRECOVERABLE");
|
|
|
|
auto packageInstaller = (jclass)FindClassOrDie(env, "android/content/pm/PackageInstaller");
|
|
|
|
constants.DATA_LOADER_TYPE_NONE =
|
|
GetStaticIntFieldValueOrDie(env, packageInstaller, "DATA_LOADER_TYPE_NONE");
|
|
constants.DATA_LOADER_TYPE_STREAMING =
|
|
GetStaticIntFieldValueOrDie(env, packageInstaller, "DATA_LOADER_TYPE_STREAMING");
|
|
constants.DATA_LOADER_TYPE_INCREMENTAL =
|
|
GetStaticIntFieldValueOrDie(env, packageInstaller, "DATA_LOADER_TYPE_INCREMENTAL");
|
|
|
|
CHECK(constants.DATA_LOADER_TYPE_NONE == DATA_LOADER_TYPE_NONE);
|
|
CHECK(constants.DATA_LOADER_TYPE_STREAMING == DATA_LOADER_TYPE_STREAMING);
|
|
CHECK(constants.DATA_LOADER_TYPE_INCREMENTAL == DATA_LOADER_TYPE_INCREMENTAL);
|
|
|
|
constants.DATA_LOADER_LOCATION_DATA_APP =
|
|
GetStaticIntFieldValueOrDie(env, packageInstaller, "LOCATION_DATA_APP");
|
|
constants.DATA_LOADER_LOCATION_MEDIA_OBB =
|
|
GetStaticIntFieldValueOrDie(env, packageInstaller, "LOCATION_MEDIA_OBB");
|
|
constants.DATA_LOADER_LOCATION_MEDIA_DATA =
|
|
GetStaticIntFieldValueOrDie(env, packageInstaller, "LOCATION_MEDIA_DATA");
|
|
|
|
CHECK(constants.DATA_LOADER_LOCATION_DATA_APP == DATA_LOADER_LOCATION_DATA_APP);
|
|
CHECK(constants.DATA_LOADER_LOCATION_MEDIA_OBB == DATA_LOADER_LOCATION_MEDIA_OBB);
|
|
CHECK(constants.DATA_LOADER_LOCATION_MEDIA_DATA == DATA_LOADER_LOCATION_MEDIA_DATA);
|
|
|
|
auto parcelFileDescriptor = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
|
|
parcelFileDescriptorGetFileDescriptor =
|
|
GetMethodIDOrDie(env, parcelFileDescriptor, "getFileDescriptor",
|
|
"()Ljava/io/FileDescriptor;");
|
|
|
|
auto control = FindClassOrDie(env, "android/content/pm/FileSystemControlParcel");
|
|
incremental =
|
|
GetFieldIDOrDie(env, control, "incremental",
|
|
"Landroid/os/incremental/IncrementalFileSystemControlParcel;");
|
|
service = GetFieldIDOrDie(env, control, "service",
|
|
"Landroid/os/incremental/IIncrementalServiceConnector;");
|
|
callback =
|
|
GetFieldIDOrDie(env, control, "callback",
|
|
"Landroid/content/pm/IPackageInstallerSessionFileSystemConnector;");
|
|
|
|
auto incControl =
|
|
FindClassOrDie(env, "android/os/incremental/IncrementalFileSystemControlParcel");
|
|
controlCmd = GetFieldIDOrDie(env, incControl, "cmd", "Landroid/os/ParcelFileDescriptor;");
|
|
controlPendingReads = GetFieldIDOrDie(env, incControl, "pendingReads",
|
|
"Landroid/os/ParcelFileDescriptor;");
|
|
controlLog = GetFieldIDOrDie(env, incControl, "log", "Landroid/os/ParcelFileDescriptor;");
|
|
controlBlocksWritten = GetFieldIDOrDie(env, incControl, "blocksWritten",
|
|
"Landroid/os/ParcelFileDescriptor;");
|
|
|
|
auto params = FindClassOrDie(env, "android/content/pm/DataLoaderParamsParcel");
|
|
paramsType = GetFieldIDOrDie(env, params, "type", "I");
|
|
paramsPackageName = GetFieldIDOrDie(env, params, "packageName", "Ljava/lang/String;");
|
|
paramsClassName = GetFieldIDOrDie(env, params, "className", "Ljava/lang/String;");
|
|
paramsArguments = GetFieldIDOrDie(env, params, "arguments", "Ljava/lang/String;");
|
|
|
|
auto callbackControl =
|
|
FindClassOrDie(env,
|
|
"android/content/pm/IPackageInstallerSessionFileSystemConnector");
|
|
callbackControlWriteData =
|
|
GetMethodIDOrDie(env, callbackControl, "writeData",
|
|
"(Ljava/lang/String;JJLandroid/os/ParcelFileDescriptor;)V");
|
|
|
|
auto list = (jclass)FindClassOrDie(env, "java/util/List");
|
|
listGet = GetMethodIDOrDie(env, list, "get", "(I)Ljava/lang/Object;");
|
|
listSize = GetMethodIDOrDie(env, list, "size", "()I");
|
|
|
|
auto installationFileParcel =
|
|
(jclass)FindClassOrDie(env, "android/content/pm/InstallationFileParcel");
|
|
installationFileLocation = GetFieldIDOrDie(env, installationFileParcel, "location", "I");
|
|
installationFileName =
|
|
GetFieldIDOrDie(env, installationFileParcel, "name", "Ljava/lang/String;");
|
|
installationFileLengthBytes = GetFieldIDOrDie(env, installationFileParcel, "size", "J");
|
|
installationFileMetadata = GetFieldIDOrDie(env, installationFileParcel, "metadata", "[B");
|
|
|
|
auto incrementalServiceConnector =
|
|
FindClassOrDie(env, "android/os/incremental/IIncrementalServiceConnector");
|
|
incrementalServiceConnectorSetStorageParams =
|
|
GetMethodIDOrDie(env, incrementalServiceConnector, "setStorageParams", "(Z)I");
|
|
}
|
|
};
|
|
|
|
JavaVM* getJavaVM(JNIEnv* env) {
|
|
CHECK(env);
|
|
JavaVM* jvm = nullptr;
|
|
env->GetJavaVM(&jvm);
|
|
CHECK(jvm);
|
|
return jvm;
|
|
}
|
|
|
|
const JniIds& jniIds(JNIEnv* env) {
|
|
static const JniIds ids(env);
|
|
return ids;
|
|
}
|
|
|
|
bool checkAndClearJavaException(JNIEnv* env, std::string_view method) {
|
|
if (!env->ExceptionCheck()) {
|
|
return false;
|
|
}
|
|
|
|
LOG(ERROR) << "Java exception during DataLoader::" << method;
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
return true;
|
|
}
|
|
|
|
bool reportStatusViaCallback(JNIEnv* env, jobject listener, jint storageId, jint status) {
|
|
if (listener == nullptr) {
|
|
ALOGE("No listener object to talk to IncrementalService. "
|
|
"DataLoaderId=%d, "
|
|
"status=%d",
|
|
storageId, status);
|
|
return false;
|
|
}
|
|
|
|
const auto& jni = jniIds(env);
|
|
|
|
env->CallVoidMethod(listener, jni.listenerOnStatusChanged, storageId, status);
|
|
ALOGI("Reported status back to IncrementalService. DataLoaderId=%d, "
|
|
"status=%d",
|
|
storageId, status);
|
|
|
|
return true;
|
|
}
|
|
|
|
class DataLoaderConnector;
|
|
using DataLoaderConnectorPtr = std::shared_ptr<DataLoaderConnector>;
|
|
using DataLoaderConnectorsMap = std::unordered_map<int, DataLoaderConnectorPtr>;
|
|
|
|
struct Globals {
|
|
Globals() { managedDataLoaderFactory = new android::dataloader::ManagedDataLoaderFactory(); }
|
|
|
|
DataLoaderFactory* managedDataLoaderFactory = nullptr;
|
|
DataLoaderFactory* legacyDataLoaderFactory = nullptr;
|
|
DataLoaderFactory* dataLoaderFactory = nullptr;
|
|
|
|
std::mutex dataLoaderConnectorsLock;
|
|
// id->DataLoader map
|
|
DataLoaderConnectorsMap dataLoaderConnectors GUARDED_BY(dataLoaderConnectorsLock);
|
|
|
|
std::atomic_bool stopped;
|
|
std::thread pendingReadsLooperThread;
|
|
std::thread logLooperThread;
|
|
std::vector<ReadInfo> pendingReads;
|
|
std::vector<ReadInfo> pageReads;
|
|
std::vector<ReadInfoWithUid> pendingReadsWithUid;
|
|
std::vector<ReadInfoWithUid> pageReadsWithUid;
|
|
};
|
|
|
|
static Globals& globals() {
|
|
static Globals globals;
|
|
return globals;
|
|
}
|
|
|
|
struct IncFsLooper : public android::Looper {
|
|
IncFsLooper() : Looper(/*allowNonCallbacks=*/false) {}
|
|
~IncFsLooper() {}
|
|
};
|
|
|
|
static android::Looper& pendingReadsLooper() {
|
|
static IncFsLooper pendingReadsLooper;
|
|
return pendingReadsLooper;
|
|
}
|
|
|
|
static android::Looper& logLooper() {
|
|
static IncFsLooper logLooper;
|
|
return logLooper;
|
|
}
|
|
|
|
struct DataLoaderParamsPair {
|
|
static DataLoaderParamsPair createFromManaged(JNIEnv* env, jobject params);
|
|
|
|
const android::dataloader::DataLoaderParams& dataLoaderParams() const {
|
|
return mDataLoaderParams;
|
|
}
|
|
const ::DataLoaderParams& ndkDataLoaderParams() const { return mNDKDataLoaderParams; }
|
|
|
|
private:
|
|
DataLoaderParamsPair(android::dataloader::DataLoaderParams&& dataLoaderParams);
|
|
|
|
android::dataloader::DataLoaderParams mDataLoaderParams;
|
|
::DataLoaderParams mNDKDataLoaderParams;
|
|
};
|
|
|
|
static constexpr auto kPendingReadsBufferSize = 256;
|
|
|
|
class DataLoaderConnector : public android::dataloader::FilesystemConnector,
|
|
public android::dataloader::StatusListener {
|
|
public:
|
|
DataLoaderConnector(JNIEnv* env, jobject service, jint storageId, UniqueControl control,
|
|
jobject serviceConnector, jobject callbackControl, jobject listener)
|
|
: mJvm(getJavaVM(env)),
|
|
mService(env->NewGlobalRef(service)),
|
|
mServiceConnector(env->NewGlobalRef(serviceConnector)),
|
|
mCallbackControl(env->NewGlobalRef(callbackControl)),
|
|
mListener(env->NewGlobalRef(listener)),
|
|
mStorageId(storageId),
|
|
mControl(std::move(control)) {
|
|
CHECK(mJvm != nullptr);
|
|
}
|
|
DataLoaderConnector(const DataLoaderConnector&) = delete;
|
|
DataLoaderConnector(const DataLoaderConnector&&) = delete;
|
|
virtual ~DataLoaderConnector() {
|
|
if (mDataLoader && mDataLoader->onDestroy) {
|
|
mDataLoader->onDestroy(mDataLoader);
|
|
checkAndClearJavaException(__func__);
|
|
}
|
|
mDataLoader = nullptr;
|
|
|
|
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
|
|
|
|
const auto& jni = jniIds(env);
|
|
reportStatusViaCallback(env, mListener, mStorageId, jni.constants.DATA_LOADER_DESTROYED);
|
|
|
|
env->DeleteGlobalRef(mService);
|
|
env->DeleteGlobalRef(mServiceConnector);
|
|
env->DeleteGlobalRef(mCallbackControl);
|
|
env->DeleteGlobalRef(mListener);
|
|
} // to avoid delete-non-virtual-dtor
|
|
|
|
bool tryFactory(DataLoaderFactory* factory, bool withFeatures,
|
|
const DataLoaderParamsPair& params, jobject managedParams) {
|
|
if (!factory) {
|
|
return true;
|
|
}
|
|
|
|
// Let's try the non-default first.
|
|
mDataLoader = factory->onCreate(factory, ¶ms.ndkDataLoaderParams(), this, this, mJvm,
|
|
mService, managedParams);
|
|
if (checkAndClearJavaException(__func__)) {
|
|
return false;
|
|
}
|
|
if (!mDataLoader) {
|
|
return true;
|
|
}
|
|
|
|
mDataLoaderFeatures = withFeatures && mDataLoader->getFeatures
|
|
? mDataLoader->getFeatures(mDataLoader)
|
|
: DATA_LOADER_FEATURE_NONE;
|
|
if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
|
|
ALOGE("DataLoader supports UID");
|
|
CHECK(mDataLoader->onPageReadsWithUid);
|
|
CHECK(mDataLoader->onPendingReadsWithUid);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool onCreate(const DataLoaderParamsPair& params, jobject managedParams) {
|
|
CHECK(mDataLoader == nullptr);
|
|
|
|
if (!mDataLoader &&
|
|
!tryFactory(globals().dataLoaderFactory, /*withFeatures=*/true, params,
|
|
managedParams)) {
|
|
return false;
|
|
}
|
|
if (!mDataLoader &&
|
|
!tryFactory(globals().legacyDataLoaderFactory, /*withFeatures=*/false, params,
|
|
managedParams)) {
|
|
return false;
|
|
}
|
|
if (!mDataLoader &&
|
|
!tryFactory(globals().managedDataLoaderFactory, /*withFeatures=*/false, params,
|
|
managedParams)) {
|
|
return false;
|
|
}
|
|
if (!mDataLoader) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool onStart() {
|
|
CHECK(mDataLoader);
|
|
bool result = !mDataLoader->onStart || mDataLoader->onStart(mDataLoader);
|
|
if (checkAndClearJavaException(__func__)) {
|
|
result = false;
|
|
}
|
|
mRunning = result;
|
|
return result;
|
|
}
|
|
void onStop() {
|
|
CHECK(mDataLoader);
|
|
|
|
// Stopping both loopers and waiting for them to exit - we should be able to acquire/release
|
|
// both mutexes.
|
|
mRunning = false;
|
|
std::lock_guard{mPendingReadsLooperBusy}; // NOLINT
|
|
std::lock_guard{mLogLooperBusy}; // NOLINT
|
|
|
|
if (mDataLoader->onStop) {
|
|
mDataLoader->onStop(mDataLoader);
|
|
}
|
|
checkAndClearJavaException(__func__);
|
|
}
|
|
|
|
bool onPrepareImage(const android::dataloader::DataLoaderInstallationFiles& addedFiles) {
|
|
CHECK(mDataLoader);
|
|
bool result = !mDataLoader->onPrepareImage ||
|
|
mDataLoader->onPrepareImage(mDataLoader, addedFiles.data(), addedFiles.size());
|
|
return result;
|
|
}
|
|
|
|
template <class ReadInfoType>
|
|
int onPendingReadsLooperEvent(std::vector<ReadInfoType>& pendingReads) {
|
|
CHECK(mDataLoader);
|
|
std::lock_guard lock{mPendingReadsLooperBusy};
|
|
while (mRunning.load(std::memory_order_relaxed)) {
|
|
pendingReads.resize(kPendingReadsBufferSize);
|
|
if (android::incfs::waitForPendingReads(mControl, 0ms, &pendingReads) !=
|
|
android::incfs::WaitResult::HaveData ||
|
|
pendingReads.empty()) {
|
|
return 1;
|
|
}
|
|
if constexpr (std::is_same_v<ReadInfoType, ReadInfo>) {
|
|
if (mDataLoader->onPendingReads) {
|
|
mDataLoader->onPendingReads(mDataLoader, pendingReads.data(),
|
|
pendingReads.size());
|
|
}
|
|
} else {
|
|
if (mDataLoader->onPendingReadsWithUid) {
|
|
mDataLoader->onPendingReadsWithUid(mDataLoader, pendingReads.data(),
|
|
pendingReads.size());
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
template <class ReadInfoType>
|
|
int onLogLooperEvent(std::vector<ReadInfoType>& pageReads) {
|
|
CHECK(mDataLoader);
|
|
std::lock_guard lock{mLogLooperBusy};
|
|
while (mRunning.load(std::memory_order_relaxed)) {
|
|
pageReads.clear();
|
|
if (android::incfs::waitForPageReads(mControl, 0ms, &pageReads) !=
|
|
android::incfs::WaitResult::HaveData ||
|
|
pageReads.empty()) {
|
|
return 1;
|
|
}
|
|
if constexpr (std::is_same_v<ReadInfoType, ReadInfo>) {
|
|
if (mDataLoader->onPageReads) {
|
|
mDataLoader->onPageReads(mDataLoader, pageReads.data(), pageReads.size());
|
|
}
|
|
} else {
|
|
if (mDataLoader->onPageReadsWithUid) {
|
|
mDataLoader->onPageReadsWithUid(mDataLoader, pageReads.data(),
|
|
pageReads.size());
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int onPendingReadsLooperEvent(std::vector<ReadInfo>& pendingReads,
|
|
std::vector<ReadInfoWithUid>& pendingReadsWithUid) {
|
|
CHECK(mDataLoader);
|
|
if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
|
|
return this->onPendingReadsLooperEvent(pendingReadsWithUid);
|
|
} else {
|
|
return this->onPendingReadsLooperEvent(pendingReads);
|
|
}
|
|
}
|
|
|
|
int onLogLooperEvent(std::vector<ReadInfo>& pageReads,
|
|
std::vector<ReadInfoWithUid>& pageReadsWithUid) {
|
|
CHECK(mDataLoader);
|
|
if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
|
|
return this->onLogLooperEvent(pageReadsWithUid);
|
|
} else {
|
|
return this->onLogLooperEvent(pageReads);
|
|
}
|
|
}
|
|
|
|
void writeData(jstring name, jlong offsetBytes, jlong lengthBytes, jobject incomingFd) const {
|
|
CHECK(mCallbackControl);
|
|
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
|
|
const auto& jni = jniIds(env);
|
|
env->CallVoidMethod(mCallbackControl, jni.callbackControlWriteData, name, offsetBytes,
|
|
lengthBytes, incomingFd);
|
|
}
|
|
|
|
android::incfs::UniqueFd openForSpecialOps(FileId fid) const {
|
|
return android::incfs::openForSpecialOps(mControl, fid);
|
|
}
|
|
|
|
int writeBlocks(android::dataloader::Span<const IncFsDataBlock> blocks) const {
|
|
return android::incfs::writeBlocks(blocks);
|
|
}
|
|
|
|
int getRawMetadata(FileId fid, char buffer[], size_t* bufferSize) const {
|
|
return IncFs_GetMetadataById(mControl, fid, buffer, bufferSize);
|
|
}
|
|
|
|
bool setParams(DataLoaderFilesystemParams params) const {
|
|
CHECK(mServiceConnector);
|
|
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
|
|
const auto& jni = jniIds(env);
|
|
int result = env->CallIntMethod(mServiceConnector,
|
|
jni.incrementalServiceConnectorSetStorageParams,
|
|
params.readLogsEnabled);
|
|
if (result != 0) {
|
|
LOG(ERROR) << "setStorageParams failed with error: " << result;
|
|
}
|
|
if (checkAndClearJavaException(__func__)) {
|
|
return false;
|
|
}
|
|
return (result == 0);
|
|
}
|
|
|
|
bool reportStatus(DataLoaderStatus status) {
|
|
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
|
|
const auto& jni = jniIds(env);
|
|
|
|
jint osStatus;
|
|
switch (status) {
|
|
case DATA_LOADER_UNAVAILABLE:
|
|
osStatus = jni.constants.DATA_LOADER_UNAVAILABLE;
|
|
break;
|
|
case DATA_LOADER_UNRECOVERABLE:
|
|
osStatus = jni.constants.DATA_LOADER_UNRECOVERABLE;
|
|
break;
|
|
default: {
|
|
ALOGE("Unable to report invalid status. status=%d", status);
|
|
return false;
|
|
}
|
|
}
|
|
return reportStatusViaCallback(env, mListener, mStorageId, osStatus);
|
|
}
|
|
|
|
bool checkAndClearJavaException(std::string_view method) const {
|
|
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
|
|
return ::checkAndClearJavaException(env, method);
|
|
}
|
|
|
|
const UniqueControl& control() const { return mControl; }
|
|
jobject getListenerLocalRef(JNIEnv* env) const { return env->NewLocalRef(mListener); }
|
|
|
|
private:
|
|
JavaVM* const mJvm;
|
|
jobject const mService;
|
|
jobject const mServiceConnector;
|
|
jobject const mCallbackControl;
|
|
jobject const mListener;
|
|
|
|
jint const mStorageId;
|
|
UniqueControl const mControl;
|
|
|
|
::DataLoader* mDataLoader = nullptr;
|
|
DataLoaderFeatures mDataLoaderFeatures = DATA_LOADER_FEATURE_NONE;
|
|
|
|
std::mutex mPendingReadsLooperBusy;
|
|
std::mutex mLogLooperBusy;
|
|
std::atomic<bool> mRunning{false};
|
|
};
|
|
|
|
static int onPendingReadsLooperEvent(int fd, int events, void* data) {
|
|
if (globals().stopped) {
|
|
// No more listeners.
|
|
return 0;
|
|
}
|
|
auto&& dataLoaderConnector = (DataLoaderConnector*)data;
|
|
return dataLoaderConnector->onPendingReadsLooperEvent(globals().pendingReads,
|
|
globals().pendingReadsWithUid);
|
|
}
|
|
|
|
static int onLogLooperEvent(int fd, int events, void* data) {
|
|
if (globals().stopped) {
|
|
// No more listeners.
|
|
return 0;
|
|
}
|
|
auto&& dataLoaderConnector = (DataLoaderConnector*)data;
|
|
return dataLoaderConnector->onLogLooperEvent(globals().pageReads, globals().pageReadsWithUid);
|
|
}
|
|
|
|
static int createFdFromManaged(JNIEnv* env, jobject pfd) {
|
|
if (!pfd) {
|
|
return -1;
|
|
}
|
|
|
|
const auto& jni = jniIds(env);
|
|
auto managedFd = env->CallObjectMethod(pfd, jni.parcelFileDescriptorGetFileDescriptor);
|
|
return fcntl(jniGetFDFromFileDescriptor(env, managedFd), F_DUPFD_CLOEXEC, 0);
|
|
}
|
|
|
|
static jobject createServiceConnector(JNIEnv* env, jobject managedControl) {
|
|
const auto& jni = jniIds(env);
|
|
return env->GetObjectField(managedControl, jni.service);
|
|
}
|
|
|
|
static jobject createCallbackControl(JNIEnv* env, jobject managedControl) {
|
|
const auto& jni = jniIds(env);
|
|
return env->GetObjectField(managedControl, jni.callback);
|
|
}
|
|
|
|
static UniqueControl createIncFsControlFromManaged(JNIEnv* env, jobject managedControl) {
|
|
const auto& jni = jniIds(env);
|
|
auto managedIncControl = env->GetObjectField(managedControl, jni.incremental);
|
|
if (!managedIncControl) {
|
|
return UniqueControl();
|
|
}
|
|
auto cmd = createFdFromManaged(env, env->GetObjectField(managedIncControl, jni.controlCmd));
|
|
auto pr = createFdFromManaged(env,
|
|
env->GetObjectField(managedIncControl, jni.controlPendingReads));
|
|
auto log = createFdFromManaged(env, env->GetObjectField(managedIncControl, jni.controlLog));
|
|
auto blocksWritten =
|
|
createFdFromManaged(env,
|
|
env->GetObjectField(managedIncControl, jni.controlBlocksWritten));
|
|
return android::incfs::createControl(cmd, pr, log, blocksWritten);
|
|
}
|
|
|
|
DataLoaderParamsPair::DataLoaderParamsPair(android::dataloader::DataLoaderParams&& dataLoaderParams)
|
|
: mDataLoaderParams(std::move(dataLoaderParams)) {
|
|
mNDKDataLoaderParams.type = mDataLoaderParams.type();
|
|
mNDKDataLoaderParams.packageName = mDataLoaderParams.packageName().c_str();
|
|
mNDKDataLoaderParams.className = mDataLoaderParams.className().c_str();
|
|
mNDKDataLoaderParams.arguments = mDataLoaderParams.arguments().c_str();
|
|
}
|
|
|
|
DataLoaderParamsPair DataLoaderParamsPair::createFromManaged(JNIEnv* env, jobject managedParams) {
|
|
const auto& jni = jniIds(env);
|
|
|
|
const DataLoaderType type = (DataLoaderType)env->GetIntField(managedParams, jni.paramsType);
|
|
|
|
ScopedLocalRef<jstring> paramsPackageName(env,
|
|
GetStringField(env, managedParams,
|
|
jni.paramsPackageName));
|
|
ScopedLocalRef<jstring> paramsClassName(env,
|
|
GetStringField(env, managedParams,
|
|
jni.paramsClassName));
|
|
ScopedLocalRef<jstring> paramsArguments(env,
|
|
GetStringField(env, managedParams,
|
|
jni.paramsArguments));
|
|
ScopedUtfChars package(env, paramsPackageName.get());
|
|
ScopedUtfChars className(env, paramsClassName.get());
|
|
ScopedUtfChars arguments(env, paramsArguments.get());
|
|
return DataLoaderParamsPair(android::dataloader::DataLoaderParams(type, package.c_str(),
|
|
className.c_str(),
|
|
arguments.c_str()));
|
|
}
|
|
|
|
static void pendingReadsLooperThread() {
|
|
constexpr auto kTimeoutMsecs = -1;
|
|
while (!globals().stopped) {
|
|
pendingReadsLooper().pollAll(kTimeoutMsecs);
|
|
}
|
|
}
|
|
|
|
static void logLooperThread() {
|
|
constexpr auto kTimeoutMsecs = -1;
|
|
while (!globals().stopped) {
|
|
logLooper().pollAll(kTimeoutMsecs);
|
|
}
|
|
}
|
|
|
|
static std::string pathFromFd(int fd) {
|
|
static constexpr char fdNameFormat[] = "/proc/self/fd/%d";
|
|
char fdNameBuffer[NELEM(fdNameFormat) + 11 + 1]; // max int length + '\0'
|
|
snprintf(fdNameBuffer, NELEM(fdNameBuffer), fdNameFormat, fd);
|
|
|
|
std::string res;
|
|
// lstat() is supposed to return us exactly the needed buffer size, but
|
|
// somehow it may also return a smaller (but still >0) st_size field.
|
|
// That's why let's only use it for the initial estimate.
|
|
struct stat st = {};
|
|
if (::lstat(fdNameBuffer, &st) || st.st_size == 0) {
|
|
st.st_size = PATH_MAX;
|
|
}
|
|
auto bufSize = st.st_size;
|
|
for (;;) {
|
|
res.resize(bufSize + 1, '\0');
|
|
auto size = ::readlink(fdNameBuffer, &res[0], res.size());
|
|
if (size < 0) {
|
|
return {};
|
|
}
|
|
if (size > bufSize) {
|
|
// File got renamed in between lstat() and readlink() calls? Retry.
|
|
bufSize *= 2;
|
|
continue;
|
|
}
|
|
res.resize(size);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void DataLoader_Initialize(struct ::DataLoaderFactory* factory) {
|
|
CHECK(factory) << "DataLoader factory is invalid.";
|
|
globals().legacyDataLoaderFactory = factory;
|
|
}
|
|
|
|
void DataLoader_Initialize_WithFeatures(struct ::DataLoaderFactory* factory) {
|
|
CHECK(factory) << "DataLoader factory is invalid.";
|
|
globals().dataLoaderFactory = factory;
|
|
}
|
|
|
|
void DataLoader_FilesystemConnector_writeData(DataLoaderFilesystemConnectorPtr ifs, jstring name,
|
|
jlong offsetBytes, jlong lengthBytes,
|
|
jobject incomingFd) {
|
|
auto connector = static_cast<DataLoaderConnector*>(ifs);
|
|
return connector->writeData(name, offsetBytes, lengthBytes, incomingFd);
|
|
}
|
|
|
|
int DataLoader_FilesystemConnector_openForSpecialOps(DataLoaderFilesystemConnectorPtr ifs,
|
|
IncFsFileId fid) {
|
|
auto connector = static_cast<DataLoaderConnector*>(ifs);
|
|
return connector->openForSpecialOps(fid).release();
|
|
}
|
|
|
|
int DataLoader_FilesystemConnector_writeBlocks(DataLoaderFilesystemConnectorPtr ifs,
|
|
const IncFsDataBlock blocks[], int blocksCount) {
|
|
auto connector = static_cast<DataLoaderConnector*>(ifs);
|
|
return connector->writeBlocks({blocks, static_cast<size_t>(blocksCount)});
|
|
}
|
|
|
|
int DataLoader_FilesystemConnector_getRawMetadata(DataLoaderFilesystemConnectorPtr ifs,
|
|
IncFsFileId fid, char buffer[],
|
|
size_t* bufferSize) {
|
|
auto connector = static_cast<DataLoaderConnector*>(ifs);
|
|
return connector->getRawMetadata(fid, buffer, bufferSize);
|
|
}
|
|
|
|
bool DataLoader_FilesystemConnector_setParams(DataLoaderFilesystemConnectorPtr ifs,
|
|
DataLoaderFilesystemParams params) {
|
|
auto connector = static_cast<DataLoaderConnector*>(ifs);
|
|
return connector->setParams(params);
|
|
}
|
|
|
|
int DataLoader_StatusListener_reportStatus(DataLoaderStatusListenerPtr listener,
|
|
DataLoaderStatus status) {
|
|
auto connector = static_cast<DataLoaderConnector*>(listener);
|
|
return connector->reportStatus(status);
|
|
}
|
|
|
|
bool DataLoaderService_OnCreate(JNIEnv* env, jobject service, jint storageId, jobject control,
|
|
jobject params, jobject listener) {
|
|
{
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
auto dlIt = globals().dataLoaderConnectors.find(storageId);
|
|
if (dlIt != globals().dataLoaderConnectors.end()) {
|
|
ALOGI("id(%d): already exist, skipping creation.", storageId);
|
|
return true;
|
|
}
|
|
}
|
|
auto nativeControl = createIncFsControlFromManaged(env, control);
|
|
if (nativeControl) {
|
|
using namespace android::incfs;
|
|
ALOGI("DataLoader::create incremental fds: %d/%d/%d/%d", nativeControl.cmd(),
|
|
nativeControl.pendingReads(), nativeControl.logs(), nativeControl.blocksWritten());
|
|
auto cmdPath = pathFromFd(nativeControl.cmd());
|
|
auto dir = path::dirName(cmdPath);
|
|
ALOGI("DataLoader::create incremental dir: %s, files: %s/%s/%s/%s",
|
|
details::c_str(dir).get(), details::c_str(path::baseName(cmdPath)).get(),
|
|
details::c_str(path::baseName(pathFromFd(nativeControl.pendingReads()))).get(),
|
|
details::c_str(path::baseName(pathFromFd(nativeControl.logs()))).get(),
|
|
details::c_str(path::baseName(pathFromFd(nativeControl.blocksWritten()))).get());
|
|
} else {
|
|
ALOGI("DataLoader::create no incremental control");
|
|
}
|
|
|
|
auto nativeParams = DataLoaderParamsPair::createFromManaged(env, params);
|
|
ALOGI("DataLoader::create params: %d|%s|%s|%s", nativeParams.dataLoaderParams().type(),
|
|
nativeParams.dataLoaderParams().packageName().c_str(),
|
|
nativeParams.dataLoaderParams().className().c_str(),
|
|
nativeParams.dataLoaderParams().arguments().c_str());
|
|
|
|
auto serviceConnector = createServiceConnector(env, control);
|
|
auto callbackControl = createCallbackControl(env, control);
|
|
|
|
auto reportUnavailable = [env, storageId](jobject listener) {
|
|
const auto& jni = jniIds(env);
|
|
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_UNAVAILABLE);
|
|
};
|
|
// By default, it's disabled. Need to assign listener to enable.
|
|
std::unique_ptr<_jobject, decltype(reportUnavailable)>
|
|
reportUnavailableOnExit(nullptr, reportUnavailable);
|
|
|
|
auto dataLoaderConnector =
|
|
std::make_unique<DataLoaderConnector>(env, service, storageId, std::move(nativeControl),
|
|
serviceConnector, callbackControl, listener);
|
|
bool created = dataLoaderConnector->onCreate(nativeParams, params);
|
|
{
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
auto [dlIt, dlInserted] =
|
|
globals().dataLoaderConnectors.try_emplace(storageId,
|
|
std::move(dataLoaderConnector));
|
|
if (!dlInserted) {
|
|
ALOGE("id(%d): already exist, skipping creation.", storageId);
|
|
return false;
|
|
}
|
|
|
|
if (!created) {
|
|
globals().dataLoaderConnectors.erase(dlIt);
|
|
// Enable the reporter.
|
|
reportUnavailableOnExit.reset(listener);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const auto& jni = jniIds(env);
|
|
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_CREATED);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DataLoaderService_OnStart(JNIEnv* env, jint storageId) {
|
|
auto destroyAndReportUnavailable = [env, storageId](jobject listener) {
|
|
// Because of the MT the installer can call commit and recreate/restart dataLoader before
|
|
// system server has a change to destroy it.
|
|
DataLoaderService_OnDestroy(env, storageId);
|
|
const auto& jni = jniIds(env);
|
|
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_UNAVAILABLE);
|
|
};
|
|
// By default, it's disabled. Need to assign listener to enable.
|
|
std::unique_ptr<_jobject, decltype(destroyAndReportUnavailable)>
|
|
destroyAndReportUnavailableOnExit(nullptr, destroyAndReportUnavailable);
|
|
|
|
DataLoaderConnectorPtr dataLoaderConnector;
|
|
{
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
auto dlIt = globals().dataLoaderConnectors.find(storageId);
|
|
if (dlIt == globals().dataLoaderConnectors.end()) {
|
|
ALOGE("Failed to start id(%d): not found", storageId);
|
|
return false;
|
|
}
|
|
dataLoaderConnector = dlIt->second;
|
|
}
|
|
const UniqueControl* control = &(dataLoaderConnector->control());
|
|
jobject listener = dataLoaderConnector->getListenerLocalRef(env);
|
|
|
|
if (!dataLoaderConnector->onStart()) {
|
|
ALOGE("Failed to start id(%d): onStart returned false", storageId);
|
|
destroyAndReportUnavailableOnExit.reset(listener);
|
|
return false;
|
|
}
|
|
|
|
if (control->pendingReads() >= 0) {
|
|
auto&& looper = pendingReadsLooper();
|
|
if (!globals().pendingReadsLooperThread.joinable()) {
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
if (!globals().pendingReadsLooperThread.joinable()) {
|
|
globals().pendingReadsLooperThread = std::thread(&pendingReadsLooperThread);
|
|
}
|
|
}
|
|
|
|
looper.addFd(control->pendingReads(), android::Looper::POLL_CALLBACK,
|
|
android::Looper::EVENT_INPUT, &onPendingReadsLooperEvent,
|
|
dataLoaderConnector.get());
|
|
looper.wake();
|
|
}
|
|
|
|
if (control->logs() >= 0) {
|
|
auto&& looper = logLooper();
|
|
if (!globals().logLooperThread.joinable()) {
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
if (!globals().logLooperThread.joinable()) {
|
|
globals().logLooperThread = std::thread(&logLooperThread);
|
|
}
|
|
}
|
|
|
|
looper.addFd(control->logs(), android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
|
|
&onLogLooperEvent, dataLoaderConnector.get());
|
|
looper.wake();
|
|
}
|
|
|
|
const auto& jni = jniIds(env);
|
|
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_STARTED);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void DataLoaderService_OnStop_NoStatus(const UniqueControl* control,
|
|
const DataLoaderConnectorPtr& dataLoaderConnector) {
|
|
if (control->pendingReads() >= 0) {
|
|
pendingReadsLooper().removeFd(control->pendingReads());
|
|
pendingReadsLooper().wake();
|
|
}
|
|
if (control->logs() >= 0) {
|
|
logLooper().removeFd(control->logs());
|
|
logLooper().wake();
|
|
}
|
|
dataLoaderConnector->onStop();
|
|
}
|
|
|
|
bool DataLoaderService_OnStop(JNIEnv* env, jint storageId) {
|
|
DataLoaderConnectorPtr dataLoaderConnector;
|
|
{
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
auto dlIt = globals().dataLoaderConnectors.find(storageId);
|
|
if (dlIt == globals().dataLoaderConnectors.end()) {
|
|
ALOGI("Failed to stop id(%d): not found", storageId);
|
|
return true;
|
|
}
|
|
dataLoaderConnector = dlIt->second;
|
|
}
|
|
const UniqueControl* control = &(dataLoaderConnector->control());
|
|
jobject listener = dataLoaderConnector->getListenerLocalRef(env);
|
|
|
|
// Just stop.
|
|
DataLoaderService_OnStop_NoStatus(control, dataLoaderConnector);
|
|
|
|
const auto& jni = jniIds(env);
|
|
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_STOPPED);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DataLoaderService_OnDestroy(JNIEnv* env, jint storageId) {
|
|
DataLoaderConnectorPtr dataLoaderConnector;
|
|
{
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
auto dlIt = globals().dataLoaderConnectors.find(storageId);
|
|
if (dlIt == globals().dataLoaderConnectors.end()) {
|
|
ALOGI("Failed to destroy id(%d): not found", storageId);
|
|
return true;
|
|
}
|
|
dataLoaderConnector = std::move(dlIt->second);
|
|
globals().dataLoaderConnectors.erase(dlIt);
|
|
}
|
|
const UniqueControl* control = &(dataLoaderConnector->control());
|
|
|
|
// Stop/destroy.
|
|
DataLoaderService_OnStop_NoStatus(control, dataLoaderConnector);
|
|
// This will destroy the last instance of the DataLoaderConnectorPtr and should trigger the
|
|
// destruction of the DataLoader. However if there are any hanging instances, the destruction
|
|
// will be postponed. E.g. OnPrepareImage in progress at the same time we call OnDestroy.
|
|
dataLoaderConnector = {};
|
|
|
|
return true;
|
|
}
|
|
|
|
struct DataLoaderInstallationFilesPair {
|
|
static DataLoaderInstallationFilesPair createFromManaged(JNIEnv* env, jobjectArray jfiles);
|
|
|
|
using Files = std::vector<android::dataloader::DataLoaderInstallationFile>;
|
|
const Files& files() const { return mFiles; }
|
|
|
|
using NDKFiles = std::vector<::DataLoaderInstallationFile>;
|
|
const NDKFiles& ndkFiles() const { return mNDKFiles; }
|
|
|
|
private:
|
|
DataLoaderInstallationFilesPair(Files&& files);
|
|
|
|
Files mFiles;
|
|
NDKFiles mNDKFiles;
|
|
};
|
|
|
|
DataLoaderInstallationFilesPair DataLoaderInstallationFilesPair::createFromManaged(
|
|
JNIEnv* env, jobjectArray jfiles) {
|
|
const auto& jni = jniIds(env);
|
|
|
|
// jfiles is a Java array of InstallationFileParcel
|
|
auto size = env->GetArrayLength(jfiles);
|
|
DataLoaderInstallationFilesPair::Files files;
|
|
files.reserve(size);
|
|
|
|
for (int i = 0; i < size; ++i) {
|
|
ScopedLocalRef<jobject> jfile(env, env->GetObjectArrayElement(jfiles, i));
|
|
|
|
DataLoaderLocation location =
|
|
(DataLoaderLocation)env->GetIntField(jfile.get(), jni.installationFileLocation);
|
|
ScopedUtfChars name(env, GetStringField(env, jfile.get(), jni.installationFileName));
|
|
IncFsSize size = env->GetLongField(jfile.get(), jni.installationFileLengthBytes);
|
|
|
|
ScopedLocalRef<jbyteArray> jmetadataBytes(env,
|
|
GetByteArrayField(env, jfile.get(),
|
|
jni.installationFileMetadata));
|
|
auto metadataElements = env->GetByteArrayElements(jmetadataBytes.get(), nullptr);
|
|
auto metadataLength = env->GetArrayLength(jmetadataBytes.get());
|
|
RawMetadata metadata(metadataElements, metadataElements + metadataLength);
|
|
env->ReleaseByteArrayElements(jmetadataBytes.get(), metadataElements, 0);
|
|
|
|
files.emplace_back(location, name.c_str(), size, std::move(metadata));
|
|
}
|
|
|
|
return DataLoaderInstallationFilesPair(std::move(files));
|
|
}
|
|
|
|
DataLoaderInstallationFilesPair::DataLoaderInstallationFilesPair(Files&& files)
|
|
: mFiles(std::move(files)) {
|
|
const auto size = mFiles.size();
|
|
mNDKFiles.resize(size);
|
|
for (size_t i = 0; i < size; ++i) {
|
|
const auto& file = mFiles[i];
|
|
auto&& ndkFile = mNDKFiles[i];
|
|
|
|
ndkFile.location = file.location();
|
|
ndkFile.name = file.name().c_str();
|
|
ndkFile.size = file.size();
|
|
ndkFile.metadata.data = file.metadata().data();
|
|
ndkFile.metadata.size = file.metadata().size();
|
|
}
|
|
}
|
|
|
|
bool DataLoaderService_OnPrepareImage(JNIEnv* env, jint storageId, jobjectArray addedFiles,
|
|
jobjectArray removedFiles) {
|
|
DataLoaderConnectorPtr dataLoaderConnector;
|
|
{
|
|
std::lock_guard lock{globals().dataLoaderConnectorsLock};
|
|
auto dlIt = globals().dataLoaderConnectors.find(storageId);
|
|
if (dlIt == globals().dataLoaderConnectors.end()) {
|
|
ALOGE("Failed to handle onPrepareImage for id(%d): not found", storageId);
|
|
return false;
|
|
}
|
|
dataLoaderConnector = dlIt->second;
|
|
}
|
|
jobject listener = dataLoaderConnector->getListenerLocalRef(env);
|
|
|
|
auto addedFilesPair = DataLoaderInstallationFilesPair::createFromManaged(env, addedFiles);
|
|
bool result = dataLoaderConnector->onPrepareImage(addedFilesPair.ndkFiles());
|
|
|
|
const auto& jni = jniIds(env);
|
|
|
|
if (checkAndClearJavaException(env, "onPrepareImage")) {
|
|
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_UNAVAILABLE);
|
|
return false;
|
|
}
|
|
|
|
reportStatusViaCallback(env, listener, storageId,
|
|
result ? jni.constants.DATA_LOADER_IMAGE_READY
|
|
: jni.constants.DATA_LOADER_IMAGE_NOT_READY);
|
|
|
|
return result;
|
|
}
|