summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Songchun Fan <schfan@google.com> 2019-11-29 15:43:27 -0800
committer Songchun Fan <schfan@google.com> 2019-12-04 18:05:41 -0800
commit4e758697ebacc463fa91aa9bb661b71490d419fa (patch)
tree27929fc627e045038af449e46b232b142a992c53
parent6dd47b58c53acee91810cd2714b97aacbfbfec6a (diff)
[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
-rw-r--r--core/java/android/content/pm/IPackageInstallerSession.aidl1
-rw-r--r--core/java/android/content/pm/PackageInstaller.java47
-rw-r--r--core/java/android/os/incremental/IncrementalFileStorages.java280
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java47
4 files changed, 374 insertions, 1 deletions
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index b74061793f9b..0b3c7654f4fb 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -46,4 +46,5 @@ interface IPackageInstallerSession {
int getParentSessionId();
boolean isStaged();
+ void addFile(in String name, long size, in byte[] metadata);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 4ab6f3cd92b4..e9fc8f6acfc6 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -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;
@@ -1212,6 +1214,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>
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
new file mode 100644
index 000000000000..5bd0748b8d97
--- /dev/null
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -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();
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4852947619f9..883baf7886af 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -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) {