234 lines
9.7 KiB
Java
234 lines
9.7 KiB
Java
![]() |
/*
|
||
|
* 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 android.os.incremental;
|
||
|
|
||
|
/**
|
||
|
* Set up files and directories used in an installation session. Currently only used by Incremental
|
||
|
* Installation. For Incremental installation, the expected outcome of this function is: 0) All the
|
||
|
* files are in defaultStorage 1) All APK files are in the same directory, bound to mApkStorage, and
|
||
|
* bound to the InstallerSession's stage dir. The files are linked from mApkStorage to
|
||
|
* defaultStorage. 2) All lib files are in the sub directories as their names suggest, and in the
|
||
|
* same parent directory as the APK files. The files are linked from mApkStorage to defaultStorage.
|
||
|
* 3) OBB files are in another directory that is different from APK files and lib files, bound to
|
||
|
* mObbStorage. The files are linked from mObbStorage to defaultStorage.
|
||
|
*
|
||
|
* @throws IllegalStateException the session is not an Incremental installation session.
|
||
|
*/
|
||
|
|
||
|
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
|
||
|
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.content.Context;
|
||
|
import android.content.pm.DataLoaderParams;
|
||
|
import android.content.pm.IDataLoaderStatusListener;
|
||
|
import android.content.pm.IPackageLoadingProgressCallback;
|
||
|
import android.content.pm.InstallationFileParcel;
|
||
|
|
||
|
import java.io.File;
|
||
|
import java.io.IOException;
|
||
|
import java.util.List;
|
||
|
import java.util.UUID;
|
||
|
|
||
|
/**
|
||
|
* This class manages storage instances used during a package installation session.
|
||
|
* @hide
|
||
|
*/
|
||
|
public final class IncrementalFileStorages {
|
||
|
private static final String TAG = "IncrementalFileStorages";
|
||
|
|
||
|
private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
|
||
|
|
||
|
private @NonNull final IncrementalManager mIncrementalManager;
|
||
|
private @NonNull final File mStageDir;
|
||
|
private @Nullable IncrementalStorage mInheritedStorage;
|
||
|
private @Nullable IncrementalStorage mDefaultStorage;
|
||
|
|
||
|
/**
|
||
|
* Set up files and directories used in an installation session. Only used by Incremental.
|
||
|
* All the files will be created in defaultStorage.
|
||
|
*
|
||
|
* @throws IllegalStateException the session is not an Incremental installation session.
|
||
|
* @throws IOException if fails to setup files or directories.
|
||
|
*/
|
||
|
public static IncrementalFileStorages initialize(Context context,
|
||
|
@NonNull File stageDir,
|
||
|
@Nullable File inheritedDir,
|
||
|
@NonNull DataLoaderParams dataLoaderParams,
|
||
|
@Nullable IDataLoaderStatusListener statusListener,
|
||
|
@Nullable StorageHealthCheckParams healthCheckParams,
|
||
|
@Nullable IStorageHealthListener healthListener,
|
||
|
@NonNull List<InstallationFileParcel> addedFiles,
|
||
|
@NonNull PerUidReadTimeouts[] perUidReadTimeouts,
|
||
|
@Nullable IPackageLoadingProgressCallback progressCallback) throws IOException {
|
||
|
IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
|
||
|
Context.INCREMENTAL_SERVICE);
|
||
|
if (incrementalManager == null) {
|
||
|
throw new IOException("Failed to obtain incrementalManager.");
|
||
|
}
|
||
|
|
||
|
final IncrementalFileStorages result = new IncrementalFileStorages(stageDir, inheritedDir,
|
||
|
incrementalManager, dataLoaderParams);
|
||
|
for (InstallationFileParcel file : addedFiles) {
|
||
|
if (file.location == LOCATION_DATA_APP) {
|
||
|
try {
|
||
|
result.addApkFile(file);
|
||
|
} catch (IOException e) {
|
||
|
throw new IOException(
|
||
|
"Failed to add file to IncFS: " + file.name + ", reason: ", e);
|
||
|
}
|
||
|
} else {
|
||
|
throw new IOException("Unknown file location: " + file.location);
|
||
|
}
|
||
|
}
|
||
|
// Register progress loading callback after files have been added
|
||
|
if (progressCallback != null) {
|
||
|
incrementalManager.registerLoadingProgressCallback(stageDir.getAbsolutePath(),
|
||
|
progressCallback);
|
||
|
}
|
||
|
result.startLoading(dataLoaderParams, statusListener, healthCheckParams, healthListener,
|
||
|
perUidReadTimeouts);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private IncrementalFileStorages(@NonNull File stageDir,
|
||
|
@Nullable File inheritedDir,
|
||
|
@NonNull IncrementalManager incrementalManager,
|
||
|
@NonNull DataLoaderParams dataLoaderParams) throws IOException {
|
||
|
try {
|
||
|
mStageDir = stageDir;
|
||
|
mIncrementalManager = incrementalManager;
|
||
|
if (inheritedDir != null && IncrementalManager.isIncrementalPath(
|
||
|
inheritedDir.getAbsolutePath())) {
|
||
|
mInheritedStorage = mIncrementalManager.openStorage(
|
||
|
inheritedDir.getAbsolutePath());
|
||
|
if (mInheritedStorage != null) {
|
||
|
boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals(
|
||
|
dataLoaderParams.getComponentName().getPackageName());
|
||
|
if (systemDataLoader && !mInheritedStorage.isFullyLoaded()) {
|
||
|
// System data loader does not support incomplete storages.
|
||
|
throw new IOException("Inherited storage has missing pages.");
|
||
|
}
|
||
|
|
||
|
mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
|
||
|
mInheritedStorage, IncrementalManager.CREATE_MODE_CREATE
|
||
|
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
|
||
|
if (mDefaultStorage == null) {
|
||
|
throw new IOException(
|
||
|
"Couldn't create linked incremental storage at " + stageDir);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
|
||
|
dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE
|
||
|
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
|
||
|
if (mDefaultStorage == null) {
|
||
|
throw new IOException(
|
||
|
"Couldn't create incremental storage at " + stageDir);
|
||
|
}
|
||
|
} catch (IOException e) {
|
||
|
cleanUp();
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void addApkFile(@NonNull InstallationFileParcel apk) throws IOException {
|
||
|
final String apkName = apk.name;
|
||
|
final File targetFile = new File(mStageDir, apkName);
|
||
|
if (!targetFile.exists()) {
|
||
|
mDefaultStorage.makeFile(apkName, apk.size, 0777, null, apk.metadata,
|
||
|
apk.signature, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Starts or re-starts loading of data.
|
||
|
*/
|
||
|
public void startLoading(
|
||
|
@NonNull DataLoaderParams dataLoaderParams,
|
||
|
@Nullable IDataLoaderStatusListener statusListener,
|
||
|
@Nullable StorageHealthCheckParams healthCheckParams,
|
||
|
@Nullable IStorageHealthListener healthListener,
|
||
|
@NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
|
||
|
if (!mDefaultStorage.startLoading(dataLoaderParams, statusListener, healthCheckParams,
|
||
|
healthListener, perUidReadTimeouts)) {
|
||
|
throw new IOException(
|
||
|
"Failed to start or restart loading data for Incremental installation.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates file in default storage and sets its content.
|
||
|
*/
|
||
|
public void makeFile(@NonNull String name, @NonNull byte[] content,
|
||
|
@NonNull int mode) throws IOException {
|
||
|
mDefaultStorage.makeFile(name, content.length, mode, UUID.randomUUID(),
|
||
|
null, null, content);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a hardlink from inherited storage to default.
|
||
|
*/
|
||
|
public boolean makeLink(@NonNull String relativePath, @NonNull String fromBase,
|
||
|
@NonNull String toBase) throws IOException {
|
||
|
if (mInheritedStorage == null) {
|
||
|
return false;
|
||
|
}
|
||
|
final File sourcePath = new File(fromBase, relativePath);
|
||
|
final File destPath = new File(toBase, relativePath);
|
||
|
mInheritedStorage.makeLink(sourcePath.getAbsolutePath(), mDefaultStorage,
|
||
|
destPath.getAbsolutePath());
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Permanently disables readlogs.
|
||
|
*/
|
||
|
public void disallowReadLogs() {
|
||
|
mDefaultStorage.disallowReadLogs();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resets the states and unbinds storage instances for an installation session.
|
||
|
*/
|
||
|
public void cleanUpAndMarkComplete() {
|
||
|
IncrementalStorage defaultStorage = cleanUp();
|
||
|
if (defaultStorage != null) {
|
||
|
defaultStorage.onInstallationComplete();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private IncrementalStorage cleanUp() {
|
||
|
IncrementalStorage defaultStorage = mDefaultStorage;
|
||
|
mInheritedStorage = null;
|
||
|
mDefaultStorage = null;
|
||
|
if (defaultStorage == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
mIncrementalManager.unregisterLoadingProgressCallbacks(mStageDir.getAbsolutePath());
|
||
|
defaultStorage.unBind(mStageDir.getAbsolutePath());
|
||
|
} catch (IOException ignored) {
|
||
|
}
|
||
|
return defaultStorage;
|
||
|
}
|
||
|
}
|