Add ephemeral installs
* Add a new --ephemeral argument to 'adb install'
* Add plumbing to internally track ephemeralness
* Create new app directory for ephemeral installs
Bug: 25119046
Change-Id: I1d379f5ccd42e9444c9051eef2d025a37bd824fe
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 659dc73..544fd6a 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -458,6 +458,8 @@
params.setSize(Long.parseLong(nextOptionData()));
} else if (opt.equals("--abi")) {
params.abiOverride = checkAbiArgument(nextOptionData());
+ } else if (opt.equals("--ephemeral")) {
+ params.installFlags |= PackageManager.INSTALL_EPHEMERAL;
} else if (opt.equals("--user")) {
userId = Integer.parseInt(nextOptionData());
} else if (opt.equals("--install-location")) {
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 65e5945..545478c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -383,6 +383,13 @@
public static final int FLAG_HARDWARE_ACCELERATED = 1<<29;
/**
+ * Value for {@link #flags}: {@code true} if the application is blocked via restrictions
+ * and for most purposes is considered as not installed.
+ * {@hide}
+ */
+ public static final int FLAG_EPHEMERAL = 1<<30;
+
+ /**
* Value for {@link #flags}: true if code from this application will need to be
* loaded into other applications' processes. On devices that support multiple
* instruction sets, this implies the code might be loaded into a process that's
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aa960a4..8f186e3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -454,6 +454,14 @@
public static final int INSTALL_QUICK = 0x00000800;
/**
+ * Flag parameter for {@link #installPackage} to indicate that this package is
+ * to be installed as a lightweight "ephemeral" app.
+ *
+ * @hide
+ */
+ public static final int INSTALL_EPHEMERAL = 0x00001000;
+
+ /**
* Flag parameter for
* {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
* that you don't want to kill the app containing the component. Be careful when you set this
@@ -862,6 +870,14 @@
public static final int INSTALL_FAILED_ABORTED = -115;
/**
+ * Installation failed return code: ephemeral app installs are incompatible with some
+ * other installation flags supplied for the operation; or other circumstances such
+ * as trying to upgrade a system app via an ephemeral install.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_EPHEMERAL_INVALID = -116;
+
+ /**
* Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
* package's data directory.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fd1e57b..0307108 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -627,6 +627,7 @@
public final static int PARSE_ENFORCE_CODE = 1<<10;
// TODO: fix b/25118622; remove this entirely once signature processing is quick
public final static int PARSE_SKIP_VERIFICATION = 1<<11;
+ public final static int PARSE_IS_EPHEMERAL = 1<<12;
private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 53627fc..a01d34a 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -235,6 +235,11 @@
}
/** {@hide} */
+ public static File getDataAppEphemeralDirectory(String volumeUuid) {
+ return new File(getDataDirectory(volumeUuid), "app-ephemeral");
+ }
+
+ /** {@hide} */
@Deprecated
public static File getDataUserDirectory(String volumeUuid) {
return getDataUserCredentialEncryptedDirectory(volumeUuid);
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index de29a96..8219c61 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -58,6 +58,7 @@
public class PackageHelper {
public static final int RECOMMEND_INSTALL_INTERNAL = 1;
public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
+ public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
public static final int RECOMMEND_FAILED_INVALID_APK = -2;
public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
@@ -442,7 +443,12 @@
final int prefer;
final boolean checkBoth;
- if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
+ boolean ephemeral = false;
+ if ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
+ prefer = RECOMMEND_INSTALL_INTERNAL;
+ ephemeral = true;
+ checkBoth = false;
+ } else if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
} else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
@@ -483,8 +489,12 @@
}
if (prefer == RECOMMEND_INSTALL_INTERNAL) {
+ // The ephemeral case will either fit and return EPHEMERAL, or will not fit
+ // and will fall through to return INSUFFICIENT_STORAGE
if (fitsOnInternal) {
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return (ephemeral)
+ ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
+ : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
if (fitsOnExternal) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 5d1906c..7e4e46b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -157,7 +157,6 @@
private final PackageManagerService mPm;
private AppOpsManager mAppOps;
- private StorageManager mStorage;
private final HandlerThread mInstallThread;
private final Handler mInstallHandler;
@@ -220,7 +219,8 @@
synchronized (mSessions) {
readSessionsLocked();
- reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL);
+ reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isEphemeral*/);
+ reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isEphemeral*/);
final ArraySet<File> unclaimedIcons = newArraySet(
mSessionsDir.listFiles());
@@ -241,11 +241,10 @@
public void systemReady() {
mAppOps = mContext.getSystemService(AppOpsManager.class);
- mStorage = mContext.getSystemService(StorageManager.class);
}
- private void reconcileStagesLocked(String volumeUuid) {
- final File stagingDir = buildStagingDir(volumeUuid);
+ private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) {
+ final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
final ArraySet<File> unclaimedStages = newArraySet(
stagingDir.listFiles(sStageFilter));
@@ -270,7 +269,7 @@
public void onPrivateVolumeMounted(String volumeUuid) {
synchronized (mSessions) {
- reconcileStagesLocked(volumeUuid);
+ reconcileStagesLocked(volumeUuid, false /*isEphemeral*/);
}
}
@@ -311,12 +310,12 @@
}
@Deprecated
- public File allocateStageDirLegacy(String volumeUuid) throws IOException {
+ public File allocateStageDirLegacy(String volumeUuid, boolean isEphemeral) throws IOException {
synchronized (mSessions) {
try {
final int sessionId = allocateSessionIdLocked();
mLegacySessions.put(sessionId, true);
- final File stageDir = buildStageDir(volumeUuid, sessionId);
+ final File stageDir = buildStageDir(volumeUuid, sessionId, isEphemeral);
prepareStageDir(stageDir);
return stageDir;
} catch (IllegalStateException e) {
@@ -678,7 +677,9 @@
File stageDir = null;
String stageCid = null;
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- stageDir = buildStageDir(params.volumeUuid, sessionId);
+ final boolean isEphemeral =
+ (params.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
+ stageDir = buildStageDir(params.volumeUuid, sessionId, isEphemeral);
} else {
stageCid = buildExternalStageCid(sessionId);
}
@@ -777,12 +778,15 @@
throw new IllegalStateException("Failed to allocate session ID");
}
- private File buildStagingDir(String volumeUuid) {
+ private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
+ if (isEphemeral) {
+ return Environment.getDataAppEphemeralDirectory(volumeUuid);
+ }
return Environment.getDataAppDirectory(volumeUuid);
}
- private File buildStageDir(String volumeUuid, int sessionId) {
- final File stagingDir = buildStagingDir(volumeUuid);
+ private File buildStageDir(String volumeUuid, int sessionId, boolean isEphemeral) {
+ final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
return new File(stagingDir, "vmdl" + sessionId + ".tmp");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0fde27f..ac5648b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -37,6 +37,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
+import static android.content.pm.PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
@@ -452,6 +453,7 @@
/** Directory where installed third-party apps stored */
final File mAppInstallDir;
+ final File mEphemeralInstallDir;
/**
* Directory to which applications installed internally have their
@@ -1372,6 +1374,7 @@
} break;
case POST_INSTALL: {
if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
+
PostInstallData data = mRunningInstalls.get(msg.arg1);
mRunningInstalls.delete(msg.arg1);
boolean deleteOld = false;
@@ -1429,19 +1432,33 @@
}
}
}
- sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- packageName, extras, 0, null, null, firstUsers);
+ // don't broadcast for ephemeral installs/updates
+ final boolean isEphemeral = isEphemeral(res.pkg);
+ if (!isEphemeral) {
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/, null /*targetPackage*/,
+ null /*finishedReceiver*/, firstUsers);
+ }
final boolean update = res.removedInfo.removedPackage != null;
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
- sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- packageName, extras, 0, null, null, updateUsers);
+ if (!isEphemeral) {
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/, null /*targetPackage*/,
+ null /*finishedReceiver*/, updateUsers);
+ }
if (update) {
- sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- packageName, extras, 0, null, null, updateUsers);
- sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
- null, null, 0, packageName, null, updateUsers);
+ if (!isEphemeral) {
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+ packageName, extras, 0 /*flags*/,
+ null /*targetPackage*/, null /*finishedReceiver*/,
+ updateUsers);
+ sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
+ null /*package*/, null /*extras*/, 0 /*flags*/,
+ packageName /*targetPackage*/,
+ null /*finishedReceiver*/, updateUsers);
+ }
// treat asec-hosted packages like removable media on upgrade
if (res.pkg.isForwardLocked() || isExternal(res.pkg)) {
@@ -1968,6 +1985,7 @@
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
+ mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
@@ -2202,6 +2220,9 @@
scanDirTracedLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
scanFlags | SCAN_REQUIRE_KNOWN, 0);
+ scanDirLI(mEphemeralInstallDir, PackageParser.PARSE_IS_EPHEMERAL,
+ scanFlags | SCAN_REQUIRE_KNOWN, 0);
+
/**
* Remove disable package settings for any updated system
* apps that were removed via an OTA. If they're not a
@@ -9871,6 +9892,11 @@
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user) {
+ if (DEBUG_EPHEMERAL) {
+ if ((sessionParams.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
+ Slog.d(TAG, "Ephemeral install of " + packageName);
+ }
+ }
final VerificationParams verifParams = new VerificationParams(
null, sessionParams.originatingUri, sessionParams.referrerUri,
sessionParams.originatingUid, null);
@@ -10262,6 +10288,13 @@
if (Build.IS_DEBUGGABLE && (installFlags & PackageManager.INSTALL_QUICK) != 0) {
return false;
}
+ // Ephemeral apps don't get the full verification treatment
+ if ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "INSTALL_EPHEMERAL so skipping verification");
+ }
+ return false;
+ }
boolean ensureVerifyAppsEnabled = isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS);
@@ -10911,16 +10944,24 @@
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
+ final boolean ephemeral = (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
PackageInfoLite pkgLite = null;
if (onInt && onSd) {
// Check if both bits are set.
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ } else if (onSd && ephemeral) {
+ Slog.w(TAG, "Conflicting flags specified for installing ephemeral on external");
+ ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);
+ if (DEBUG_EPHEMERAL && ephemeral) {
+ Slog.v(TAG, "pkgLite for install: " + pkgLite);
+ }
+
/*
* If we have too little free space, try to free cache
* before giving up.
@@ -10980,6 +11021,13 @@
// Set the flag to install on external media.
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
+ } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag");
+ }
+ installFlags |= PackageManager.INSTALL_EPHEMERAL;
+ installFlags &= ~(PackageManager.INSTALL_EXTERNAL
+ |PackageManager.INSTALL_INTERNAL);
} else {
// Make sure the flag for installing on external
// media is unset
@@ -11312,6 +11360,10 @@
return (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
}
+ protected boolean isEphemeral() {
+ return (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
+ }
+
UserHandle getUser() {
return user;
}
@@ -11389,7 +11441,9 @@
}
try {
- final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid);
+ final boolean isEphemeral = (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
+ final File tempDir =
+ mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
@@ -12184,6 +12238,8 @@
private void replacePackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags,
UserHandle user, String installerPackageName, String volumeUuid,
PackageInstalledInfo res) {
+ final boolean isEphemeral = (parseFlags & PackageParser.PARSE_IS_EPHEMERAL) != 0;
+
final PackageParser.Package oldPackage;
final String pkgName = pkg.packageName;
final int[] allUsers;
@@ -12192,6 +12248,14 @@
// First find the old package info and check signatures
synchronized(mPackages) {
oldPackage = mPackages.get(pkgName);
+ final boolean oldIsEphemeral
+ = ((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_EPHEMERAL) != 0);
+ if (isEphemeral && !oldIsEphemeral) {
+ // can't downgrade from full to ephemeral
+ Slog.w(TAG, "Can't replace app with ephemeral: " + pkgName);
+ res.returnCode = PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID;
+ return;
+ }
if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
final PackageSetting ps = mSettings.mPackages.get(pkgName);
if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
@@ -12588,6 +12652,7 @@
final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)
|| (args.volumeUuid != null));
final boolean quickInstall = ((installFlags & PackageManager.INSTALL_QUICK) != 0);
+ final boolean ephemeral = ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0);
boolean replace = false;
int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (args.move != null) {
@@ -12599,12 +12664,21 @@
if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
+ // Sanity check
+ if (ephemeral && (forwardLocked || onExternal)) {
+ Slog.i(TAG, "Incompatible ephemeral install; fwdLocked=" + forwardLocked
+ + " external=" + onExternal);
+ res.returnCode = PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID;
+ return;
+ }
+
// Retrieve PackageSettings and parse package
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
- | (quickInstall ? PackageParser.PARSE_SKIP_VERIFICATION : 0);
+ | (quickInstall ? PackageParser.PARSE_SKIP_VERIFICATION : 0)
+ | (ephemeral ? PackageParser.PARSE_IS_EPHEMERAL : 0);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
@@ -12786,11 +12860,18 @@
}
- if (systemApp && onExternal) {
- // Disable updates to system apps on sdcard
- res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
- "Cannot install updates to system apps on sdcard");
- return;
+ if (systemApp) {
+ if (onExternal) {
+ // Abort update; system app can't be replaced with app on sdcard
+ res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ "Cannot install updates to system apps on sdcard");
+ return;
+ } else if (ephemeral) {
+ // Abort update; system app can't be replaced with an ephemeral app
+ res.setError(INSTALL_FAILED_EPHEMERAL_INVALID,
+ "Cannot update a system app with an ephemeral app");
+ return;
+ }
}
if (args.move != null) {
@@ -12986,6 +13067,14 @@
return (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
+ private static boolean isEphemeral(PackageParser.Package pkg) {
+ return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EPHEMERAL) != 0;
+ }
+
+ private static boolean isEphemeral(PackageSetting ps) {
+ return (ps.pkgFlags & ApplicationInfo.FLAG_EPHEMERAL) != 0;
+ }
+
private static boolean isSystemApp(PackageParser.Package pkg) {
return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
@@ -13008,6 +13097,9 @@
private int packageFlagsToInstallFlags(PackageSetting ps) {
int installFlags = 0;
+ if (isEphemeral(ps)) {
+ installFlags |= PackageManager.INSTALL_EPHEMERAL;
+ }
if (isExternal(ps) && TextUtils.isEmpty(ps.volumeUuid)) {
// This existing package was an external ASEC install when we have
// the external flag without a UUID
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index dbb5818..d1b46c1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -705,6 +705,9 @@
case "--abi":
sessionParams.abiOverride = checkAbiArgument(getNextArg());
break;
+ case "--ephemeral":
+ sessionParams.installFlags |= PackageManager.INSTALL_EPHEMERAL;
+ break;
case "--user":
params.userId = UserHandle.parseUserArg(getNextArgRequired());
break;