[incremental/installation] new API to add file to session
A new method called addInstallationFile() to add files to an installation session. Test: builds Change-Id: Iaf086f5cfbc131428668e37d83e7677f5d5bf586
This commit is contained in:
@@ -46,4 +46,5 @@ interface IPackageInstallerSession {
|
||||
int getParentSessionId();
|
||||
|
||||
boolean isStaged();
|
||||
void addFile(in String name, long size, in byte[] metadata);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ import android.os.ParcelableException;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.incremental.IncrementalDataLoaderParams;
|
||||
import android.os.incremental.IncrementalDataLoaderParamsParcel;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.ArraySet;
|
||||
@@ -1211,6 +1213,27 @@ public class PackageInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure files for an installation session.
|
||||
*
|
||||
* Currently only for Incremental installation session. Once this method is called,
|
||||
* the files and their paths, as specified in the parameters, will be created and properly
|
||||
* configured in the Incremental File System.
|
||||
*
|
||||
* TODO(b/136132412): update this and InstallationFile class with latest API design.
|
||||
*
|
||||
* @throws IllegalStateException if {@link SessionParams#incrementalParams} is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
|
||||
try {
|
||||
mSession.addFile(name, size, metadata);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release this session object. You can open the session again if it
|
||||
* hasn't been finalized.
|
||||
@@ -1398,6 +1421,8 @@ public class PackageInstaller {
|
||||
public boolean isStaged;
|
||||
/** {@hide} */
|
||||
public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
|
||||
/** {@hide} */
|
||||
public IncrementalDataLoaderParams incrementalParams;
|
||||
|
||||
/**
|
||||
* Construct parameters for a new package install session.
|
||||
@@ -1431,6 +1456,12 @@ public class PackageInstaller {
|
||||
isMultiPackage = source.readBoolean();
|
||||
isStaged = source.readBoolean();
|
||||
requiredInstalledVersionCode = source.readLong();
|
||||
IncrementalDataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
|
||||
IncrementalDataLoaderParamsParcel.class.getClassLoader());
|
||||
if (dataLoaderParamsParcel != null) {
|
||||
incrementalParams = new IncrementalDataLoaderParams(
|
||||
dataLoaderParamsParcel);
|
||||
}
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@@ -1454,6 +1485,7 @@ public class PackageInstaller {
|
||||
ret.isMultiPackage = isMultiPackage;
|
||||
ret.isStaged = isStaged;
|
||||
ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
|
||||
ret.incrementalParams = incrementalParams;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1782,6 +1814,16 @@ public class PackageInstaller {
|
||||
return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Incremental data loader params.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
|
||||
public void setIncrementalParams(@NonNull IncrementalDataLoaderParams incrementalParams) {
|
||||
this.incrementalParams = incrementalParams;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.printPair("mode", mode);
|
||||
@@ -1831,6 +1873,11 @@ public class PackageInstaller {
|
||||
dest.writeBoolean(isMultiPackage);
|
||||
dest.writeBoolean(isStaged);
|
||||
dest.writeLong(requiredInstalledVersionCode);
|
||||
if (incrementalParams != null) {
|
||||
dest.writeParcelable(incrementalParams.getData(), flags);
|
||||
} else {
|
||||
dest.writeParcelable(null, flags);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SessionParams>
|
||||
|
||||
280
core/java/android/os/incremental/IncrementalFileStorages.java
Normal file
280
core/java/android/os/incremental/IncrementalFileStorages.java
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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 dalvik.system.VMRuntime.getInstructionSet;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.pm.InstallationFile;
|
||||
import android.os.IVold;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This class manages storage instances used during a package installation session.
|
||||
* @hide
|
||||
*/
|
||||
public final class IncrementalFileStorages {
|
||||
private static final String TAG = "IncrementalFileStorages";
|
||||
private @Nullable IncrementalStorage mDefaultStorage;
|
||||
private @Nullable IncrementalStorage mApkStorage;
|
||||
private @Nullable IncrementalStorage mObbStorage;
|
||||
private @Nullable String mDefaultDir;
|
||||
private @Nullable String mObbDir;
|
||||
private @NonNull IncrementalManager mIncrementalManager;
|
||||
private @Nullable ArraySet<String> mLibDirs;
|
||||
private @NonNull String mPackageName;
|
||||
private @NonNull File mStageDir;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public IncrementalFileStorages(@NonNull String packageName,
|
||||
@NonNull File stageDir,
|
||||
@NonNull IncrementalManager incrementalManager,
|
||||
@NonNull IncrementalDataLoaderParams incrementalDataLoaderParams) {
|
||||
mPackageName = packageName;
|
||||
mStageDir = stageDir;
|
||||
mIncrementalManager = incrementalManager;
|
||||
if (incrementalDataLoaderParams.getPackageName().equals("local")) {
|
||||
final String incrementalPath = incrementalDataLoaderParams.getStaticArgs();
|
||||
mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
|
||||
mDefaultDir = incrementalPath;
|
||||
return;
|
||||
}
|
||||
mDefaultDir = getTempDir();
|
||||
if (mDefaultDir == null) {
|
||||
return;
|
||||
}
|
||||
mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
|
||||
incrementalDataLoaderParams,
|
||||
IncrementalManager.CREATE_MODE_CREATE
|
||||
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file into the installation session. Makes sure it will be placed inside
|
||||
* a proper storage instance, based on its file type.
|
||||
*/
|
||||
public void addFile(@NonNull InstallationFile file) throws IOException {
|
||||
if (mDefaultStorage == null) {
|
||||
throw new IOException("Cannot add file because default storage does not exist");
|
||||
}
|
||||
if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
|
||||
addApkFile(file);
|
||||
} else if (file.getFileType() == InstallationFile.FILE_TYPE_OBB) {
|
||||
addObbFile(file);
|
||||
} else if (file.getFileType() == InstallationFile.FILE_TYPE_LIB) {
|
||||
addLibFile(file);
|
||||
} else {
|
||||
throw new IOException("Unknown file type: " + file.getFileType());
|
||||
}
|
||||
}
|
||||
|
||||
private void addApkFile(@NonNull InstallationFile apk) throws IOException {
|
||||
// Create a storage for APK files and lib files
|
||||
final String stageDirPath = mStageDir.getAbsolutePath();
|
||||
if (mApkStorage == null) {
|
||||
mApkStorage = mIncrementalManager.createStorage(stageDirPath, mDefaultStorage,
|
||||
IncrementalManager.CREATE_MODE_CREATE
|
||||
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
|
||||
mApkStorage.bind(stageDirPath);
|
||||
}
|
||||
|
||||
if (!new File(mDefaultDir, apk.getName()).exists()) {
|
||||
mDefaultStorage.makeFile(apk.getName(), apk.getSize(),
|
||||
apk.getMetadata());
|
||||
}
|
||||
// Assuming APK files are already named properly, e.g., "base.apk"
|
||||
mDefaultStorage.makeLink(apk.getName(), mApkStorage, apk.getName());
|
||||
}
|
||||
|
||||
private void addLibFile(@NonNull InstallationFile lib) throws IOException {
|
||||
// TODO(b/136132412): remove this after we have incfs support for lib file mapping
|
||||
if (mApkStorage == null) {
|
||||
throw new IOException("Cannot add lib file without adding an apk file first");
|
||||
}
|
||||
if (mLibDirs == null) {
|
||||
mLibDirs = new ArraySet<>();
|
||||
}
|
||||
String current = "";
|
||||
final Path libDirPath = Paths.get(lib.getName()).getParent();
|
||||
final int numDirComponents = libDirPath.getNameCount();
|
||||
for (int i = 0; i < numDirComponents; i++) {
|
||||
String dirName = libDirPath.getName(i).toString();
|
||||
try {
|
||||
dirName = getInstructionSet(dirName);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
current += dirName;
|
||||
if (!mLibDirs.contains(current)) {
|
||||
mDefaultStorage.makeDirectory(current);
|
||||
mApkStorage.makeDirectory(current);
|
||||
mLibDirs.add(current);
|
||||
}
|
||||
current += '/';
|
||||
}
|
||||
String libFilePath = current + Paths.get(lib.getName()).getFileName();
|
||||
mDefaultStorage.makeFile(libFilePath, lib.getSize(), lib.getMetadata());
|
||||
mDefaultStorage.makeLink(libFilePath, mApkStorage, libFilePath);
|
||||
}
|
||||
|
||||
private void addObbFile(@NonNull InstallationFile obb) throws IOException {
|
||||
if (mObbStorage == null) {
|
||||
// Create a storage for OBB files
|
||||
mObbDir = getTempDir();
|
||||
if (mObbDir == null) {
|
||||
throw new IOException("Failed to create obb storage directory.");
|
||||
}
|
||||
mObbStorage = mIncrementalManager.createStorage(
|
||||
mObbDir, mDefaultStorage,
|
||||
IncrementalManager.CREATE_MODE_CREATE
|
||||
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
|
||||
}
|
||||
mDefaultStorage.makeFile(obb.getName(), obb.getSize(), obb.getMetadata());
|
||||
mDefaultStorage.makeLink(obb.getName(), mObbStorage, obb.getName());
|
||||
}
|
||||
|
||||
private boolean hasObb() {
|
||||
return (mObbStorage != null && mObbDir != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts loading data for default storage.
|
||||
* TODO(b/136132412): update the implementation with latest API design.
|
||||
*/
|
||||
public boolean startLoading() {
|
||||
if (mDefaultStorage == null) {
|
||||
return false;
|
||||
}
|
||||
return mDefaultStorage.startLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up obb storage directory and create bindings.
|
||||
*/
|
||||
public void finishSetUp() {
|
||||
if (!hasObb()) {
|
||||
return;
|
||||
}
|
||||
final String mainObbDir = String.format("/storage/emulated/0/Android/obb/%s", mPackageName);
|
||||
final String packageObbDirRoot =
|
||||
String.format("/mnt/runtime/%s/emulated/0/Android/obb/", mPackageName);
|
||||
final String[] obbDirs = {
|
||||
packageObbDirRoot + "read",
|
||||
packageObbDirRoot + "write",
|
||||
packageObbDirRoot + "full",
|
||||
packageObbDirRoot + "default",
|
||||
String.format("/data/media/0/Android/obb/%s", mPackageName),
|
||||
mainObbDir,
|
||||
};
|
||||
try {
|
||||
Slog.i(TAG, "Creating obb directory '" + mainObbDir + "'");
|
||||
final IVold vold = IVold.Stub.asInterface(ServiceManager.getServiceOrThrow("vold"));
|
||||
vold.mkdirs(mainObbDir);
|
||||
for (String d : obbDirs) {
|
||||
mObbStorage.bindPermanent(d);
|
||||
}
|
||||
} catch (ServiceManager.ServiceNotFoundException ex) {
|
||||
Slog.e(TAG, "vold service is not found.");
|
||||
cleanUp();
|
||||
} catch (IOException | RemoteException ex) {
|
||||
Slog.e(TAG, "Failed to create obb dir at: " + mainObbDir, ex);
|
||||
cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the states and unbinds storage instances for an installation session.
|
||||
* TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
|
||||
*/
|
||||
public void cleanUp() {
|
||||
if (mDefaultStorage != null && mDefaultDir != null) {
|
||||
try {
|
||||
mDefaultStorage.unBind(mDefaultDir);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
mDefaultDir = null;
|
||||
mDefaultStorage = null;
|
||||
}
|
||||
if (mApkStorage != null && mStageDir != null) {
|
||||
try {
|
||||
mApkStorage.unBind(mStageDir.getAbsolutePath());
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
mApkStorage = null;
|
||||
}
|
||||
if (mObbStorage != null && mObbDir != null) {
|
||||
try {
|
||||
mObbStorage.unBind(mObbDir);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
mObbDir = null;
|
||||
mObbStorage = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getTempDir() {
|
||||
final String tmpDirRoot = "/data/tmp";
|
||||
final Random random = new Random();
|
||||
final Path tmpDir =
|
||||
Paths.get(tmpDirRoot, String.valueOf(random.nextInt(Integer.MAX_VALUE - 1)));
|
||||
try {
|
||||
Files.createDirectories(tmpDir);
|
||||
} catch (Exception ex) {
|
||||
Slog.e(TAG, "Failed to create dir", ex);
|
||||
return null;
|
||||
}
|
||||
return tmpDir.toAbsolutePath().toString();
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import android.content.IntentSender;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageInstallObserver2;
|
||||
import android.content.pm.IPackageInstallerSession;
|
||||
import android.content.pm.InstallationFile;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
@@ -81,6 +82,8 @@ import android.os.Process;
|
||||
import android.os.RevocableFileDescriptor;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.incremental.IncrementalFileStorages;
|
||||
import android.os.incremental.IncrementalManager;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.stats.devicepolicy.DevicePolicyEnums;
|
||||
import android.system.ErrnoException;
|
||||
@@ -309,6 +312,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
@GuardedBy("mLock")
|
||||
private boolean mVerityFound;
|
||||
|
||||
private IncrementalFileStorages mIncrementalFileStorages;
|
||||
|
||||
private static final FileFilter sAddedFilter = new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
@@ -471,6 +476,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
mStagedSessionErrorCode = stagedSessionErrorCode;
|
||||
mStagedSessionErrorMessage =
|
||||
stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
|
||||
|
||||
// TODO(b/136132412): sanity check if session should not be incremental
|
||||
if (!params.isStaged && params.incrementalParams != null
|
||||
&& !params.incrementalParams.getPackageName().isEmpty()) {
|
||||
IncrementalManager incrementalManager = (IncrementalManager) mContext.getSystemService(
|
||||
Context.INCREMENTAL_SERVICE);
|
||||
if (incrementalManager != null) {
|
||||
mIncrementalFileStorages =
|
||||
new IncrementalFileStorages(mPackageName, stageDir, incrementalManager,
|
||||
params.incrementalParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SessionInfo generateInfo() {
|
||||
@@ -874,10 +891,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
if (mIncrementalFileStorages != null) {
|
||||
mIncrementalFileStorages.finishSetUp();
|
||||
}
|
||||
|
||||
mHandler.obtainMessage(MSG_SEAL, statusReceiver).sendToTarget();
|
||||
}
|
||||
|
||||
private void handleSeal(@NonNull IntentSender statusReceiver) {
|
||||
// TODO(b/136132412): update with new APIs
|
||||
if (mIncrementalFileStorages != null) {
|
||||
mIncrementalFileStorages.startLoading();
|
||||
}
|
||||
if (!markAsCommitted(statusReceiver)) {
|
||||
return;
|
||||
}
|
||||
@@ -1492,6 +1517,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
computeProgressLocked(true);
|
||||
|
||||
// Unpack native libraries
|
||||
// TODO(b/136132412): skip for incremental installation
|
||||
extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
|
||||
}
|
||||
|
||||
@@ -2182,7 +2208,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
destroyInternal();
|
||||
}
|
||||
|
||||
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
|
||||
}
|
||||
|
||||
@@ -2268,6 +2293,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
return mParentSessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
|
||||
if (mIncrementalFileStorages == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot add Incremental File to a non-Incremental session.");
|
||||
}
|
||||
try {
|
||||
mIncrementalFileStorages.addFile(new InstallationFile(name, size, metadata));
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to add and configure Incremental File: " + name, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
|
||||
final IntentSender statusReceiver;
|
||||
final String packageName;
|
||||
@@ -2390,6 +2429,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
// since these packages are supposed to be read on reboot.
|
||||
// Those dirs are deleted when the staged session has reached a final state.
|
||||
if (stageDir != null && !params.isStaged) {
|
||||
if (mIncrementalFileStorages != null) {
|
||||
mIncrementalFileStorages.cleanUp();
|
||||
}
|
||||
try {
|
||||
mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
|
||||
} catch (InstallerException ignored) {
|
||||
@@ -2403,6 +2445,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
mSessionProvider.getSession(childSessionId).cleanStageDir();
|
||||
}
|
||||
} else {
|
||||
if (mIncrementalFileStorages != null) {
|
||||
mIncrementalFileStorages.cleanUp();
|
||||
}
|
||||
try {
|
||||
mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
|
||||
} catch (InstallerException ignored) {
|
||||
|
||||
Reference in New Issue
Block a user