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;