Loading core/java/android/content/pm/IPackageInstallerSession.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -46,4 +46,5 @@ interface IPackageInstallerSession { int getParentSessionId(); boolean isStaged(); void addFile(in String name, long size, in byte[] metadata); } core/java/android/content/pm/PackageInstaller.java +47 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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} */ Loading @@ -1454,6 +1485,7 @@ public class PackageInstaller { ret.isMultiPackage = isMultiPackage; ret.isStaged = isStaged; ret.requiredInstalledVersionCode = requiredInstalledVersionCode; ret.incrementalParams = incrementalParams; return ret; } Loading Loading @@ -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); Loading Loading @@ -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> Loading core/java/android/os/incremental/IncrementalFileStorages.java 0 → 100644 +280 −0 Original line number Diff line number Diff line /* * 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(); } } services/core/java/com/android/server/pm/PackageInstallerSession.java +46 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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() { Loading Loading @@ -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; } Loading Loading @@ -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()); } Loading Loading @@ -2182,7 +2208,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } destroyInternal(); } dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); } Loading Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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) { Loading Loading
core/java/android/content/pm/IPackageInstallerSession.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -46,4 +46,5 @@ interface IPackageInstallerSession { int getParentSessionId(); boolean isStaged(); void addFile(in String name, long size, in byte[] metadata); }
core/java/android/content/pm/PackageInstaller.java +47 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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} */ Loading @@ -1454,6 +1485,7 @@ public class PackageInstaller { ret.isMultiPackage = isMultiPackage; ret.isStaged = isStaged; ret.requiredInstalledVersionCode = requiredInstalledVersionCode; ret.incrementalParams = incrementalParams; return ret; } Loading Loading @@ -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); Loading Loading @@ -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> Loading
core/java/android/os/incremental/IncrementalFileStorages.java 0 → 100644 +280 −0 Original line number Diff line number Diff line /* * 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(); } }
services/core/java/com/android/server/pm/PackageInstallerSession.java +46 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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() { Loading Loading @@ -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; } Loading Loading @@ -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()); } Loading Loading @@ -2182,7 +2208,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } destroyInternal(); } dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); } Loading Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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) { Loading