diff options
| author | 2019-11-29 15:43:27 -0800 | |
|---|---|---|
| committer | 2019-12-04 18:05:41 -0800 | |
| commit | 4e758697ebacc463fa91aa9bb661b71490d419fa (patch) | |
| tree | 27929fc627e045038af449e46b232b142a992c53 | |
| parent | 6dd47b58c53acee91810cd2714b97aacbfbfec6a (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
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) { |