diff --git a/api/current.txt b/api/current.txt index f2200afa70837..0154dd2dc9cd3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8666,14 +8666,14 @@ package android.content.pm { field public static final java.lang.String EXTRA_STATUS = "android.content.pm.extra.STATUS"; field public static final java.lang.String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; field public static final int STATUS_FAILURE = 1; // 0x1 - field public static final int STATUS_FAILURE_ABORTED = 2; // 0x2 - field public static final int STATUS_FAILURE_BLOCKED = 1; // 0x1 - field public static final int STATUS_FAILURE_CONFLICT = 4; // 0x4 - field public static final int STATUS_FAILURE_INCOMPATIBLE = 6; // 0x6 - field public static final int STATUS_FAILURE_INVALID = 3; // 0x3 - field public static final int STATUS_FAILURE_STORAGE = 5; // 0x5 + field public static final int STATUS_FAILURE_ABORTED = 3; // 0x3 + field public static final int STATUS_FAILURE_BLOCKED = 2; // 0x2 + field public static final int STATUS_FAILURE_CONFLICT = 5; // 0x5 + field public static final int STATUS_FAILURE_INCOMPATIBLE = 7; // 0x7 + field public static final int STATUS_FAILURE_INVALID = 4; // 0x4 + field public static final int STATUS_FAILURE_STORAGE = 6; // 0x6 + field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff field public static final int STATUS_SUCCESS = 0; // 0x0 - field public static final int STATUS_USER_ACTION_REQUIRED = -1; // 0xffffffff } public static class PackageInstaller.Session implements java.io.Closeable { @@ -8681,7 +8681,7 @@ package android.content.pm { method public void close(); method public void commit(android.content.IntentSender); method public void fsync(java.io.OutputStream) throws java.io.IOException; - method public java.lang.String[] getNames(); + method public java.lang.String[] getNames() throws java.io.IOException; method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException; method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; method public void setProgress(float); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index aa4ea454fc5cf..4ac701fbde2ce 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -129,7 +129,7 @@ public class PackageInstaller { * * @see Intent#getParcelableExtra(String) */ - public static final int STATUS_USER_ACTION_REQUIRED = -1; + public static final int STATUS_PENDING_USER_ACTION = -1; /** * The operation succeeded. @@ -152,7 +152,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_BLOCKED = 1; + public static final int STATUS_FAILURE_BLOCKED = 2; /** * The operation failed because it was actively aborted. For example, the @@ -161,7 +161,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_ABORTED = 2; + public static final int STATUS_FAILURE_ABORTED = 3; /** * The operation failed because one or more of the APKs was invalid. For @@ -170,7 +170,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_INVALID = 3; + public static final int STATUS_FAILURE_INVALID = 4; /** * The operation failed because it conflicts (or is inconsistent with) with @@ -183,7 +183,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_CONFLICT = 4; + public static final int STATUS_FAILURE_CONFLICT = 5; /** * The operation failed because of storage issues. For example, the device @@ -192,7 +192,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_STORAGE = 5; + public static final int STATUS_FAILURE_STORAGE = 6; /** * The operation failed because it is fundamentally incompatible with this @@ -202,7 +202,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_INCOMPATIBLE = 6; + public static final int STATUS_FAILURE_INCOMPATIBLE = 7; private final Context mContext; private final PackageManager mPm; @@ -584,9 +584,12 @@ public class PackageInstaller { * This returns all names which have been previously written through * {@link #openWrite(String, long, long)} as part of this session. */ - public @NonNull String[] getNames() { + public @NonNull String[] getNames() throws IOException { try { return mSession.getNames(); + } catch (RuntimeException e) { + ExceptionUtils.maybeUnwrapIOException(e); + throw e; } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 68b91cb7dfdfe..4cdafe11ce105 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -618,6 +618,16 @@ public class StorageManager { private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES; private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES; + /** + * Return the number of available bytes until the given path is considered + * running low on storage. + * + * @hide + */ + public long getStorageBytesUntilLow(File path) { + return path.getUsableSpace() - getStorageFullBytes(path); + } + /** * Return the number of available bytes at which the given path is * considered running low on storage. diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 7a3ffdfce1333..81ea191462f01 100644 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -25,10 +25,7 @@ interface IMediaContainerService { boolean isExternal, boolean isForwardLocked, String abiOverride); int copyPackage(String packagePath, in IParcelFileDescriptorFactory target); - PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, long threshold, - String abiOverride); - boolean checkInternalFreeStorage(String packagePath, boolean isForwardLocked, long threshold); - boolean checkExternalFreeStorage(String packagePath, boolean isForwardLocked, String abiOverride); + PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, String abiOverride); ObbInfo getObbInfo(String filename); long calculateDirectorySize(String directory); /** Return file system stats: [0] is total bytes, [1] is available bytes */ diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index eff6684ed8a70..fd96f643e4629 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -16,14 +16,21 @@ package com.android.internal.content; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Environment; import android.os.FileUtils; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.storage.IMountService; +import android.os.storage.StorageManager; import android.os.storage.StorageResultCode; import android.util.Log; +import libcore.io.IoUtils; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -33,8 +40,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import libcore.io.IoUtils; - /** * Constants used internally between the PackageManager * and media container service transports. @@ -298,4 +303,78 @@ public class PackageHelper { } return false; } + + /** + * Given a requested {@link PackageInfo#installLocation} and calculated + * install size, pick the actual location to install the app. + */ + public static int resolveInstallLocation(Context context, int installLocation, long sizeBytes, + int installFlags) { + final int prefer; + final boolean checkBoth; + if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = false; + } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { + prefer = RECOMMEND_INSTALL_EXTERNAL; + checkBoth = false; + } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = false; + } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { + prefer = RECOMMEND_INSTALL_EXTERNAL; + checkBoth = true; + } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = true; + } else { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = false; + } + + final boolean emulated = Environment.isExternalStorageEmulated(); + final StorageManager storage = StorageManager.from(context); + + boolean fitsOnInternal = false; + if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) { + fitsOnInternal = (sizeBytes + <= storage.getStorageBytesUntilLow(Environment.getDataDirectory())); + } + + boolean fitsOnExternal = false; + if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) { + fitsOnExternal = (sizeBytes + <= storage.getStorageBytesUntilLow(Environment.getExternalStorageDirectory())); + } + + if (prefer == RECOMMEND_INSTALL_INTERNAL) { + if (fitsOnInternal) { + return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + } + } else if (!emulated && prefer == RECOMMEND_INSTALL_EXTERNAL) { + if (fitsOnExternal) { + return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; + } + } + + if (checkBoth) { + if (fitsOnInternal) { + return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + } else if (!emulated && fitsOnExternal) { + return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; + } + } + + /* + * If they requested to be on the external media by default, return that + * the media was unavailable. Otherwise, indicate there was insufficient + * storage space available. + */ + if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) + && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; + } else { + return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; + } + } } diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 20a621bd80841..4c225c1d55417 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -16,11 +16,13 @@ package com.android.defcontainer; +import static android.net.TrafficStats.MB_IN_BYTES; + import android.app.IntentService; +import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageCleanItem; -import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -37,8 +39,6 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StatFs; -import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.system.StructStatVfs; @@ -50,13 +50,11 @@ import com.android.internal.content.PackageHelper; import com.android.internal.os.IParcelFileDescriptorFactory; import com.android.internal.util.ArrayUtils; -import dalvik.system.VMRuntime; import libcore.io.IoUtils; import libcore.io.Streams; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -155,10 +153,12 @@ public class DefaultContainerService extends IntentService { * containing one or more APKs. */ @Override - public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags, - long threshold, String abiOverride) { - PackageInfoLite ret = new PackageInfoLite(); + public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, + String abiOverride) { + final Context context = DefaultContainerService.this; + final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; + PackageInfoLite ret = new PackageInfoLite(); if (packagePath == null) { Slog.i(TAG, "Invalid package file " + packagePath); ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; @@ -167,10 +167,12 @@ public class DefaultContainerService extends IntentService { final File packageFile = new File(packagePath); final PackageParser.PackageLite pkg; + final long sizeBytes; try { pkg = PackageParser.parsePackageLite(packageFile, 0); - } catch (PackageParserException e) { - Slog.w(TAG, "Failed to parse package at " + packagePath); + sizeBytes = calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); + } catch (PackageParserException | IOException e) { + Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e); if (!packageFile.exists()) { ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; @@ -185,55 +187,13 @@ public class DefaultContainerService extends IntentService { ret.versionCode = pkg.versionCode; ret.installLocation = pkg.installLocation; ret.verifiers = pkg.verifiers; - ret.recommendedInstallLocation = recommendAppInstallLocation(pkg, flags, threshold, - abiOverride); + ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, + pkg.installLocation, sizeBytes, flags); ret.multiArch = pkg.multiArch; return ret; } - /** - * Determine if package will fit on internal storage. - * - * @param packagePath absolute path to the package to be copied. Can be - * a single monolithic APK file or a cluster directory - * containing one or more APKs. - */ - @Override - public boolean checkInternalFreeStorage(String packagePath, boolean isForwardLocked, - long threshold) throws RemoteException { - final File packageFile = new File(packagePath); - final PackageParser.PackageLite pkg; - try { - pkg = PackageParser.parsePackageLite(packageFile, 0); - return isUnderInternalThreshold(pkg, isForwardLocked, threshold); - } catch (PackageParserException | IOException e) { - Slog.w(TAG, "Failed to parse package at " + packagePath); - return false; - } - } - - /** - * Determine if package will fit on external storage. - * - * @param packagePath absolute path to the package to be copied. Can be - * a single monolithic APK file or a cluster directory - * containing one or more APKs. - */ - @Override - public boolean checkExternalFreeStorage(String packagePath, boolean isForwardLocked, - String abiOverride) throws RemoteException { - final File packageFile = new File(packagePath); - final PackageParser.PackageLite pkg; - try { - pkg = PackageParser.parsePackageLite(packageFile, 0); - return isUnderExternalThreshold(pkg, isForwardLocked, abiOverride); - } catch (PackageParserException | IOException e) { - Slog.w(TAG, "Failed to parse package at " + packagePath); - return false; - } - } - @Override public ObbInfo getObbInfo(String filename) { try { @@ -295,13 +255,10 @@ public class DefaultContainerService extends IntentService { final PackageParser.PackageLite pkg; try { pkg = PackageParser.parsePackageLite(packageFile, 0); - return calculateContainerSize(pkg, isForwardLocked, abiOverride) * 1024 * 1024; + return calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); } catch (PackageParserException | IOException e) { - /* - * Okay, something failed, so let's just estimate it to be 2x - * the file size. Note this will be 0 if the file doesn't exist. - */ - return packageFile.length() * 2; + Slog.w(TAG, "Failed to calculate installed size: " + e); + return Long.MAX_VALUE; } } }; @@ -381,10 +338,12 @@ public class DefaultContainerService extends IntentService { return null; } - // Calculate size of container needed to hold base APK. + // Calculate size of container needed to hold base APK. Round up to + // nearest MB, and tack on an extra MB for filesystem overhead. final int sizeMb; try { - sizeMb = calculateContainerSize(pkg, handle, isForwardLocked, abis); + final long sizeBytes = calculateInstalledSizeInner(pkg, handle, isForwardLocked, abis); + sizeMb = ((int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES)) + 1; } catch (IOException e) { Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath()); return null; @@ -523,124 +482,23 @@ public class DefaultContainerService extends IntentService { } } - private static final int PREFER_INTERNAL = 1; - private static final int PREFER_EXTERNAL = 2; - - private int recommendAppInstallLocation(PackageLite pkg, int flags, long threshold, - String abiOverride) { - int prefer; - boolean checkBoth = false; - - final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; - - check_inner : { - /* - * Explicit install flags should override the manifest settings. - */ - if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { - prefer = PREFER_INTERNAL; - break check_inner; - } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { - prefer = PREFER_EXTERNAL; - break check_inner; - } - - /* No install flags. Check for manifest option. */ - if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { - prefer = PREFER_INTERNAL; - break check_inner; - } else if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { - prefer = PREFER_EXTERNAL; - checkBoth = true; - break check_inner; - } else if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { - // We default to preferring internal storage. - prefer = PREFER_INTERNAL; - checkBoth = true; - break check_inner; - } - - // Pick user preference - int installPreference = Settings.Global.getInt(getApplicationContext() - .getContentResolver(), - Settings.Global.DEFAULT_INSTALL_LOCATION, - PackageHelper.APP_INSTALL_AUTO); - if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { - prefer = PREFER_INTERNAL; - break check_inner; - } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { - prefer = PREFER_EXTERNAL; - break check_inner; - } - - /* - * Fall back to default policy of internal-only if nothing else is - * specified. - */ - prefer = PREFER_INTERNAL; - } - - final boolean emulated = Environment.isExternalStorageEmulated(); - - boolean fitsOnInternal = false; - if (checkBoth || prefer == PREFER_INTERNAL) { - try { - fitsOnInternal = isUnderInternalThreshold(pkg, isForwardLocked, threshold); - } catch (IOException e) { - return PackageHelper.RECOMMEND_FAILED_INVALID_URI; - } - } - - boolean fitsOnSd = false; - if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { - try { - fitsOnSd = isUnderExternalThreshold(pkg, isForwardLocked, abiOverride); - } catch (IOException e) { - return PackageHelper.RECOMMEND_FAILED_INVALID_URI; - } - } - - if (prefer == PREFER_INTERNAL) { - if (fitsOnInternal) { - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; - } - } else if (!emulated && prefer == PREFER_EXTERNAL) { - if (fitsOnSd) { - return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; - } - } - - if (checkBoth) { - if (fitsOnInternal) { - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; - } else if (!emulated && fitsOnSd) { - return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; - } - } - - /* - * If they requested to be on the external media by default, return that - * the media was unavailable. Otherwise, indicate there was insufficient - * storage space available. - */ - if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL) - && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; - } else { - return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; + private long calculateInstalledSizeInner(PackageLite pkg, boolean isForwardLocked, + String abiOverride) throws IOException { + NativeLibraryHelper.Handle handle = null; + try { + handle = NativeLibraryHelper.Handle.create(pkg); + return calculateInstalledSizeInner(pkg, handle, isForwardLocked, + calculateAbiList(handle, abiOverride, pkg.multiArch)); + } finally { + IoUtils.closeQuietly(handle); } } - /** - * Measure a file to see if it fits within the free space threshold. - * - * @param threshold byte threshold to compare against - * @return true if file fits under threshold - * @throws FileNotFoundException when APK does not exist - */ - private boolean isUnderInternalThreshold(PackageLite pkg, boolean isForwardLocked, - long threshold) throws IOException { + private long calculateInstalledSizeInner(PackageLite pkg, NativeLibraryHelper.Handle handle, + boolean isForwardLocked, String[] abis) throws IOException { long sizeBytes = 0; + + // Include raw APKs, and possibly unpacked resources for (String codePath : pkg.getAllCodePaths()) { sizeBytes += new File(codePath).length(); @@ -649,47 +507,12 @@ public class DefaultContainerService extends IntentService { } } - final StatFs stat = new StatFs(Environment.getDataDirectory().getPath()); - final long availBytes = stat.getAvailableBytes(); - return (availBytes - sizeBytes) > threshold; - } - - /** - * Measure a file to see if it fits in the external free space. - * - * @return true if file fits - * @throws IOException when file does not exist - */ - private boolean isUnderExternalThreshold(PackageLite pkg, boolean isForwardLocked, - String abiOverride) throws IOException { - if (Environment.isExternalStorageEmulated()) { - return false; + // Include all relevant native code + if (!ArrayUtils.isEmpty(abis)) { + sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(handle, abis); } - final int sizeMb = calculateContainerSize(pkg, isForwardLocked, abiOverride); - - final int availSdMb; - if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); - final int blocksToMb = (1 << 20) / sdStats.getBlockSize(); - availSdMb = sdStats.getAvailableBlocks() * blocksToMb; - } else { - availSdMb = -1; - } - - return availSdMb > sizeMb; - } - - private int calculateContainerSize(PackageLite pkg, boolean isForwardLocked, String abiOverride) - throws IOException { - NativeLibraryHelper.Handle handle = null; - try { - handle = NativeLibraryHelper.Handle.create(pkg); - return calculateContainerSize(pkg, handle, isForwardLocked, - calculateAbiList(handle, abiOverride, pkg.multiArch)); - } finally { - IoUtils.closeQuietly(handle); - } + return sizeBytes; } private String[] calculateAbiList(NativeLibraryHelper.Handle handle, String abiOverride, @@ -731,43 +554,4 @@ public class DefaultContainerService extends IntentService { return null; } - - /** - * Calculate the container size for a package. - * - * @return size in megabytes (2^20 bytes) - * @throws IOException when there is a problem reading the file - */ - private int calculateContainerSize(PackageLite pkg, NativeLibraryHelper.Handle handle, - boolean isForwardLocked, String[] abis) throws IOException { - // Calculate size of container needed to hold APKs. - long sizeBytes = 0; - for (String codePath : pkg.getAllCodePaths()) { - sizeBytes += new File(codePath).length(); - - if (isForwardLocked) { - sizeBytes += PackageHelper.extractPublicFiles(codePath, null); - } - } - - // Check all the native files that need to be copied and add that to the - // container size. - if (abis != null) { - sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(handle, abis); - } - - int sizeMb = (int) (sizeBytes >> 20); - if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { - sizeMb++; - } - - /* - * Add buffer size because we don't have a good way to determine the - * real FAT size. Your FAT size varies with how many directory entries - * you need, how big the whole filesystem is, and other such headaches. - */ - sizeMb++; - - return sizeMb; - } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index dca8ad421ba25..03935189c7492 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_ALL_USERS; import static android.content.pm.PackageManager.INSTALL_FROM_ADB; import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING; +import static android.net.TrafficStats.MB_IN_BYTES; import static com.android.internal.util.XmlUtils.readBitmapAttribute; import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; @@ -42,6 +43,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; @@ -65,6 +67,7 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; @@ -75,13 +78,14 @@ import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageHelper; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.server.IoThread; -import com.android.server.pm.PackageInstallerSession.Snapshot; import com.google.android.collect.Sets; import libcore.io.IoUtils; @@ -108,6 +112,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // TODO: remove outstanding sessions when installer package goes away // TODO: notify listeners in other users when package has been installed there + // TODO: purge expired sessions periodically in addition to at reboot /** XML constants used in {@link #mSessionsFile} */ private static final String TAG_SESSIONS = "sessions"; @@ -117,6 +122,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName"; private static final String ATTR_CREATED_MILLIS = "createdMillis"; private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; + private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; @@ -139,6 +145,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; private final AppOpsManager mAppOps; + private final StorageManager mStorage; private final File mStagingDir; private final HandlerThread mInstallThread; @@ -165,10 +172,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @GuardedBy("mSessions") private final SparseArray mHistoricalSessions = new SparseArray<>(); + /** Sessions allocated to legacy users */ + @GuardedBy("mSessions") + private final SparseBooleanArray mLegacySessions = new SparseBooleanArray(); + private static final FilenameFilter sStageFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { - return name.startsWith("vmdl") && name.endsWith(".tmp"); + return isStageName(name); } }; @@ -176,6 +187,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mContext = context; mPm = pm; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mStorage = StorageManager.from(mContext); mStagingDir = stagingDir; @@ -190,13 +202,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { synchronized (mSessions) { readSessionsLocked(); - // Clean up orphaned staging directories - final ArraySet stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); + final ArraySet unclaimed = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); + + // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - stages.remove(session.sessionStageDir); + unclaimed.remove(session.internalStageDir); } - for (File stage : stages) { + + // Clean up orphaned staging directories + for (File stage : unclaimed) { Slog.w(TAG, "Deleting orphan stage " + stage); if (stage.isDirectory()) { FileUtils.deleteContents(stage); @@ -206,22 +221,64 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - public static boolean isStageFile(File file) { - return sStageFilter.accept(null, file.getName()); + public void onSecureContainersAvailable() { + synchronized (mSessions) { + final ArraySet unclaimed = new ArraySet<>(); + for (String cid : PackageHelper.getSecureContainerList()) { + if (isStageName(cid)) { + unclaimed.add(cid); + } + } + + // Ignore stages claimed by active sessions + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + final String cid = session.externalStageCid; + + if (unclaimed.remove(cid)) { + // Claimed by active session, mount it + PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), + Process.SYSTEM_UID); + } + } + + // Clean up orphaned staging containers + for (String cid : unclaimed) { + Slog.w(TAG, "Deleting orphan container " + cid); + PackageHelper.destroySdDir(cid); + } + } + } + + public static boolean isStageName(String name) { + final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); + final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); + final boolean isLegacyContainer = name.startsWith("smdl2tmp"); + return isFile || isContainer || isLegacyContainer; } @Deprecated - public File allocateSessionDir() throws IOException { + public File allocateInternalStageDirLegacy() throws IOException { synchronized (mSessions) { try { final int sessionId = allocateSessionIdLocked(); - return prepareSessionStageDir(sessionId); + mLegacySessions.put(sessionId, true); + return prepareInternalStageDir(sessionId); } catch (IllegalStateException e) { throw new IOException(e); } } } + @Deprecated + public String allocateExternalStageCidLegacy() { + synchronized (mSessions) { + final int sessionId = allocateSessionIdLocked(); + mLegacySessions.put(sessionId, true); + return "smdl" + sessionId + ".tmp"; + } + } + private void readSessionsLocked() { if (LOGD) Slog.v(TAG, "readSessionsLocked()"); @@ -246,9 +303,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Slog.w(TAG, "Abandoning old session first created at " + session.createdMillis); valid = false; - } else if (!session.sessionStageDir.exists()) { - Slog.w(TAG, "Abandoning session with missing stage " - + session.sessionStageDir); + } else if (session.internalStageDir != null + && !session.internalStageDir.exists()) { + Slog.w(TAG, "Abandoning internal session with missing stage " + + session.internalStageDir); valid = false; } else { valid = true; @@ -281,7 +339,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final int userId = readIntAttribute(in, ATTR_USER_ID); final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME); final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS); - final File sessionStageDir = new File(readStringAttribute(in, ATTR_SESSION_STAGE_DIR)); + final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); + final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; + final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final SessionParams params = new SessionParams( @@ -299,7 +359,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, sessionStageDir, sealed); + createdMillis, stageDir, stageCid, sealed); } private void writeSessionsLocked() { @@ -332,7 +392,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session) throws IOException { final SessionParams params = session.params; - final Snapshot snapshot = session.snapshot(); out.startTag(null, TAG_SESSION); @@ -341,9 +400,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, session.installerPackageName); writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis); - writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, - session.sessionStageDir.getAbsolutePath()); - writeBooleanAttribute(out, ATTR_SEALED, snapshot.sealed); + if (session.internalStageDir != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, + session.internalStageDir.getAbsolutePath()); + } + if (session.externalStageCid != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.externalStageCid); + } + writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); writeIntAttribute(out, ATTR_MODE, params.mode); writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags); @@ -372,6 +436,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public int createSession(SessionParams params, String installerPackageName, int userId) { + try { + return createSessionInternal(params, installerPackageName, userId); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } + } + + private int createSessionInternal(SessionParams params, String installerPackageName, int userId) + throws IOException { final int callingUid = Binder.getCallingUid(); mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession"); @@ -393,14 +466,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags |= INSTALL_REPLACE_EXISTING; } - switch (params.mode) { - case SessionParams.MODE_FULL_INSTALL: - case SessionParams.MODE_INHERIT_EXISTING: - break; - default: - throw new IllegalArgumentException("Params must have valid mode set"); - } - // Defensively resize giant app icons if (params.appIcon != null) { final ActivityManager am = (ActivityManager) mContext.getSystemService( @@ -413,13 +478,41 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - // Sanity check that install could fit - if (params.sizeBytes > 0) { - try { - mPm.freeStorage(params.sizeBytes); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + // Figure out where we're going to be staging session data + final boolean stageInternal; + + if (params.mode == SessionParams.MODE_FULL_INSTALL) { + // Brand new install, use best resolved location. This also verifies + // that target has enough free space for the install. + final int resolved = PackageHelper.resolveInstallLocation(mContext, + params.installLocation, params.sizeBytes, params.installFlags); + if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { + stageInternal = true; + } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { + stageInternal = false; + } else { + throw new IOException("No storage with enough free space; res=" + resolved); } + + } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { + // We always stage inheriting sessions on internal storage first, + // since we don't want to grow containers until we're sure that + // everything looks legit. + stageInternal = true; + checkInternalStorage(params.sizeBytes); + + // If we have a good hunch we'll end up on external storage, verify + // free space there too. + final ApplicationInfo info = mPm.getApplicationInfo(params.appPackageName, 0, + userId); + if (info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + checkExternalStorage(params.sizeBytes); + + throw new UnsupportedOperationException("TODO: finish fleshing out ASEC support"); + } + + } else { + throw new IllegalArgumentException("Invalid install mode: " + params.mode); } final int sessionId; @@ -437,14 +530,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub { "Too many historical sessions for UID " + callingUid); } + final long createdMillis = System.currentTimeMillis(); sessionId = allocateSessionIdLocked(); - final long createdMillis = System.currentTimeMillis(); - final File sessionStageDir = prepareSessionStageDir(sessionId); + // We're staging to exactly one location + File stageDir = null; + String stageCid = null; + if (stageInternal) { + stageDir = prepareInternalStageDir(sessionId); + } else { + stageCid = prepareExternalStageCid(sessionId, params.sizeBytes); + } session = new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, sessionStageDir, false); + createdMillis, stageDir, stageCid, false); mSessions.put(sessionId, session); } @@ -453,6 +553,29 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return sessionId; } + private void checkInternalStorage(long sizeBytes) throws IOException { + if (sizeBytes <= 0) return; + + final File target = Environment.getDataDirectory(); + final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); + + mPm.freeStorage(targetBytes); + if (target.getUsableSpace() < targetBytes) { + throw new IOException("Not enough internal space to write " + sizeBytes + " bytes"); + } + } + + private void checkExternalStorage(long sizeBytes) throws IOException { + if (sizeBytes <= 0) return; + + final File target = Environment.getExternalStorageDirectory(); + final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); + + if (target.getUsableSpace() < targetBytes) { + throw new IOException("Not enough external space to write " + sizeBytes + " bytes"); + } + } + @Override public IPackageInstallerSession openSession(int sessionId) { synchronized (mSessions) { @@ -463,9 +586,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (!isCallingUidOwner(session)) { throw new SecurityException("Caller has no access to session " + sessionId); } - if (session.openCount.getAndIncrement() == 0) { - mCallbacks.notifySessionOpened(sessionId, session.userId); - } + session.open(); return session; } } @@ -475,7 +596,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { int sessionId; do { sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; - if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null) { + if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null + && !mLegacySessions.get(sessionId, false)) { return sessionId; } } while (n++ < 32); @@ -483,11 +605,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } - private File prepareSessionStageDir(int sessionId) { + private File prepareInternalStageDir(int sessionId) throws IOException { final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); if (file.exists()) { - throw new IllegalStateException("Session dir already exists: " + file); + throw new IOException("Session dir already exists: " + file); } try { @@ -495,16 +617,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Os.chmod(file.getAbsolutePath(), 0755); } catch (ErrnoException e) { // This purposefully throws if directory already exists - throw new IllegalStateException("Failed to prepare session dir", e); + throw new IOException("Failed to prepare session dir", e); } if (!SELinux.restorecon(file)) { - throw new IllegalStateException("Failed to restorecon session dir"); + throw new IOException("Failed to restorecon session dir"); } return file; } + private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException { + if (sizeBytes <= 0) { + throw new IOException("Session must provide valid size for ASEC"); + } + + final String cid = "smdl" + sessionId + ".tmp"; + + // Round up to nearest MB, plus another MB for filesystem overhead + final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; + + if (PackageHelper.createSdDir(sizeMb, cid, PackageManagerService.getEncryptKey(), + Process.SYSTEM_UID, true) == null) { + throw new IOException("Failed to create ASEC"); + } + + return cid; + } + @Override public SessionInfo getSessionInfo(int sessionId) { synchronized (mSessions) { @@ -643,7 +783,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { public void onUserActionRequired(Intent intent) { final Intent fillIn = new Intent(); fillIn.putExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_USER_ACTION_REQUIRED); + PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { mTarget.sendIntent(mContext, 0, fillIn, null, null); @@ -679,7 +819,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { public void onUserActionRequired(Intent intent) { final Intent fillIn = new Intent(); fillIn.putExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_USER_ACTION_REQUIRED); + PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { mTarget.sendIntent(mContext, 0, fillIn, null, null); @@ -817,6 +957,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } pw.println(); pw.decreaseIndent(); + + pw.println("Legacy install sessions:"); + pw.increaseIndent(); + pw.println(mLegacySessions.toString()); + pw.decreaseIndent(); } } @@ -825,6 +970,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); } + public void onSessionOpened(PackageInstallerSession session) { + mCallbacks.notifySessionOpened(session.sessionId, session.userId); + } + public void onSessionClosed(PackageInstallerSession session) { mCallbacks.notifySessionClosed(session.sessionId, session.userId); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5ef24f2ab6ac4..0616460c9da2b 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; +import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; @@ -58,6 +59,7 @@ import android.util.MathUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -79,7 +81,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE - // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL // TODO: treat INHERIT_EXISTING as installExistingPackage() @@ -93,12 +94,24 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String installerPackageName; final SessionParams params; final long createdMillis; - final File sessionStageDir; + + /** Internal location where staged data is written. */ + final File internalStageDir; + /** External container where staged data is written. */ + final String externalStageCid; + + /** + * When a {@link SessionParams#MODE_INHERIT_EXISTING} session is installed + * into an ASEC, this is the container where the stage is combined with the + * existing install. + */ + // TODO: persist this cid once we start splicing + String combinedCid; /** Note that UID is not persisted; it's always derived at runtime. */ final int installerUid; - AtomicInteger openCount = new AtomicInteger(); + private final AtomicInteger mOpenCount = new AtomicInteger(); private final Object mLock = new Object(); @@ -119,6 +132,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private int mFinalStatus; private String mFinalMessage; + @GuardedBy("mLock") + private File mResolvedStageDir; + /** * Path to the resolved base APK for this session, which may point at an APK * inside the session (when the session defines the base), or it may point @@ -165,7 +181,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, SessionParams params, long createdMillis, - File sessionStageDir, boolean sealed) { + File internalStageDir, String externalStageCid, boolean sealed) { mCallback = callback; mContext = context; mPm = pm; @@ -176,7 +192,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.installerPackageName = installerPackageName; this.params = params; this.createdMillis = createdMillis; - this.sessionStageDir = sessionStageDir; + this.internalStageDir = internalStageDir; + this.externalStageCid = externalStageCid; mSealed = sealed; @@ -195,23 +212,29 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public SessionInfo generateInfo() { final SessionInfo info = new SessionInfo(); + synchronized (mLock) { + info.sessionId = sessionId; + info.installerPackageName = installerPackageName; + info.resolvedBaseCodePath = mResolvedBaseCodePath; + info.progress = mProgress; + info.sealed = mSealed; + info.open = mOpenCount.get() > 0; - info.sessionId = sessionId; - info.installerPackageName = installerPackageName; - info.resolvedBaseCodePath = mResolvedBaseCodePath; - info.progress = mProgress; - info.sealed = mSealed; - info.open = openCount.get() > 0; - - info.mode = params.mode; - info.sizeBytes = params.sizeBytes; - info.appPackageName = params.appPackageName; - info.appIcon = params.appIcon; - info.appLabel = params.appLabel; - + info.mode = params.mode; + info.sizeBytes = params.sizeBytes; + info.appPackageName = params.appPackageName; + info.appIcon = params.appIcon; + info.appLabel = params.appLabel; + } return info; } + public boolean isSealed() { + synchronized (mLock) { + return mSealed; + } + } + private void assertNotSealed(String cookie) { synchronized (mLock) { if (mSealed) { @@ -220,6 +243,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Resolve the actual location where staged data should be written. This + * might point at an ASEC mount point, which is why we delay path resolution + * until someone actively works with the session. + */ + private File getStageDir() throws IOException { + synchronized (mLock) { + if (mResolvedStageDir == null) { + if (internalStageDir != null) { + mResolvedStageDir = internalStageDir; + } else { + final String path = PackageHelper.getSdDir(externalStageCid); + if (path != null) { + mResolvedStageDir = new File(path); + } else { + throw new IOException( + "Failed to resolve container path for " + externalStageCid); + } + } + } + return mResolvedStageDir; + } + } + @Override public void setClientProgress(float progress) { synchronized (mLock) { @@ -253,7 +300,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public String[] getNames() { assertNotSealed("getNames"); - return sessionStageDir.list(); + try { + return getStageDir().list(); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } } @Override @@ -267,8 +318,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) throws IOException { - // TODO: relay over to DCS when installing to ASEC - // Quick sanity check of state, and allocate a pipe for ourselves. We // then do heavy disk allocation outside the lock, but this open pipe // will block any attempted install transitions. @@ -285,7 +334,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(sessionStageDir, name); + final File target = new File(getStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_CREAT | O_WRONLY, 0644); @@ -331,7 +380,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(sessionStageDir, name); + final File target = new File(getStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); return new ParcelFileDescriptor(targetFd); @@ -369,10 +418,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // beyond this point we may have hardlinks to the valid install } + final File stageDir; + try { + stageDir = getStageDir(); + } catch (IOException e) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to resolve stage dir", e); + } + // Verify that stage looks sane with respect to existing application. // This currently only ensures packageName, versionCode, and certificate // consistency. - validateInstallLocked(); + validateInstallLocked(stageDir); Preconditions.checkNotNull(mPackageName); Preconditions.checkNotNull(mSignatures); @@ -394,7 +451,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Inherit any packages and native libraries from existing install that // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { - spliceExistingFilesIntoStage(); + // TODO: implement splicing into existing ASEC + spliceExistingFilesIntoStage(stageDir); } // TODO: surface more granular state from dexopt @@ -418,7 +476,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } }; - mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, + // TODO: send ASEC cid if that's where we staged things + mPm.installStage(mPackageName, this.internalStageDir, null, localObserver, params, installerPackageName, installerUid, new UserHandle(userId)); } @@ -428,13 +487,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { *

* Renames package files in stage to match split names defined inside. */ - private void validateInstallLocked() throws PackageManagerException { + private void validateInstallLocked(File stageDir) throws PackageManagerException { mPackageName = null; mVersionCode = -1; mSignatures = null; mResolvedBaseCodePath = null; - final File[] files = sessionStageDir.listFiles(); + final File[] files = stageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); } @@ -480,7 +539,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Invalid filename: " + targetName); } - final File targetFile = new File(sessionStageDir, targetName); + final File targetFile = new File(stageDir, targetName); if (!file.equals(targetFile)) { file.renameTo(targetFile); } @@ -550,7 +609,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Application is already installed; splice existing files that haven't been * overridden into our stage. */ - private void spliceExistingFilesIntoStage() throws PackageManagerException { + private void spliceExistingFilesIntoStage(File stageDir) throws PackageManagerException { final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); int n = 0; @@ -559,7 +618,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { for (File oldFile : oldFiles) { if (!PackageParser.isApkFile(oldFile)) continue; - final File newFile = new File(sessionStageDir, oldFile.getName()); + final File newFile = new File(stageDir, oldFile.getName()); try { Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath()); n++; @@ -588,9 +647,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + public void open() { + if (mOpenCount.getAndIncrement() == 0) { + mCallback.onSessionOpened(this); + } + } + @Override public void close() { - if (openCount.decrementAndGet() == 0) { + if (mOpenCount.decrementAndGet() == 0) { mCallback.onSessionClosed(this); } } @@ -621,11 +686,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSealed = true; mDestroyed = true; } - FileUtils.deleteContents(sessionStageDir); - sessionStageDir.delete(); + if (internalStageDir != null) { + FileUtils.deleteContents(internalStageDir); + internalStageDir.delete(); + } + if (externalStageCid != null) { + PackageHelper.destroySdDir(externalStageCid); + } } void dump(IndentingPrintWriter pw) { + synchronized (mLock) { + dumpLocked(pw); + } + } + + private void dumpLocked(IndentingPrintWriter pw) { pw.println("Session " + sessionId + ":"); pw.increaseIndent(); @@ -633,7 +709,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("installerPackageName", installerPackageName); pw.printPair("installerUid", installerUid); pw.printPair("createdMillis", createdMillis); - pw.printPair("sessionStageDir", sessionStageDir); + pw.printPair("internalStageDir", internalStageDir); + pw.printPair("externalStageCid", externalStageCid); pw.println(); params.dump(pw); @@ -650,18 +727,4 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.decreaseIndent(); } - - Snapshot snapshot() { - return new Snapshot(this); - } - - static class Snapshot { - final float clientProgress; - final boolean sealed; - - public Snapshot(PackageInstallerSession session) { - clientProgress = session.mClientProgress; - sealed = session.mSealed; - } - } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b62c304dd3525..51559aa717754 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -140,6 +140,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Environment.UserEnvironment; +import android.os.storage.StorageManager; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -211,6 +212,7 @@ import dalvik.system.StaleDexCacheError; import dalvik.system.VMRuntime; import libcore.io.IoUtils; +import libcore.util.EmptyArray; /** * Keep track of all those .apks everywhere. @@ -311,8 +313,6 @@ public class PackageManagerService extends IPackageManager.Stub { private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; - static final String mTempContainerPrefix = "smdl2tmp"; - private static String sPreferredInstructionSet; final ServiceThread mHandlerThread; @@ -4101,21 +4101,24 @@ public class PackageManagerService extends IPackageManager.Stub { for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) - && !PackageInstallerService.isStageFile(file); + && !PackageInstallerService.isStageName(file.getName()); if (!isPackage) { - // Ignore entries which are not apk's + // Ignore entries which are not packages continue; } try { - scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null); + scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, + scanMode, currentTime, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); - // Don't mess around with apps in system partition. + // Delete invalid userdata apps if ((flags & PackageParser.PARSE_IS_SYSTEM) == 0 && e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { - // Delete the apk - Slog.w(TAG, "Cleaning up failed install of " + file); + Slog.w(TAG, "Deleting invalid package at " + file); + if (file.isDirectory()) { + FileUtils.deleteContents(file); + } file.delete(); } } @@ -7839,19 +7842,19 @@ public class PackageManagerService extends IPackageManager.Stub { verificationParams.setInstallerUid(uid); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(originFile, false, observer, filteredFlags, + msg.obj = new InstallParams(originFile, null, false, observer, filteredFlags, installerPackageName, verificationParams, user, packageAbiOverride); mHandler.sendMessage(msg); } - void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer, - PackageInstaller.SessionParams params, String installerPackageName, int installerUid, - UserHandle user) { + void installStage(String packageName, File stagedDir, String stagedCid, + IPackageInstallObserver2 observer, PackageInstaller.SessionParams params, + String installerPackageName, int installerUid, UserHandle user) { final VerificationParams verifParams = new VerificationParams(null, params.originatingUri, params.referrerUri, installerUid, null); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(stageDir, true, observer, params.installFlags, + msg.obj = new InstallParams(stagedDir, stagedCid, true, observer, params.installFlags, installerPackageName, verifParams, user, params.abiOverride); mHandler.sendMessage(msg); } @@ -8551,10 +8554,12 @@ public class PackageManagerService extends IPackageManager.Stub { * file, or a cluster directory. This location may be untrusted. */ final File originFile; + final String originCid; /** - * Flag indicating that {@link #originFile} has already been staged, - * meaning downstream users don't need to defensively copy the contents. + * Flag indicating that {@link #originFile} or {@link #originCid} has + * already been staged, meaning downstream users don't need to + * defensively copy the contents. */ boolean originStaged; @@ -8567,11 +8572,12 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageAbiOverride; boolean multiArch; - InstallParams(File originFile, boolean originStaged, IPackageInstallObserver2 observer, - int flags, String installerPackageName, VerificationParams verificationParams, - UserHandle user, String packageAbiOverride) { + InstallParams(File originFile, String originCid, boolean originStaged, + IPackageInstallObserver2 observer, int flags, String installerPackageName, + VerificationParams verificationParams, UserHandle user, String packageAbiOverride) { super(user); - this.originFile = Preconditions.checkNotNull(originFile); + this.originFile = originFile; + this.originCid = originCid; this.originStaged = originStaged; this.observer = observer; this.flags = flags; @@ -8582,9 +8588,8 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public String toString() { - return "InstallParams{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + originFile + "}"; + return "InstallParams{" + Integer.toHexString(System.identityHashCode(this)) + + " file=" + originFile + " cid=" + originCid + "}"; } public ManifestDigest getManifestDigest() { @@ -8653,15 +8658,6 @@ public class PackageManagerService extends IPackageManager.Stub { return pkgLite.recommendedInstallLocation; } - private long getMemoryLowThreshold() { - final DeviceStorageMonitorInternal - dsm = LocalServices.getService(DeviceStorageMonitorInternal.class); - if (dsm == null) { - return 0L; - } - return dsm.getMemoryLowThreshold(); - } - /* * Invoke remote method to get package information and install * location values. Override install location based on default @@ -8679,14 +8675,9 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Conflicting flags specified for installing on both internal and external"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { - final long lowThreshold = getMemoryLowThreshold(); - if (lowThreshold == 0L) { - Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); - } - // Remote call to find out default install location final String originPath = originFile.getAbsolutePath(); - pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, lowThreshold, + pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, packageAbiOverride); // Keep track of whether this package is a multiArch package until // we perform a full scan of it. We need to do this because we might @@ -8700,12 +8691,19 @@ public class PackageManagerService extends IPackageManager.Stub { */ if (pkgLite.recommendedInstallLocation == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { - final long size = mContainerService.calculateInstalledSize( + // TODO: focus freeing disk space on the target device + final StorageManager storage = StorageManager.from(mContext); + final long lowThreshold = storage.getStorageLowBytes( + Environment.getDataDirectory()); + + final long sizeBytes = mContainerService.calculateInstalledSize( originPath, isForwardLocked(), packageAbiOverride); - if (mInstaller.freeCache(size + lowThreshold) >= 0) { + + if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) { pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, - lowThreshold, packageAbiOverride); + packageAbiOverride); } + /* * The cache free must have deleted the file we * downloaded to install. @@ -9235,24 +9233,11 @@ public class PackageManagerService extends IPackageManager.Stub { } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - final long lowThreshold; + final long sizeBytes = imcs.calculateInstalledSize(originFile.getAbsolutePath(), + isFwdLocked(), abiOverride); - final DeviceStorageMonitorInternal - dsm = LocalServices.getService(DeviceStorageMonitorInternal.class); - if (dsm == null) { - Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); - lowThreshold = 0L; - } else { - if (dsm.isMemoryLow()) { - Log.w(TAG, "Memory is reported as being too low; aborting package install"); - return false; - } - - lowThreshold = dsm.getMemoryLowThreshold(); - } - - return imcs.checkInternalFreeStorage(originFile.getAbsolutePath(), isFwdLocked(), - lowThreshold); + final StorageManager storage = StorageManager.from(mContext); + return (sizeBytes <= storage.getStorageBytesUntilLow(Environment.getDataDirectory())); } int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { @@ -9264,7 +9249,7 @@ public class PackageManagerService extends IPackageManager.Stub { resourceFile = originFile; } else { try { - final File tempDir = mInstallerService.allocateSessionDir(); + final File tempDir = mInstallerService.allocateInternalStageDirLegacy(); codeFile = tempDir; resourceFile = tempDir; } catch (IOException e) { @@ -9569,12 +9554,22 @@ public class PackageManagerService extends IPackageManager.Stub { } void createCopyFile() { - cid = getTempContainerId(); + cid = mInstallerService.allocateExternalStageCidLegacy(); } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - return imcs.checkExternalFreeStorage(originFile.getAbsolutePath(), isFwdLocked(), + final long sizeBytes = imcs.calculateInstalledSize(packagePath, isFwdLocked(), abiOverride); + + final File target; + if (isExternal()) { + target = Environment.getExternalStorageDirectory(); + } else { + target = Environment.getDataDirectory(); + } + + final StorageManager storage = StorageManager.from(mContext); + return (sizeBytes <= storage.getStorageBytesUntilLow(target)); } private final boolean isExternal() { @@ -12653,7 +12648,7 @@ public class PackageManagerService extends IPackageManager.Stub { private boolean mMediaMounted = false; - private String getEncryptKey() { + static String getEncryptKey() { try { String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( SD_ENCRYPTION_KEYSTORE_NAME); @@ -12673,30 +12668,6 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.e(TAG, "Failed to retrieve encryption keys with exception: " + ioe); return null; } - - } - - /* package */static String getTempContainerId() { - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - for (final String name : list) { - // Ignore null and non-temporary container entries - if (name == null || !name.startsWith(mTempContainerPrefix)) { - continue; - } - - String subStr = name.substring(mTempContainerPrefix.length()); - try { - int cid = Integer.parseInt(subStr); - if (cid >= tmpIdx) { - tmpIdx = cid + 1; - } - } catch (NumberFormatException e) { - } - } - } - return mTempContainerPrefix + tmpIdx; } /* @@ -12754,31 +12725,27 @@ public class PackageManagerService extends IPackageManager.Stub { */ private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus, boolean externalStorage) { - // Collection of uids - int uidArr[] = null; - // Collection of stale containers - HashSet removeCids = new HashSet(); - // Collection of packages on external media with valid containers. - HashMap processCids = new HashMap(); - // Get list of secure containers. - final String list[] = PackageHelper.getSecureContainerList(); - if (list == null || list.length == 0) { - Log.i(TAG, "No secure containers on sdcard"); + ArrayMap processCids = new ArrayMap<>(); + int[] uidArr = EmptyArray.INT; + + final String[] list = PackageHelper.getSecureContainerList(); + if (ArrayUtils.isEmpty(list)) { + Log.i(TAG, "No secure containers found"); } else { // Process list of secure containers and categorize them // as active or stale based on their package internal state. - int uidList[] = new int[list.length]; - int num = 0; + // reader synchronized (mPackages) { for (String cid : list) { + // Leave stages untouched for now; installer service owns them + if (PackageInstallerService.isStageName(cid)) continue; + if (DEBUG_SD_INSTALL) Log.i(TAG, "Processing container " + cid); String pkgName = getAsecPackageName(cid); if (pkgName == null) { - if (DEBUG_SD_INSTALL) - Log.i(TAG, "Container : " + cid + " stale"); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + " with no package name"); continue; } if (DEBUG_SD_INSTALL) @@ -12786,8 +12753,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps == null) { - Log.i(TAG, "Deleting container with no matching settings " + cid); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + " with no matching settings"); continue; } @@ -12813,35 +12779,25 @@ public class PackageManagerService extends IPackageManager.Stub { processCids.put(args, ps.codePathString); final int uid = ps.appId; if (uid != -1) { - uidList[num++] = uid; + uidArr = ArrayUtils.appendInt(uidArr, uid); } } else { - Log.i(TAG, "Deleting stale container for " + cid); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + ": expected codePath=" + + ps.codePathString); } } } - if (num > 0) { - // Sort uid list - Arrays.sort(uidList, 0, num); - // Throw away duplicates - uidArr = new int[num]; - uidArr[0] = uidList[0]; - int di = 0; - for (int i = 1; i < num; i++) { - if (uidList[i - 1] != uidList[i]) { - uidArr[di++] = uidList[i]; - } - } - } + Arrays.sort(uidArr); } + // Process packages with valid entries. if (isMounted) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); - loadMediaPackages(processCids, uidArr, removeCids); + loadMediaPackages(processCids, uidArr); startCleaningPackages(); + mInstallerService.onSecureContainersAvailable(); } else { if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); @@ -12849,8 +12805,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, - ArrayList pkgList, int uidArr[], IIntentReceiver finishedReceiver) { + private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, + ArrayList pkgList, int uidArr[], IIntentReceiver finishedReceiver) { int size = pkgList.size(); if (size > 0) { // Send broadcasts here @@ -12875,11 +12831,10 @@ public class PackageManagerService extends IPackageManager.Stub { * the cid is added to list of removeCids. We currently don't delete stale * containers. */ - private void loadMediaPackages(HashMap processCids, int uidArr[], - HashSet removeCids) { + private void loadMediaPackages(ArrayMap processCids, int[] uidArr) { ArrayList pkgList = new ArrayList(); Set keys = processCids.keySet(); - boolean doGc = false; + for (AsecInstallArgs args : keys) { String codePath = processCids.get(args); if (DEBUG_SD_INSTALL) @@ -12907,7 +12862,6 @@ public class PackageManagerService extends IPackageManager.Stub { parseFlags |= PackageParser.PARSE_FORWARD_LOCK; } - doGc = true; synchronized (mInstallLock) { PackageParser.Package pkg = null; try { @@ -12937,9 +12891,7 @@ public class PackageManagerService extends IPackageManager.Stub { } finally { if (retCode != PackageManager.INSTALL_SUCCEEDED) { - // Don't destroy container here. Wait till gc clears things - // up. - removeCids.add(args.cid); + Log.w(TAG, "Container " + args.cid + " is stale, retCode=" + retCode); } } } @@ -12974,21 +12926,6 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkgList.size() > 0) { sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null); } - // Force gc to avoid any stale parser references that we might have. - if (doGc) { - Runtime.getRuntime().gc(); - } - // List stale containers and destroy stale temporary containers. - if (removeCids != null) { - for (String cid : removeCids) { - if (cid.startsWith(mTempContainerPrefix)) { - Log.i(TAG, "Destroying stale temporary container " + cid); - PackageHelper.destroySdDir(cid); - } else { - Log.w(TAG, "Container " + cid + " is stale"); - } - } - } } /* @@ -13012,7 +12949,7 @@ public class PackageManagerService extends IPackageManager.Stub { * that we always have to post this message if status has been requested no * matter what. */ - private void unloadMediaPackages(HashMap processCids, int uidArr[], + private void unloadMediaPackages(ArrayMap processCids, int uidArr[], final boolean reportStatus) { if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages");