Add instant cookie APIs

This change adds APIs for instant apps to store cookie data
that is presisted across instant installs and across the
upgrade from an instant to a standard app. Standard apps
can use the cookie APIs but when they are uninstalled the
cookie is also deleted. The cookies are kept longer than
the instant apps as they are much smaller - 16KB by default.
We can change the cookie size via a system setting i.e.
after we ship we can increase size if needed.

We also add internal APIs to surface information about
installed and uninstalled instant apps which should be
used for showing them in the UI. For this puporse we store
the icon, permissions, and label of uninstalled apps. If
the app is re-installed we drop this meta-data but keep
the cookie around. If we have cookie data stored and the
signing cert of the app changes when it gets re-intalled
we wipe the cookie.

Test: CTS tests pass; hiddent APIs tested manually

Change-Id: If145c0440cc61a5303e2cbb70228d235d36037a5
diff --git a/services/core/java/com/android/server/BackgroundDexOptJobService.java b/services/core/java/com/android/server/BackgroundDexOptJobService.java
new file mode 100644
index 0000000..69e6ac5
--- /dev/null
+++ b/services/core/java/com/android/server/BackgroundDexOptJobService.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Environment;
+import android.os.ServiceManager;
+import android.os.storage.StorageManager;
+import android.util.ArraySet;
+import android.util.Log;
+import com.android.server.pm.PackageManagerService;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
+
+public class BackgroundDexOptJobService extends JobService {
+    private static final String TAG = "BackgroundDexOptJobService";
+
+    private static final boolean DEBUG = false;
+
+    private static final int JOB_IDLE_OPTIMIZE = 800;
+    private static final int JOB_POST_BOOT_UPDATE = 801;
+
+    private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG
+            ? TimeUnit.MINUTES.toMillis(1)
+            : TimeUnit.DAYS.toMillis(1);
+
+    private static ComponentName sDexoptServiceName = new ComponentName(
+            "android",
+            BackgroundDexOptJobService.class.getName());
+
+    /**
+     * Set of failed packages remembered across job runs.
+     */
+    static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();
+
+    /**
+     * Atomics set to true if the JobScheduler requests an abort.
+     */
+    final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
+    final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
+
+    /**
+     * Atomic set to true if one job should exit early because another job was started.
+     */
+    final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
+
+    private final File mDataDir = Environment.getDataDirectory();
+
+    public static void schedule(Context context) {
+        JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+        // Schedule a one-off job which scans installed packages and updates
+        // out-of-date oat files.
+        js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
+                    .setMinimumLatency(TimeUnit.MINUTES.toMillis(1))
+                    .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1))
+                    .build());
+
+        // Schedule a daily job which scans installed packages and compiles
+        // those with fresh profiling data.
+        js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
+                    .setRequiresDeviceIdle(true)
+                    .setRequiresCharging(true)
+                    .setPeriodic(IDLE_OPTIMIZATION_PERIOD)
+                    .build());
+
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Jobs scheduled");
+        }
+    }
+
+    public static void notifyPackageChanged(String packageName) {
+        // The idle maintanance job skips packages which previously failed to
+        // compile. The given package has changed and may successfully compile
+        // now. Remove it from the list of known failing packages.
+        synchronized (sFailedPackageNames) {
+            sFailedPackageNames.remove(packageName);
+        }
+    }
+
+    // Returns the current battery level as a 0-100 integer.
+    private int getBatteryLevel() {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        Intent intent = registerReceiver(null, filter);
+        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+        int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+
+        if (level < 0 || scale <= 0) {
+            // Battery data unavailable. This should never happen, so assume the worst.
+            return 0;
+        }
+
+        return (100 * level / scale);
+    }
+
+    private long getLowStorageThreshold() {
+        @SuppressWarnings("deprecation")
+        final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir);
+        if (lowThreshold == 0) {
+            Log.e(TAG, "Invalid low storage threshold");
+        }
+
+        return lowThreshold;
+    }
+
+    private boolean runPostBootUpdate(final JobParameters jobParams,
+            final PackageManagerService pm, final ArraySet<String> pkgs) {
+        if (mExitPostBootUpdate.get()) {
+            // This job has already been superseded. Do not start it.
+            return false;
+        }
+        new Thread("BackgroundDexOptService_PostBootUpdate") {
+            @Override
+            public void run() {
+                postBootUpdate(jobParams, pm, pkgs);
+            }
+
+        }.start();
+        return true;
+    }
+
+    private void postBootUpdate(JobParameters jobParams, PackageManagerService pm,
+            ArraySet<String> pkgs) {
+        // Load low battery threshold from the system config. This is a 0-100 integer.
+        final int lowBatteryThreshold = getResources().getInteger(
+                com.android.internal.R.integer.config_lowBatteryWarningLevel);
+        final long lowThreshold = getLowStorageThreshold();
+
+        mAbortPostBootUpdate.set(false);
+
+        for (String pkg : pkgs) {
+            if (mAbortPostBootUpdate.get()) {
+                // JobScheduler requested an early abort.
+                return;
+            }
+            if (mExitPostBootUpdate.get()) {
+                // Different job, which supersedes this one, is running.
+                break;
+            }
+            if (getBatteryLevel() < lowBatteryThreshold) {
+                // Rather bail than completely drain the battery.
+                break;
+            }
+            long usableSpace = mDataDir.getUsableSpace();
+            if (usableSpace < lowThreshold) {
+                // Rather bail than completely fill up the disk.
+                Log.w(TAG, "Aborting background dex opt job due to low storage: " +
+                        usableSpace);
+                break;
+            }
+
+            if (DEBUG_DEXOPT) {
+                Log.i(TAG, "Updating package " + pkg);
+            }
+
+            // Update package if needed. Note that there can be no race between concurrent
+            // jobs because PackageDexOptimizer.performDexOpt is synchronized.
+
+            // checkProfiles is false to avoid merging profiles during boot which
+            // might interfere with background compilation (b/28612421).
+            // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
+            // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
+            // trade-off worth doing to save boot time work.
+            pm.performDexOpt(pkg,
+                    /* checkProfiles */ false,
+                    PackageManagerService.REASON_BOOT,
+                    /* force */ false);
+        }
+        // Ran to completion, so we abandon our timeslice and do not reschedule.
+        jobFinished(jobParams, /* reschedule */ false);
+    }
+
+    private boolean runIdleOptimization(final JobParameters jobParams,
+            final PackageManagerService pm, final ArraySet<String> pkgs) {
+        new Thread("BackgroundDexOptService_IdleOptimization") {
+            @Override
+            public void run() {
+                idleOptimization(jobParams, pm, pkgs);
+            }
+        }.start();
+        return true;
+    }
+
+    private void idleOptimization(JobParameters jobParams, PackageManagerService pm,
+            ArraySet<String> pkgs) {
+        Log.i(TAG, "Performing idle optimizations");
+        // If post-boot update is still running, request that it exits early.
+        mExitPostBootUpdate.set(true);
+
+        mAbortIdleOptimization.set(false);
+
+        final long lowThreshold = getLowStorageThreshold();
+        for (String pkg : pkgs) {
+            if (mAbortIdleOptimization.get()) {
+                // JobScheduler requested an early abort.
+                return;
+            }
+
+            synchronized (sFailedPackageNames) {
+                if (sFailedPackageNames.contains(pkg)) {
+                    // Skip previously failing package
+                    continue;
+                }
+            }
+
+            long usableSpace = mDataDir.getUsableSpace();
+            if (usableSpace < lowThreshold) {
+                // Rather bail than completely fill up the disk.
+                Log.w(TAG, "Aborting background dex opt job due to low storage: " +
+                        usableSpace);
+                break;
+            }
+
+            // Conservatively add package to the list of failing ones in case performDexOpt
+            // never returns.
+            synchronized (sFailedPackageNames) {
+                sFailedPackageNames.add(pkg);
+            }
+            // Optimize package if needed. Note that there can be no race between
+            // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
+            if (pm.performDexOpt(pkg,
+                    /* checkProfiles */ true,
+                    PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                    /* force */ false)) {
+                // Dexopt succeeded, remove package from the list of failing ones.
+                synchronized (sFailedPackageNames) {
+                    sFailedPackageNames.remove(pkg);
+                }
+            }
+        }
+        // Ran to completion, so we abandon our timeslice and do not reschedule.
+        jobFinished(jobParams, /* reschedule */ false);
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "onStartJob");
+        }
+
+        // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
+        // the checks above. This check is not "live" - the value is determined by a background
+        // restart with a period of ~1 minute.
+        PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
+        if (pm.isStorageLow()) {
+            if (DEBUG_DEXOPT) {
+                Log.i(TAG, "Low storage, skipping this run");
+            }
+            return false;
+        }
+
+        final ArraySet<String> pkgs = pm.getOptimizablePackages();
+        if (pkgs == null || pkgs.isEmpty()) {
+            if (DEBUG_DEXOPT) {
+                Log.i(TAG, "No packages to optimize");
+            }
+            return false;
+        }
+
+        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
+            return runPostBootUpdate(params, pm, pkgs);
+        } else {
+            return runIdleOptimization(params, pm, pkgs);
+        }
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "onStopJob");
+        }
+
+        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
+            mAbortPostBootUpdate.set(true);
+        } else {
+            mAbortIdleOptimization.set(true);
+        }
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/PruneInstantAppsJobService.java b/services/core/java/com/android/server/PruneInstantAppsJobService.java
new file mode 100644
index 0000000..a6c3685
--- /dev/null
+++ b/services/core/java/com/android/server/PruneInstantAppsJobService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+
+import java.util.concurrent.TimeUnit;
+
+public class PruneInstantAppsJobService extends JobService {
+    private static final boolean DEBUG = false;
+
+    private static final int JOB_ID = 765123;
+
+    private static final long PRUNE_INSTANT_APPS_PERIOD_MILLIS = DEBUG
+            ? TimeUnit.MINUTES.toMillis(1) : TimeUnit.DAYS.toMillis(1);
+
+    public static void schedule(Context context) {
+        JobInfo pruneJob = new JobInfo.Builder(JOB_ID, new ComponentName(
+                context.getPackageName(), PruneInstantAppsJobService.class.getName()))
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(PRUNE_INSTANT_APPS_PERIOD_MILLIS)
+                .build();
+
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        jobScheduler.schedule(pruneJob);
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                PackageManagerInternal.class);
+        packageManagerInternal.pruneInstantApps();
+        jobFinished(params, false);
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c08bcef..0809792 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18651,7 +18651,7 @@
                     List<BroadcastFilter> registeredReceiversForUser =
                             mReceiverResolver.queryIntent(intent,
                                     resolvedType, false, false /*visibleToEphemeral*/,
-                                    false /*isEphemeral*/, users[i]);
+                                    false /*isInstant*/, users[i]);
                     if (registeredReceivers == null) {
                         registeredReceivers = registeredReceiversForUser;
                     } else if (registeredReceiversForUser != null) {
@@ -18661,7 +18661,7 @@
             } else {
                 registeredReceivers = mReceiverResolver.queryIntent(intent,
                         resolvedType, false, false /*visibleToEphemeral*/,
-                        false /*isEphemeral*/, userId);
+                        false /*isInstant*/, userId);
             }
         }
 
@@ -21553,7 +21553,7 @@
                     // Keeping this process, update its uid.
                     final UidRecord uidRec = app.uidRecord;
                     if (uidRec != null) {
-                        uidRec.ephemeral = app.info.isEphemeralApp();
+                        uidRec.ephemeral = app.info.isInstantApp();
                         if (uidRec.curProcState > app.curProcState) {
                             uidRec.curProcState = app.curProcState;
                         }
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
index 19eed35..93c14b9 100644
--- a/services/core/java/com/android/server/firewall/IntentFirewall.java
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -152,7 +152,7 @@
         // component-filter that matches this intent
         List<Rule> candidateRules;
         candidateRules = resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/,
-                false /*visibleToEphemeral*/, false /*isEphemeral*/, 0);
+                false /*visibleToEphemeral*/, false /*isInstant*/, 0);
         if (candidateRules == null) {
             candidateRules = new ArrayList<Rule>();
         }
diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java
index 8948fa3..07c9dec 100644
--- a/services/core/java/com/android/server/pm/BasePermission.java
+++ b/services/core/java/com/android/server/pm/BasePermission.java
@@ -95,7 +95,7 @@
                 && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
     }
 
-    public boolean isEphemeral() {
+    public boolean isInstant() {
         return (protectionLevel & PermissionInfo.PROTECTION_FLAG_EPHEMERAL) != 0;
     }
 }
diff --git a/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java b/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
deleted file mode 100644
index 3d946e0..0000000
--- a/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
+++ /dev/null
@@ -1,851 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.EphemeralApplicationInfo;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageUserState;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.AtomicFile;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.Xml;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.XmlUtils;
-import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class is a part of the package manager service that is responsible
- * for managing data associated with ephemeral apps such as cached uninstalled
- * ephemeral apps and ephemeral apps' cookies.
- */
-class EphemeralApplicationRegistry {
-    private static final boolean DEBUG = false;
-
-    private static final boolean ENABLED = true;
-
-    private static final String LOG_TAG = "EphemeralAppRegistry";
-
-    private static final long DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS =
-            DEBUG ? 60 * 1000L /* one min */ : 30 * 24 * 60 * 60 * 1000L; /* one month */
-
-    private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-
-    private static final String EPHEMERAL_APPS_FOLDER = "ephemeral";
-    private static final String EPHEMERAL_APP_ICON_FILE = "icon.png";
-    private static final String EPHEMERAL_APP_COOKIE_FILE_PREFIX = "cookie_";
-    private static final String EPHEMERAL_APP_COOKIE_FILE_SIFFIX = ".dat";
-    private static final String EPHEMERAL_APP_METADATA_FILE = "metadata.xml";
-
-    private static final String TAG_PACKAGE = "package";
-    private static final String TAG_PERMS = "perms";
-    private static final String TAG_PERM = "perm";
-
-    private static final String ATTR_LABEL = "label";
-    private static final String ATTR_NAME = "name";
-    private static final String ATTR_GRANTED = "granted";
-
-    private final PackageManagerService mService;
-
-    @GuardedBy("mService.mPackages")
-    private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps;
-
-    /**
-     * Automatic grants for access to instant app metadata.
-     * The key is the target application UID.
-     * The value is a set of instant app UIDs.
-     * UserID -> TargetAppId -> InstantAppId
-     */
-    private SparseArray<SparseArray<SparseBooleanArray>> mEphemeralGrants;
-    /** The set of all installed instant apps. UserID -> AppID */
-    private SparseArray<SparseBooleanArray> mInstalledEphemeralAppUids;
-
-    public EphemeralApplicationRegistry(PackageManagerService service) {
-        mService = service;
-    }
-
-    public byte[] getEphemeralApplicationCookieLPw(String packageName, int userId) {
-        if (!ENABLED) {
-            return EmptyArray.BYTE;
-        }
-        pruneUninstalledEphemeralAppsLPw(userId);
-
-        File cookieFile = peekEphemeralCookieFile(packageName, userId);
-        if (cookieFile != null && cookieFile.exists()) {
-            try {
-                return IoUtils.readFileAsByteArray(cookieFile.toString());
-            } catch (IOException e) {
-                Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
-            }
-        }
-        return null;
-    }
-
-    public boolean setEphemeralApplicationCookieLPw(String packageName,
-            byte[] cookie, int userId) {
-        if (!ENABLED) {
-            return false;
-        }
-        pruneUninstalledEphemeralAppsLPw(userId);
-
-        PackageParser.Package pkg = mService.mPackages.get(packageName);
-        if (pkg == null) {
-            return false;
-        }
-
-        if (!isValidCookie(mService.mContext, cookie)) {
-            return false;
-        }
-
-        File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
-        if (!appDir.exists() && !appDir.mkdirs()) {
-            return false;
-        }
-
-        File cookieFile = computeEphemeralCookieFile(pkg, userId);
-        if (cookieFile.exists() && !cookieFile.delete()) {
-            return false;
-        }
-
-        try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
-            fos.write(cookie, 0, cookie.length);
-        } catch (IOException e) {
-            Slog.w(LOG_TAG, "Error writing cookie file: " + cookieFile);
-            return false;
-        }
-        return true;
-    }
-
-    public Bitmap getEphemeralApplicationIconLPw(String packageName, int userId) {
-        if (!ENABLED) {
-            return null;
-        }
-        pruneUninstalledEphemeralAppsLPw(userId);
-
-        File iconFile = new File(getEphemeralApplicationDir(packageName, userId),
-                EPHEMERAL_APP_ICON_FILE);
-        if (iconFile.exists()) {
-            return BitmapFactory.decodeFile(iconFile.toString());
-        }
-        return null;
-    }
-
-    public List<EphemeralApplicationInfo> getEphemeralApplicationsLPw(int userId) {
-        if (!ENABLED) {
-            return Collections.emptyList();
-        }
-        pruneUninstalledEphemeralAppsLPw(userId);
-
-        List<EphemeralApplicationInfo> result = getInstalledEphemeralApplicationsLPr(userId);
-        result.addAll(getUninstalledEphemeralApplicationsLPr(userId));
-        return result;
-    }
-
-    public void onPackageInstalledLPw(PackageParser.Package pkg) {
-        if (!ENABLED) {
-            return;
-        }
-        PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-        for (int userId : UserManagerService.getInstance().getUserIds()) {
-            pruneUninstalledEphemeralAppsLPw(userId);
-
-            // Ignore not installed apps
-            if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
-                continue;
-            }
-
-            // Propagate permissions before removing any state
-            // TODO: Fix this later
-            // propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
-            if (pkg.applicationInfo.isEphemeralApp()) {
-                addEphemeralAppLPw(userId, ps.appId);
-            }
-
-            // Remove the in-memory state
-            if (mUninstalledEphemeralApps != null) {
-                List<UninstalledEphemeralAppState> uninstalledAppStates =
-                        mUninstalledEphemeralApps.get(userId);
-                if (uninstalledAppStates != null) {
-                    final int appCount = uninstalledAppStates.size();
-                    for (int i = 0; i < appCount; i++) {
-                        UninstalledEphemeralAppState uninstalledAppState =
-                                uninstalledAppStates.get(i);
-                        if (uninstalledAppState.mEphemeralApplicationInfo
-                                .getPackageName().equals(pkg.packageName)) {
-                            uninstalledAppStates.remove(i);
-                            break;
-                        }
-                    }
-                }
-            }
-
-            // Remove the on-disk state except the cookie
-            File ephemeralAppDir = getEphemeralApplicationDir(pkg.packageName, userId);
-            new File(ephemeralAppDir, EPHEMERAL_APP_METADATA_FILE).delete();
-            new File(ephemeralAppDir, EPHEMERAL_APP_ICON_FILE).delete();
-
-            // If app signature changed - wipe the cookie
-            File currentCookieFile = peekEphemeralCookieFile(pkg.packageName, userId);
-            if (currentCookieFile == null) {
-                continue;
-            }
-            File expectedCookeFile = computeEphemeralCookieFile(pkg, userId);
-            if (!currentCookieFile.equals(expectedCookeFile)) {
-                Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
-                        + " changed - dropping cookie");
-                currentCookieFile.delete();
-            }
-        }
-    }
-
-    public void onPackageUninstalledLPw(PackageParser.Package pkg) {
-        if (!ENABLED) {
-            return;
-        }
-        if (pkg == null) {
-            return;
-        }
-        PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-        for (int userId : UserManagerService.getInstance().getUserIds()) {
-            pruneUninstalledEphemeralAppsLPw(userId);
-
-            if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
-                continue;
-            }
-
-            if (pkg.applicationInfo.isEphemeralApp()) {
-                // Add a record for an uninstalled ephemeral app
-                addUninstalledEphemeralAppLPw(pkg, userId);
-                removeEphemeralAppLPw(userId, ps.appId);
-            } else {
-                // Deleting an app prunes all ephemeral state such as cookie
-                deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
-                removeAppLPw(userId, ps.appId);
-            }
-        }
-    }
-
-    public void onUserRemovedLPw(int userId) {
-        if (!ENABLED) {
-            return;
-        }
-        if (mUninstalledEphemeralApps != null) {
-            mUninstalledEphemeralApps.remove(userId);
-        }
-        if (mInstalledEphemeralAppUids != null) {
-            mInstalledEphemeralAppUids.remove(userId);
-        }
-        if (mEphemeralGrants != null) {
-            mEphemeralGrants.remove(userId);
-        }
-        deleteDir(getEphemeralApplicationsDir(userId));
-    }
-
-    public boolean isEphemeralAccessGranted(int userId, int targetAppId, int ephemeralAppId) {
-        if (mEphemeralGrants == null) {
-            return false;
-        }
-        final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
-        if (targetAppList == null) {
-            return false;
-        }
-        final SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
-        if (ephemeralGrantList == null) {
-            return false;
-        }
-        return ephemeralGrantList.get(ephemeralAppId);
-    }
-
-    public void grantEphemeralAccessLPw(int userId, Intent intent,
-            int targetAppId, int ephemeralAppId) {
-        if (mInstalledEphemeralAppUids == null) {
-            return;     // no ephemeral apps installed; no need to grant
-        }
-        SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
-        if (ephemeralAppList == null || !ephemeralAppList.get(ephemeralAppId)) {
-            return;     // ephemeral app id isn't installed; no need to grant
-        }
-        if (ephemeralAppList.get(targetAppId)) {
-            return;     // target app id is an ephemeral app; no need to grant
-        }
-        if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
-            final Set<String> categories = intent.getCategories();
-            if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
-                return;  // launched via VIEW/BROWSABLE intent; no need to grant
-            }
-        }
-        if (mEphemeralGrants == null) {
-            mEphemeralGrants = new SparseArray<>();
-        }
-        SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
-        if (targetAppList == null) {
-            targetAppList = new SparseArray<>();
-            mEphemeralGrants.put(userId, targetAppList);
-        }
-        SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
-        if (ephemeralGrantList == null) {
-            ephemeralGrantList = new SparseBooleanArray();
-            targetAppList.put(targetAppId, ephemeralGrantList);
-        }
-        ephemeralGrantList.put(ephemeralAppId, true /*granted*/);
-    }
-
-    public void addEphemeralAppLPw(int userId, int ephemeralAppId) {
-        if (mInstalledEphemeralAppUids == null) {
-            mInstalledEphemeralAppUids = new SparseArray<>();
-        }
-        SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
-        if (ephemeralAppList == null) {
-            ephemeralAppList = new SparseBooleanArray();
-            mInstalledEphemeralAppUids.put(userId, ephemeralAppList);
-        }
-        ephemeralAppList.put(ephemeralAppId, true /*installed*/);
-    }
-
-    private void removeEphemeralAppLPw(int userId, int ephemeralAppId) {
-        // remove from the installed list
-        if (mInstalledEphemeralAppUids == null) {
-            return; // no ephemeral apps on the system
-        }
-        final SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
-        if (ephemeralAppList == null) {
-            Slog.w(LOG_TAG, "Remove ephemeral not in install list");
-            return;
-        } else {
-            ephemeralAppList.delete(ephemeralAppId);
-        }
-        // remove any grants
-        if (mEphemeralGrants == null) {
-            return; // no grants on the system
-        }
-        final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
-        if (targetAppList == null) {
-            return; // no grants for this user
-        }
-        final int numApps = targetAppList.size();
-        for (int i = targetAppList.size() - 1; i >= 0; --i) {
-            targetAppList.valueAt(i).delete(ephemeralAppId);
-        }
-    }
-
-    private void removeAppLPw(int userId, int targetAppId) {
-        // remove from the installed list
-        if (mEphemeralGrants == null) {
-            return; // no grants on the system
-        }
-        final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
-        if (targetAppList == null) {
-            return; // no grants for this user
-        }
-        targetAppList.delete(targetAppId);
-    }
-
-    private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
-        EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
-        if (uninstalledApp == null) {
-            return;
-        }
-        if (mUninstalledEphemeralApps == null) {
-            mUninstalledEphemeralApps = new SparseArray<>();
-        }
-        List<UninstalledEphemeralAppState> uninstalledAppStates =
-                mUninstalledEphemeralApps.get(userId);
-        if (uninstalledAppStates == null) {
-            uninstalledAppStates = new ArrayList<>();
-            mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
-        }
-        UninstalledEphemeralAppState uninstalledAppState = new UninstalledEphemeralAppState(
-                uninstalledApp, System.currentTimeMillis());
-        uninstalledAppStates.add(uninstalledAppState);
-
-        writeUninstalledEphemeralAppMetadata(uninstalledApp, userId);
-        writeEphemeralApplicationIconLPw(pkg, userId);
-    }
-
-    private void writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId) {
-        File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
-        if (!appDir.exists()) {
-            return;
-        }
-
-        Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
-
-        final Bitmap bitmap;
-        if (icon instanceof BitmapDrawable) {
-            bitmap = ((BitmapDrawable) icon).getBitmap();
-        } else  {
-            bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
-                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(bitmap);
-            icon.draw(canvas);
-        }
-
-        File iconFile = new File(getEphemeralApplicationDir(pkg.packageName, userId),
-                EPHEMERAL_APP_ICON_FILE);
-
-        try (FileOutputStream out = new FileOutputStream(iconFile)) {
-            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
-        } catch (Exception e) {
-            Slog.e(LOG_TAG, "Error writing ephemeral app icon", e);
-        }
-    }
-
-    private void pruneUninstalledEphemeralAppsLPw(int userId) {
-        final long maxCacheDurationMillis = Settings.Global.getLong(
-                mService.mContext.getContentResolver(),
-                Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
-                DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
-
-        // Prune in-memory state
-        if (mUninstalledEphemeralApps != null) {
-            List<UninstalledEphemeralAppState> uninstalledAppStates =
-                    mUninstalledEphemeralApps.get(userId);
-            if (uninstalledAppStates != null) {
-                final int appCount = uninstalledAppStates.size();
-                for (int j = appCount - 1; j >= 0; j--) {
-                    UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(j);
-                    final long elapsedCachingMillis = System.currentTimeMillis()
-                            - uninstalledAppState.mTimestamp;
-                    if (elapsedCachingMillis > maxCacheDurationMillis) {
-                        uninstalledAppStates.remove(j);
-                    }
-                }
-                if (uninstalledAppStates.isEmpty()) {
-                    mUninstalledEphemeralApps.remove(userId);
-                }
-            }
-        }
-
-        // Prune on-disk state
-        File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
-        if (!ephemeralAppsDir.exists()) {
-            return;
-        }
-        File[] files = ephemeralAppsDir.listFiles();
-        if (files == null) {
-            return;
-        }
-        for (File ephemeralDir : files) {
-            if (!ephemeralDir.isDirectory()) {
-                continue;
-            }
-
-            File metadataFile = new File(ephemeralDir, EPHEMERAL_APP_METADATA_FILE);
-            if (!metadataFile.exists()) {
-                continue;
-            }
-
-            final long elapsedCachingMillis = System.currentTimeMillis()
-                    - metadataFile.lastModified();
-            if (elapsedCachingMillis > maxCacheDurationMillis) {
-                deleteDir(ephemeralDir);
-            }
-        }
-    }
-
-    private List<EphemeralApplicationInfo> getInstalledEphemeralApplicationsLPr(int userId) {
-        List<EphemeralApplicationInfo> result = null;
-
-        final int packageCount = mService.mPackages.size();
-        for (int i = 0; i < packageCount; i++) {
-            PackageParser.Package pkg = mService.mPackages.valueAt(i);
-            if (!pkg.applicationInfo.isEphemeralApp()) {
-                continue;
-            }
-            EphemeralApplicationInfo info = createEphemeralAppInfoForPackage(pkg, userId);
-            if (info == null) {
-                continue;
-            }
-            if (result == null) {
-                result = new ArrayList<>();
-            }
-            result.add(info);
-        }
-
-        return result;
-    }
-
-    private EphemeralApplicationInfo createEphemeralAppInfoForPackage(
-            PackageParser.Package pkg, int userId) {
-        PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return null;
-        }
-        PackageUserState userState = ps.readUserState(userId);
-        if (userState == null || !userState.installed || userState.hidden) {
-            return null;
-        }
-
-        String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
-        pkg.requestedPermissions.toArray(requestedPermissions);
-
-        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
-        String[] grantedPermissions = new String[permissions.size()];
-        permissions.toArray(grantedPermissions);
-
-        return new EphemeralApplicationInfo(pkg.applicationInfo,
-                requestedPermissions, grantedPermissions);
-    }
-
-    private List<EphemeralApplicationInfo> getUninstalledEphemeralApplicationsLPr(int userId) {
-        List<UninstalledEphemeralAppState> uninstalledAppStates =
-                getUninstalledEphemeralAppStatesLPr(userId);
-        if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        List<EphemeralApplicationInfo> uninstalledApps = new ArrayList<>();
-        final int stateCount = uninstalledAppStates.size();
-        for (int i = 0; i < stateCount; i++) {
-            UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
-            uninstalledApps.add(uninstalledAppState.mEphemeralApplicationInfo);
-        }
-        return uninstalledApps;
-    }
-
-    private void propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId) {
-        EphemeralApplicationInfo appInfo = getOrParseUninstalledEphemeralAppInfo(pkg.packageName, userId);
-        if (appInfo == null) {
-            return;
-        }
-        if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
-            return;
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            for (String grantedPermission : appInfo.getGrantedPermissions()) {
-                mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private EphemeralApplicationInfo getOrParseUninstalledEphemeralAppInfo(String packageName,
-            int userId) {
-        if (mUninstalledEphemeralApps != null) {
-            List<UninstalledEphemeralAppState> uninstalledAppStates =
-                    mUninstalledEphemeralApps.get(userId);
-            if (uninstalledAppStates != null) {
-                final int appCount = uninstalledAppStates.size();
-                for (int i = 0; i < appCount; i++) {
-                    UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
-                    if (uninstalledAppState.mEphemeralApplicationInfo
-                            .getPackageName().equals(packageName)) {
-                        return uninstalledAppState.mEphemeralApplicationInfo;
-                    }
-                }
-            }
-        }
-
-        File metadataFile = new File(getEphemeralApplicationDir(packageName, userId),
-                EPHEMERAL_APP_METADATA_FILE);
-        UninstalledEphemeralAppState uninstalledAppState = parseMetadataFile(metadataFile);
-        if (uninstalledAppState == null) {
-            return null;
-        }
-
-        return uninstalledAppState.mEphemeralApplicationInfo;
-    }
-
-    private List<UninstalledEphemeralAppState> getUninstalledEphemeralAppStatesLPr(int userId) {
-        List<UninstalledEphemeralAppState> uninstalledAppStates = null;
-        if (mUninstalledEphemeralApps != null) {
-            uninstalledAppStates = mUninstalledEphemeralApps.get(userId);
-            if (uninstalledAppStates != null) {
-                return uninstalledAppStates;
-            }
-        }
-
-        File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
-        if (ephemeralAppsDir.exists()) {
-            File[] files = ephemeralAppsDir.listFiles();
-            if (files != null) {
-                for (File ephemeralDir : files) {
-                    if (!ephemeralDir.isDirectory()) {
-                        continue;
-                    }
-                    File metadataFile = new File(ephemeralDir,
-                            EPHEMERAL_APP_METADATA_FILE);
-                    UninstalledEphemeralAppState uninstalledAppState =
-                            parseMetadataFile(metadataFile);
-                    if (uninstalledAppState == null) {
-                        continue;
-                    }
-                    if (uninstalledAppStates == null) {
-                        uninstalledAppStates = new ArrayList<>();
-                    }
-                    uninstalledAppStates.add(uninstalledAppState);
-                }
-            }
-        }
-
-        if (uninstalledAppStates != null) {
-            if (mUninstalledEphemeralApps == null) {
-                mUninstalledEphemeralApps = new SparseArray<>();
-            }
-            mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
-        }
-
-        return uninstalledAppStates;
-    }
-
-    private static boolean isValidCookie(Context context, byte[] cookie) {
-        if (ArrayUtils.isEmpty(cookie)) {
-            return true;
-        }
-        return cookie.length <= context.getPackageManager().getEphemeralCookieMaxSizeBytes();
-    }
-
-    private static UninstalledEphemeralAppState parseMetadataFile(File metadataFile) {
-        if (!metadataFile.exists()) {
-            return null;
-        }
-        FileInputStream in;
-        try {
-            in = new AtomicFile(metadataFile).openRead();
-        } catch (FileNotFoundException fnfe) {
-            Slog.i(LOG_TAG, "No ephemeral metadata file");
-            return null;
-        }
-
-        final File ephemeralDir = metadataFile.getParentFile();
-        final long timestamp = metadataFile.lastModified();
-        final String packageName = ephemeralDir.getName();
-
-        try {
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(in, StandardCharsets.UTF_8.name());
-            return new UninstalledEphemeralAppState(
-                    parseMetadata(parser, packageName), timestamp);
-        } catch (XmlPullParserException | IOException e) {
-            throw new IllegalStateException("Failed parsing ephemeral"
-                    + " metadata file: " + metadataFile, e);
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
-    }
-
-    private static File computeEphemeralCookieFile(PackageParser.Package pkg, int userId) {
-        File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
-        String cookieFile = EPHEMERAL_APP_COOKIE_FILE_PREFIX + computePackageCertDigest(pkg)
-                + EPHEMERAL_APP_COOKIE_FILE_SIFFIX;
-        return new File(appDir, cookieFile);
-    }
-
-    private static File peekEphemeralCookieFile(String packageName, int userId) {
-        File appDir = getEphemeralApplicationDir(packageName, userId);
-        if (!appDir.exists()) {
-            return null;
-        }
-        for (File file : appDir.listFiles()) {
-            if (!file.isDirectory()
-                    && file.getName().startsWith(EPHEMERAL_APP_COOKIE_FILE_PREFIX)
-                    && file.getName().endsWith(EPHEMERAL_APP_COOKIE_FILE_SIFFIX)) {
-                return file;
-            }
-        }
-        return null;
-    }
-
-    private static EphemeralApplicationInfo parseMetadata(XmlPullParser parser, String packageName)
-            throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-            if (TAG_PACKAGE.equals(parser.getName())) {
-                return parsePackage(parser, packageName);
-            }
-        }
-        return null;
-    }
-
-    private static EphemeralApplicationInfo parsePackage(XmlPullParser parser, String packageName)
-            throws IOException, XmlPullParserException {
-        String label = parser.getAttributeValue(null, ATTR_LABEL);
-
-        List<String> outRequestedPermissions = new ArrayList<>();
-        List<String> outGrantedPermissions = new ArrayList<>();
-
-        final int outerDepth = parser.getDepth();
-        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-            if (TAG_PERMS.equals(parser.getName())) {
-                parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
-            }
-        }
-
-        String[] requestedPermissions = new String[outRequestedPermissions.size()];
-        outRequestedPermissions.toArray(requestedPermissions);
-
-        String[] grantedPermissions = new String[outGrantedPermissions.size()];
-        outGrantedPermissions.toArray(grantedPermissions);
-
-        return new EphemeralApplicationInfo(packageName, label,
-                requestedPermissions, grantedPermissions);
-    }
-
-    private static void parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions,
-            List<String> outGrantedPermissions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        while (XmlUtils.nextElementWithin(parser,outerDepth)) {
-            if (TAG_PERM.equals(parser.getName())) {
-                String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
-                outRequestedPermissions.add(permission);
-                if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
-                    outGrantedPermissions.add(permission);
-                }
-            }
-        }
-    }
-
-    private void writeUninstalledEphemeralAppMetadata(
-            EphemeralApplicationInfo ephemeralApp, int userId) {
-        File appDir = getEphemeralApplicationDir(ephemeralApp.getPackageName(), userId);
-        if (!appDir.exists() && !appDir.mkdirs()) {
-            return;
-        }
-
-        File metadataFile = new File(appDir, EPHEMERAL_APP_METADATA_FILE);
-
-        AtomicFile destination = new AtomicFile(metadataFile);
-        FileOutputStream out = null;
-        try {
-            out = destination.startWrite();
-
-            XmlSerializer serializer = Xml.newSerializer();
-            serializer.setOutput(out, StandardCharsets.UTF_8.name());
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
-            serializer.startDocument(null, true);
-
-            serializer.startTag(null, TAG_PACKAGE);
-            serializer.attribute(null, ATTR_LABEL, ephemeralApp.loadLabel(
-                    mService.mContext.getPackageManager()).toString());
-
-            serializer.startTag(null, TAG_PERMS);
-            for (String permission : ephemeralApp.getRequestedPermissions()) {
-                serializer.startTag(null, TAG_PERM);
-                serializer.attribute(null, ATTR_NAME, permission);
-                if (ArrayUtils.contains(ephemeralApp.getGrantedPermissions(), permission)) {
-                    serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
-                }
-                serializer.endTag(null, TAG_PERM);
-            }
-            serializer.endTag(null, TAG_PERMS);
-
-            serializer.endTag(null, TAG_PACKAGE);
-
-            serializer.endDocument();
-            destination.finishWrite(out);
-        } catch (Throwable t) {
-            Slog.wtf(LOG_TAG, "Failed to write ephemeral state, restoring backup", t);
-            destination.failWrite(out);
-        } finally {
-            IoUtils.closeQuietly(out);
-        }
-    }
-
-    private static String computePackageCertDigest(PackageParser.Package pkg) {
-        MessageDigest messageDigest;
-        try {
-            messageDigest = MessageDigest.getInstance("SHA256");
-        } catch (NoSuchAlgorithmException e) {
-            /* can't happen */
-            return null;
-        }
-
-        messageDigest.update(pkg.mSignatures[0].toByteArray());
-
-        final byte[] digest = messageDigest.digest();
-        final int digestLength = digest.length;
-        final int charCount = 2 * digestLength;
-
-        final char[] chars = new char[charCount];
-        for (int i = 0; i < digestLength; i++) {
-            final int byteHex = digest[i] & 0xFF;
-            chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
-            chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
-        }
-        return new String(chars);
-    }
-
-    private static File getEphemeralApplicationsDir(int userId) {
-        return new File(Environment.getUserSystemDirectory(userId),
-                EPHEMERAL_APPS_FOLDER);
-    }
-
-    private static File getEphemeralApplicationDir(String packageName, int userId) {
-        return new File (getEphemeralApplicationsDir(userId), packageName);
-    }
-
-    private static void deleteDir(File dir) {
-        File[] files = dir.listFiles();
-        if (files != null) {
-            for (File file : dir.listFiles()) {
-                deleteDir(file);
-            }
-        }
-        dir.delete();
-    }
-
-    private static final class UninstalledEphemeralAppState {
-        final EphemeralApplicationInfo mEphemeralApplicationInfo;
-        final long mTimestamp;
-
-        public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp,
-                long timestamp) {
-            mEphemeralApplicationInfo = ephemeralApp;
-            mTimestamp = timestamp;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java
index 96a0d18..3c55422 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -235,7 +235,7 @@
                 }
                 List<EphemeralResponse> matchedResolveInfoList = ephemeralResolver.queryIntent(
                         intent, resolvedType, false /*defaultOnly*/, false /*visibleToEphemeral*/,
-                        false /*isEphemeral*/, userId);
+                        false /*isInstant*/, userId);
                 if (!matchedResolveInfoList.isEmpty()) {
                     return matchedResolveInfoList.get(0);
                 }
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
new file mode 100644
index 0000000..829c473
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -0,0 +1,959 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Intent;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.PackageParser;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.PackageUtils;
+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.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+import libcore.io.IoUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * This class is a part of the package manager service that is responsible
+ * for managing data associated with instant apps such as cached uninstalled
+ * instant apps and instant apps' cookies. In addition it is responsible for
+ * pruning installed instant apps and meta-data for uninstalled instant apps
+ * when free space is needed.
+ */
+class InstantAppRegistry {
+    private static final boolean DEBUG = false;
+
+    private static final String LOG_TAG = "InstantAppRegistry";
+
+    private static final long DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS =
+            DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */
+
+    private static final String INSTANT_APPS_FOLDER = "instant";
+    private static final String INSTANT_APP_ICON_FILE = "icon.png";
+    private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_";
+    private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat";
+    private static final String INSTANT_APP_METADATA_FILE = "metadata.xml";
+
+    private static final String TAG_PACKAGE = "package";
+    private static final String TAG_PERMISSIONS = "permissions";
+    private static final String TAG_PERMISSION = "permission";
+
+    private static final String ATTR_LABEL = "label";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_GRANTED = "granted";
+
+    private final PackageManagerService mService;
+    private final CookiePersistence mCookiePersistence;
+
+    /** State for uninstalled instant apps */
+    @GuardedBy("mService.mPackages")
+    private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
+
+    /**
+     * Automatic grants for access to instant app metadata.
+     * The key is the target application UID.
+     * The value is a set of instant app UIDs.
+     * UserID -> TargetAppId -> InstantAppId
+     */
+    @GuardedBy("mService.mPackages")
+    private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants;
+
+    /** The set of all installed instant apps. UserID -> AppID */
+    @GuardedBy("mService.mPackages")
+    private SparseArray<SparseBooleanArray> mInstalledInstantAppUids;
+
+    public InstantAppRegistry(PackageManagerService service) {
+        mService = service;
+        mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
+    }
+
+    public byte[] getInstantAppCookieLPw(@NonNull String packageName,
+                                         @UserIdInt int userId) {
+        byte[] pendingCookie = mCookiePersistence.getPendingPersistCookie(userId, packageName);
+        if (pendingCookie != null) {
+            return pendingCookie;
+        }
+        File cookieFile = peekInstantCookieFile(packageName, userId);
+        if (cookieFile != null && cookieFile.exists()) {
+            try {
+                return IoUtils.readFileAsByteArray(cookieFile.toString());
+            } catch (IOException e) {
+                Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
+            }
+        }
+        return null;
+    }
+
+    public boolean setInstantAppCookieLPw(@NonNull String packageName,
+                                          @Nullable byte[] cookie, @UserIdInt int userId) {
+        if (cookie != null && cookie.length > 0) {
+            final int maxCookieSize = mService.mContext.getPackageManager()
+                    .getInstantAppCookieMaxSize();
+            if (cookie.length > maxCookieSize) {
+                Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
+                        + cookie.length + " bytes while max size is " + maxCookieSize);
+                return false;
+            }
+        }
+
+        mCookiePersistence.schedulePersist(userId, packageName, cookie);
+        return true;
+    }
+
+    private void persistInstantApplicationCookie(@Nullable byte[] cookie,
+            @NonNull String packageName, @UserIdInt int userId) {
+        synchronized (mService.mPackages) {
+            PackageParser.Package pkg = mService.mPackages.get(packageName);
+            if (pkg == null) {
+                return;
+            }
+
+            File appDir = getInstantApplicationDir(packageName, userId);
+            if (!appDir.exists() && !appDir.mkdirs()) {
+                Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
+                return;
+            }
+
+            File cookieFile = computeInstantCookieFile(pkg, userId);
+            if (cookieFile.exists() && !cookieFile.delete()) {
+                Slog.e(LOG_TAG, "Cannot delete instant app cookie file");
+            }
+
+            // No cookie or an empty one means delete - done
+            if (cookie == null || cookie.length <= 0) {
+                return;
+            }
+
+            try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
+                fos.write(cookie, 0, cookie.length);
+            } catch (IOException e) {
+                Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
+            }
+        }
+    }
+
+    public Bitmap getInstantAppIconLPw(@NonNull String packageName,
+                                       @UserIdInt int userId) {
+        File iconFile = new File(getInstantApplicationDir(packageName, userId),
+                INSTANT_APP_ICON_FILE);
+        if (iconFile.exists()) {
+            return BitmapFactory.decodeFile(iconFile.toString());
+        }
+        return null;
+    }
+
+    public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
+        List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
+        List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
+        if (installedApps != null) {
+            if (uninstalledApps != null) {
+                installedApps.addAll(uninstalledApps);
+            }
+            return installedApps;
+        }
+        return uninstalledApps;
+    }
+
+    public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) {
+        PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+
+        for (int userId : userIds) {
+            // Ignore not installed apps
+            if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
+                continue;
+            }
+
+            // Propagate permissions before removing any state
+            propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
+
+            // Track instant apps
+            if (pkg.applicationInfo.isInstantApp()) {
+                addInstantAppLPw(userId, ps.appId);
+            }
+
+            // Remove the in-memory state
+            removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
+                            state.mInstantAppInfo.getPackageName().equals(pkg.packageName),
+                    userId);
+
+            // Remove the on-disk state except the cookie
+            File instantAppDir = getInstantApplicationDir(pkg.packageName, userId);
+            new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
+            new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+
+            // If app signature changed - wipe the cookie
+            File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId);
+            if (currentCookieFile == null) {
+                continue;
+            }
+            File expectedCookeFile = computeInstantCookieFile(pkg, userId);
+            if (!currentCookieFile.equals(expectedCookeFile)) {
+                Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
+                        + " changed - dropping cookie");
+                currentCookieFile.delete();
+            }
+        }
+    }
+
+    public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
+            @NonNull int[] userIds) {
+        PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+
+        for (int userId : userIds) {
+            if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
+                continue;
+            }
+
+            if (pkg.applicationInfo.isInstantApp()) {
+                // Add a record for an uninstalled instant app
+                addUninstalledInstantAppLPw(pkg, userId);
+                removeInstantAppLPw(userId, ps.appId);
+            } else {
+                // Deleting an app prunes all instant state such as cookie
+                deleteDir(getInstantApplicationDir(pkg.packageName, userId));
+                removeAppLPw(userId, ps.appId);
+            }
+        }
+    }
+
+    public void onUserRemovedLPw(int userId) {
+        if (mUninstalledInstantApps != null) {
+            mUninstalledInstantApps.remove(userId);
+            if (mUninstalledInstantApps.size() <= 0) {
+                mUninstalledInstantApps = null;
+            }
+        }
+        if (mInstalledInstantAppUids != null) {
+            mInstalledInstantAppUids.remove(userId);
+            if (mInstalledInstantAppUids.size() <= 0) {
+                mInstalledInstantAppUids = null;
+            }
+        }
+        if (mInstantGrants != null) {
+            mInstantGrants.remove(userId);
+            if (mInstantGrants.size() <= 0) {
+                mInstantGrants = null;
+            }
+        }
+        deleteDir(getInstantApplicationsDir(userId));
+    }
+
+    public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
+            int instantAppId) {
+        if (mInstantGrants == null) {
+            return false;
+        }
+        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
+        if (targetAppList == null) {
+            return false;
+        }
+        final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
+        if (instantGrantList == null) {
+            return false;
+        }
+        return instantGrantList.get(instantAppId);
+    }
+
+    public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
+            int targetAppId, int instantAppId) {
+        if (mInstalledInstantAppUids == null) {
+            return;     // no instant apps installed; no need to grant
+        }
+        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
+        if (instantAppList == null || !instantAppList.get(instantAppId)) {
+            return;     // instant app id isn't installed; no need to grant
+        }
+        if (instantAppList.get(targetAppId)) {
+            return;     // target app id is an instant app; no need to grant
+        }
+        if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
+            final Set<String> categories = intent.getCategories();
+            if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
+                return;  // launched via VIEW/BROWSABLE intent; no need to grant
+            }
+        }
+        if (mInstantGrants == null) {
+            mInstantGrants = new SparseArray<>();
+        }
+        SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
+        if (targetAppList == null) {
+            targetAppList = new SparseArray<>();
+            mInstantGrants.put(userId, targetAppList);
+        }
+        SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
+        if (instantGrantList == null) {
+            instantGrantList = new SparseBooleanArray();
+            targetAppList.put(targetAppId, instantGrantList);
+        }
+        instantGrantList.put(instantAppId, true /*granted*/);
+    }
+
+    public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
+        if (mInstalledInstantAppUids == null) {
+            mInstalledInstantAppUids = new SparseArray<>();
+        }
+        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
+        if (instantAppList == null) {
+            instantAppList = new SparseBooleanArray();
+            mInstalledInstantAppUids.put(userId, instantAppList);
+        }
+        instantAppList.put(instantAppId, true /*installed*/);
+    }
+
+    private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
+        // remove from the installed list
+        if (mInstalledInstantAppUids == null) {
+            return; // no instant apps on the system
+        }
+        final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
+        if (instantAppList == null) {
+            return;
+        }
+
+        instantAppList.delete(instantAppId);
+
+        // remove any grants
+        if (mInstantGrants == null) {
+            return; // no grants on the system
+        }
+        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
+        if (targetAppList == null) {
+            return; // no grants for this user
+        }
+        for (int i = targetAppList.size() - 1; i >= 0; --i) {
+            targetAppList.valueAt(i).delete(instantAppId);
+        }
+    }
+
+    private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
+        // remove from the installed list
+        if (mInstantGrants == null) {
+            return; // no grants on the system
+        }
+        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
+        if (targetAppList == null) {
+            return; // no grants for this user
+        }
+        targetAppList.delete(targetAppId);
+    }
+
+    private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
+            @UserIdInt int userId) {
+        InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
+                pkg, userId, false);
+        if (uninstalledApp == null) {
+            return;
+        }
+        if (mUninstalledInstantApps == null) {
+            mUninstalledInstantApps = new SparseArray<>();
+        }
+        List<UninstalledInstantAppState> uninstalledAppStates =
+                mUninstalledInstantApps.get(userId);
+        if (uninstalledAppStates == null) {
+            uninstalledAppStates = new ArrayList<>();
+            mUninstalledInstantApps.put(userId, uninstalledAppStates);
+        }
+        UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
+                uninstalledApp, System.currentTimeMillis());
+        uninstalledAppStates.add(uninstalledAppState);
+
+        writeUninstalledInstantAppMetadata(uninstalledApp, userId);
+        writeInstantApplicationIconLPw(pkg, userId);
+    }
+
+    private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
+            @UserIdInt int userId) {
+        File appDir = getInstantApplicationDir(pkg.packageName, userId);
+        if (!appDir.exists()) {
+            return;
+        }
+
+        Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
+
+        final Bitmap bitmap;
+        if (icon instanceof BitmapDrawable) {
+            bitmap = ((BitmapDrawable) icon).getBitmap();
+        } else  {
+            bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            icon.draw(canvas);
+        }
+
+        File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
+                INSTANT_APP_ICON_FILE);
+
+        try (FileOutputStream out = new FileOutputStream(iconFile)) {
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+        } catch (Exception e) {
+            Slog.e(LOG_TAG, "Error writing instant app icon", e);
+        }
+    }
+
+    public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
+            @UserIdInt int userId) {
+        removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
+                state.mInstantAppInfo.getPackageName().equals(packageName),
+                userId);
+
+        File instantAppDir = getInstantApplicationDir(packageName, userId);
+        new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
+        new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+        File cookie = peekInstantCookieFile(packageName, userId);
+        if (cookie != null) {
+            cookie.delete();
+        }
+    }
+
+    private void removeUninstalledInstantAppStateLPw(
+            @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
+        if (mUninstalledInstantApps == null) {
+            return;
+        }
+        List<UninstalledInstantAppState> uninstalledAppStates =
+                mUninstalledInstantApps.get(userId);
+        if (uninstalledAppStates == null) {
+            return;
+        }
+        final int appCount = uninstalledAppStates.size();
+        for (int i = 0; i < appCount; i++) {
+            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
+            if (!criteria.test(uninstalledAppState)) {
+                continue;
+            }
+            uninstalledAppStates.remove(i);
+            if (uninstalledAppStates.isEmpty()) {
+                mUninstalledInstantApps.remove(userId);
+                if (mUninstalledInstantApps.size() <= 0) {
+                    mUninstalledInstantApps = null;
+                }
+                return;
+            }
+        }
+    }
+
+    public void pruneInstantAppsLPw() {
+        // For now we prune only state for uninstalled instant apps
+        final long maxCacheDurationMillis = Settings.Global.getLong(
+                mService.mContext.getContentResolver(),
+                Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS,
+                DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS);
+
+        for (int userId : UserManagerService.getInstance().getUserIds()) {
+            // Prune in-memory state
+            removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
+                final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
+                return (elapsedCachingMillis > maxCacheDurationMillis);
+            }, userId);
+
+            // Prune on-disk state
+            File instantAppsDir = getInstantApplicationsDir(userId);
+            if (!instantAppsDir.exists()) {
+                continue;
+            }
+            File[] files = instantAppsDir.listFiles();
+            if (files == null) {
+                continue;
+            }
+            for (File instantDir : files) {
+                if (!instantDir.isDirectory()) {
+                    continue;
+                }
+
+                File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
+                if (!metadataFile.exists()) {
+                    continue;
+                }
+
+                final long elapsedCachingMillis = System.currentTimeMillis()
+                        - metadataFile.lastModified();
+                if (elapsedCachingMillis > maxCacheDurationMillis) {
+                    deleteDir(instantDir);
+                }
+            }
+        }
+    }
+
+    private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
+            @UserIdInt int userId) {
+        List<InstantAppInfo> result = null;
+
+        final int packageCount = mService.mPackages.size();
+        for (int i = 0; i < packageCount; i++) {
+            PackageParser.Package pkg = mService.mPackages.valueAt(i);
+            if (!pkg.applicationInfo.isInstantApp()) {
+                continue;
+            }
+            InstantAppInfo info = createInstantAppInfoForPackage(
+                    pkg, userId, true);
+            if (info == null) {
+                continue;
+            }
+            if (result == null) {
+                result = new ArrayList<>();
+            }
+            result.add(info);
+        }
+
+        return result;
+    }
+
+    private @NonNull
+    InstantAppInfo createInstantAppInfoForPackage(
+            @NonNull PackageParser.Package pkg, @UserIdInt int userId,
+            boolean addApplicationInfo) {
+        PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return null;
+        }
+        if (!ps.getInstalled(userId)) {
+            return null;
+        }
+
+        String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
+        pkg.requestedPermissions.toArray(requestedPermissions);
+
+        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
+        String[] grantedPermissions = new String[permissions.size()];
+        permissions.toArray(grantedPermissions);
+
+        if (addApplicationInfo) {
+            return new InstantAppInfo(pkg.applicationInfo,
+                    requestedPermissions, grantedPermissions);
+        } else {
+            return new InstantAppInfo(pkg.applicationInfo.packageName,
+                    pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
+                    requestedPermissions, grantedPermissions);
+        }
+    }
+
+    private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
+            @UserIdInt int userId) {
+        List<UninstalledInstantAppState> uninstalledAppStates =
+                getUninstalledInstantAppStatesLPr(userId);
+        if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
+            return null;
+        }
+
+        List<InstantAppInfo> uninstalledApps = null;
+        final int stateCount = uninstalledAppStates.size();
+        for (int i = 0; i < stateCount; i++) {
+            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
+            if (uninstalledApps == null) {
+                uninstalledApps = new ArrayList<>();
+            }
+            uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
+        }
+        return uninstalledApps;
+    }
+
+    private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
+            @UserIdInt int userId) {
+        InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
+                packageName, userId);
+        if (appInfo == null) {
+            return;
+        }
+        if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
+            return;
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            for (String grantedPermission : appInfo.getGrantedPermissions()) {
+                BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
+                if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
+                    mService.grantRuntimePermission(packageName, grantedPermission, userId);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private @NonNull
+    InstantAppInfo peekOrParseUninstalledInstantAppInfo(
+            @NonNull String packageName, @UserIdInt int userId) {
+        if (mUninstalledInstantApps != null) {
+            List<UninstalledInstantAppState> uninstalledAppStates =
+                    mUninstalledInstantApps.get(userId);
+            if (uninstalledAppStates != null) {
+                final int appCount = uninstalledAppStates.size();
+                for (int i = 0; i < appCount; i++) {
+                    UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
+                    if (uninstalledAppState.mInstantAppInfo
+                            .getPackageName().equals(packageName)) {
+                        return uninstalledAppState.mInstantAppInfo;
+                    }
+                }
+            }
+        }
+
+        File metadataFile = new File(getInstantApplicationDir(packageName, userId),
+                INSTANT_APP_METADATA_FILE);
+        UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
+        if (uninstalledAppState == null) {
+            return null;
+        }
+
+        return uninstalledAppState.mInstantAppInfo;
+    }
+
+    private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
+            @UserIdInt int userId) {
+        List<UninstalledInstantAppState> uninstalledAppStates = null;
+        if (mUninstalledInstantApps != null) {
+            uninstalledAppStates = mUninstalledInstantApps.get(userId);
+            if (uninstalledAppStates != null) {
+                return uninstalledAppStates;
+            }
+        }
+
+        File instantAppsDir = getInstantApplicationsDir(userId);
+        if (instantAppsDir.exists()) {
+            File[] files = instantAppsDir.listFiles();
+            if (files != null) {
+                for (File instantDir : files) {
+                    if (!instantDir.isDirectory()) {
+                        continue;
+                    }
+                    File metadataFile = new File(instantDir,
+                            INSTANT_APP_METADATA_FILE);
+                    UninstalledInstantAppState uninstalledAppState =
+                            parseMetadataFile(metadataFile);
+                    if (uninstalledAppState == null) {
+                        continue;
+                    }
+                    if (uninstalledAppStates == null) {
+                        uninstalledAppStates = new ArrayList<>();
+                    }
+                    uninstalledAppStates.add(uninstalledAppState);
+                }
+            }
+        }
+
+        if (uninstalledAppStates != null) {
+            if (mUninstalledInstantApps == null) {
+                mUninstalledInstantApps = new SparseArray<>();
+            }
+            mUninstalledInstantApps.put(userId, uninstalledAppStates);
+        }
+
+        return uninstalledAppStates;
+    }
+
+    private static @Nullable UninstalledInstantAppState parseMetadataFile(
+            @NonNull File metadataFile) {
+        if (!metadataFile.exists()) {
+            return null;
+        }
+        FileInputStream in;
+        try {
+            in = new AtomicFile(metadataFile).openRead();
+        } catch (FileNotFoundException fnfe) {
+            Slog.i(LOG_TAG, "No instant metadata file");
+            return null;
+        }
+
+        final File instantDir = metadataFile.getParentFile();
+        final long timestamp = metadataFile.lastModified();
+        final String packageName = instantDir.getName();
+
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, StandardCharsets.UTF_8.name());
+            return new UninstalledInstantAppState(
+                    parseMetadata(parser, packageName), timestamp);
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed parsing instant"
+                    + " metadata file: " + metadataFile, e);
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
+            @UserIdInt int userId) {
+        File appDir = getInstantApplicationDir(pkg.packageName, userId);
+        String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
+                pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
+        return new File(appDir, cookieFile);
+    }
+
+    private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
+            @UserIdInt int userId) {
+        File appDir = getInstantApplicationDir(packageName, userId);
+        if (!appDir.exists()) {
+            return null;
+        }
+        File[] files = appDir.listFiles();
+        if (files == null) {
+            return null;
+        }
+        for (File file : files) {
+            if (!file.isDirectory()
+                    && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
+                    && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
+                return file;
+            }
+        }
+        return null;
+    }
+
+    private static @Nullable
+    InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
+                                 @NonNull String packageName)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (TAG_PACKAGE.equals(parser.getName())) {
+                return parsePackage(parser, packageName);
+            }
+        }
+        return null;
+    }
+
+    private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
+                                               @NonNull String packageName)
+            throws IOException, XmlPullParserException {
+        String label = parser.getAttributeValue(null, ATTR_LABEL);
+
+        List<String> outRequestedPermissions = new ArrayList<>();
+        List<String> outGrantedPermissions = new ArrayList<>();
+
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (TAG_PERMISSIONS.equals(parser.getName())) {
+                parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
+            }
+        }
+
+        String[] requestedPermissions = new String[outRequestedPermissions.size()];
+        outRequestedPermissions.toArray(requestedPermissions);
+
+        String[] grantedPermissions = new String[outGrantedPermissions.size()];
+        outGrantedPermissions.toArray(grantedPermissions);
+
+        return new InstantAppInfo(packageName, label,
+                requestedPermissions, grantedPermissions);
+    }
+
+    private static void parsePermissions(@NonNull XmlPullParser parser,
+            @NonNull List<String> outRequestedPermissions,
+            @NonNull List<String> outGrantedPermissions)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser,outerDepth)) {
+            if (TAG_PERMISSION.equals(parser.getName())) {
+                String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+                outRequestedPermissions.add(permission);
+                if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
+                    outGrantedPermissions.add(permission);
+                }
+            }
+        }
+    }
+
+    private void writeUninstalledInstantAppMetadata(
+            @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
+        File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
+        if (!appDir.exists() && !appDir.mkdirs()) {
+            return;
+        }
+
+        File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
+
+        AtomicFile destination = new AtomicFile(metadataFile);
+        FileOutputStream out = null;
+        try {
+            out = destination.startWrite();
+
+            XmlSerializer serializer = Xml.newSerializer();
+            serializer.setOutput(out, StandardCharsets.UTF_8.name());
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startDocument(null, true);
+
+            serializer.startTag(null, TAG_PACKAGE);
+            serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
+                    mService.mContext.getPackageManager()).toString());
+
+            serializer.startTag(null, TAG_PERMISSIONS);
+            for (String permission : instantApp.getRequestedPermissions()) {
+                serializer.startTag(null, TAG_PERMISSION);
+                serializer.attribute(null, ATTR_NAME, permission);
+                if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
+                    serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
+                }
+                serializer.endTag(null, TAG_PERMISSION);
+            }
+            serializer.endTag(null, TAG_PERMISSIONS);
+
+            serializer.endTag(null, TAG_PACKAGE);
+
+            serializer.endDocument();
+            destination.finishWrite(out);
+        } catch (Throwable t) {
+            Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
+            destination.failWrite(out);
+        } finally {
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private static @NonNull File getInstantApplicationsDir(int userId) {
+        return new File(Environment.getUserSystemDirectory(userId),
+                INSTANT_APPS_FOLDER);
+    }
+
+    private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
+        return new File (getInstantApplicationsDir(userId), packageName);
+    }
+
+    private static void deleteDir(@NonNull File dir) {
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                deleteDir(file);
+            }
+        }
+        dir.delete();
+    }
+
+    private static final class UninstalledInstantAppState {
+        final InstantAppInfo mInstantAppInfo;
+        final long mTimestamp;
+
+        public UninstalledInstantAppState(InstantAppInfo instantApp,
+                long timestamp) {
+            mInstantAppInfo = instantApp;
+            mTimestamp = timestamp;
+        }
+    }
+
+    private final class CookiePersistence extends Handler {
+        private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
+
+        // In case you wonder why we stash the cookies aside, we use
+        // the user id for the message id and the package for the payload.
+        // Handler allows removing messages by id and tag where the
+        // tag is is compared using ==. So to allow cancelling the
+        // pending persistence for an app under a given user we use
+        // the fact that package names are interned in the system
+        // process so the == comparison would match and we end up
+        // with a way to cancel persisting the cookie for a user
+        // and package.
+        private final SparseArray<ArrayMap<String, byte[]>> mPendingPersistCookies =
+                new SparseArray<>();
+
+        public CookiePersistence(Looper looper) {
+            super(looper);
+        }
+
+        public void schedulePersist(@UserIdInt int userId,
+                @NonNull String packageName, @NonNull byte[] cookie) {
+            cancelPendingPersist(userId, packageName);
+            addPendingPersistCookie(userId, packageName, cookie);
+            sendMessageDelayed(obtainMessage(userId, packageName),
+                    PERSIST_COOKIE_DELAY_MILLIS);
+        }
+
+        public @Nullable byte[] getPendingPersistCookie(@UserIdInt int userId,
+                @NonNull String packageName) {
+            ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId);
+            if (pendingWorkForUser != null) {
+                return pendingWorkForUser.remove(packageName);
+            }
+            return null;
+        }
+
+        private void cancelPendingPersist(@UserIdInt int userId,
+                @NonNull String packageName) {
+            removePendingPersistCookie(userId, packageName);
+            removeMessages(userId, packageName);
+        }
+
+        private void addPendingPersistCookie(@UserIdInt int userId,
+                @NonNull String packageName, @NonNull byte[] cookie) {
+            ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId);
+            if (pendingWorkForUser == null) {
+                pendingWorkForUser = new ArrayMap<>();
+                mPendingPersistCookies.put(userId, pendingWorkForUser);
+            }
+            pendingWorkForUser.put(packageName, cookie);
+        }
+
+        private byte[] removePendingPersistCookie(@UserIdInt int userId,
+                @NonNull String packageName) {
+            ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId);
+            byte[] cookie = null;
+            if (pendingWorkForUser != null) {
+                cookie = pendingWorkForUser.remove(packageName);
+                if (pendingWorkForUser.isEmpty()) {
+                    mPendingPersistCookies.remove(userId);
+                }
+            }
+            return cookie;
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            int userId = message.what;
+            String packageName = (String) message.obj;
+            byte[] cookie = removePendingPersistCookie(userId, packageName);
+            persistInstantApplicationCookie(cookie, packageName, userId);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index da6a67e..efd3132 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -225,8 +225,8 @@
         synchronized (mSessions) {
             readSessionsLocked();
 
-            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isEphemeral*/);
-            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isEphemeral*/);
+            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isInstant*/);
+            reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isInstant*/);
 
             final ArraySet<File> unclaimedIcons = newArraySet(
                     mSessionsDir.listFiles());
@@ -271,7 +271,7 @@
 
     public void onPrivateVolumeMounted(String volumeUuid) {
         synchronized (mSessions) {
-            reconcileStagesLocked(volumeUuid, false /*isEphemeral*/);
+            reconcileStagesLocked(volumeUuid, false /*isInstant*/);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4a426bd..add2c66 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -127,7 +127,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AppsQueryHelper;
 import android.content.pm.ComponentInfo;
-import android.content.pm.EphemeralApplicationInfo;
+import android.content.pm.InstantAppInfo;
 import android.content.pm.EphemeralRequest;
 import android.content.pm.EphemeralResolveInfo;
 import android.content.pm.EphemeralResponse;
@@ -255,6 +255,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.AttributeCache;
+import com.android.server.BackgroundDexOptJobService;
 import com.android.server.EventLogTags;
 import com.android.server.FgThread;
 import com.android.server.IntentResolver;
@@ -375,7 +376,7 @@
     // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
     // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
     // user, but by default initialize to this.
-    static final boolean DEBUG_DEXOPT = false;
+    public static final boolean DEBUG_DEXOPT = false;
 
     private static final boolean DEBUG_ABI_SELECTION = false;
     private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
@@ -386,7 +387,7 @@
     static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
 
     private static final boolean DISABLE_EPHEMERAL_APPS = false;
-    private static final boolean HIDE_EPHEMERAL_APIS = true;
+    private static final boolean HIDE_EPHEMERAL_APIS = false;
 
     private static final boolean ENABLE_QUOTA =
             SystemProperties.getBoolean("persist.fw.quota", false);
@@ -716,7 +717,7 @@
     // If mac_permissions.xml was found for seinfo labeling.
     boolean mFoundPolicyFile;
 
-    private final EphemeralApplicationRegistry mEphemeralApplicationRegistry;
+    private final InstantAppRegistry mInstantAppRegistry;
 
     public static final class SharedLibraryEntry {
         public final String path;
@@ -1751,7 +1752,7 @@
             }
 
             synchronized (mPackages) {
-                mEphemeralApplicationRegistry.onPackageInstalledLPw(res.pkg);
+                mInstantAppRegistry.onPackageInstalledLPw(res.pkg, res.newUsers);
             }
 
             final String packageName = res.pkg.applicationInfo.packageName;
@@ -2255,7 +2256,7 @@
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
 
             mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);
-            mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this);
+            mInstantAppRegistry = new InstantAppRegistry(this);
 
             File dataDir = Environment.getDataDirectory();
             mAppInstallDir = new File(dataDir, "app");
@@ -3303,10 +3304,13 @@
         //   * An installed app can see metadata for 1) other installed apps
         //     and 2) ephemeral apps that have explicitly interacted with it
         //   * Ephemeral apps can only see their own metadata
+        //   * Holding a signature permission allows seeing instant apps
         final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
         if (callingAppId != Process.SYSTEM_UID
                 && callingAppId != Process.SHELL_UID
-                && callingAppId != Process.ROOT_UID) {
+                && callingAppId != Process.ROOT_UID
+                && checkUidPermission(Manifest.permission.ACCESS_INSTANT_APPS,
+                        Binder.getCallingUid()) != PackageManager.PERMISSION_GRANTED) {
             final String ephemeralPackageName = getEphemeralPackageName(Binder.getCallingUid());
             if (ephemeralPackageName != null) {
                 // ephemeral apps can only get information on themselves
@@ -3314,9 +3318,9 @@
                     return null;
                 }
             } else {
-                if (p.applicationInfo.isEphemeralApp()) {
+                if (p.applicationInfo.isInstantApp()) {
                     // only get access to the ephemeral app if we've been granted access
-                    if (!mEphemeralApplicationRegistry.isEphemeralAccessGranted(
+                    if (!mInstantAppRegistry.isInstantAccessGranted(
                             userId, callingAppId, ps.appId)) {
                         return null;
                     }
@@ -4617,7 +4621,7 @@
                 return;
             }
 
-            if (pkg.applicationInfo.isEphemeralApp() && !bp.isEphemeral()) {
+            if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
                 throw new SecurityException("Cannot grant non-ephemeral permission"
                         + name + " for package " + packageName);
             }
@@ -5912,7 +5916,7 @@
         CrossProfileIntentResolver resolver = mSettings.mCrossProfileIntentResolvers.get(userId);
         if (resolver != null) {
             return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/,
-                    false /*visibleToEphemeral*/, false /*isEphemeral*/, userId);
+                    false /*visibleToEphemeral*/, false /*isInstant*/, userId);
         }
         return null;
     }
@@ -5940,7 +5944,7 @@
             final Object obj = mSettings.getUserIdLPr(appId);
             if (obj instanceof PackageSetting) {
                 final PackageSetting ps = (PackageSetting) obj;
-                return ps.pkg.applicationInfo.isEphemeralApp() ? ps.pkg.packageName : null;
+                return ps.pkg.applicationInfo.isInstantApp() ? ps.pkg.packageName : null;
             }
         }
         return null;
@@ -6218,7 +6222,7 @@
         }
         for (int i = resolveInfos.size() - 1; i >= 0; i--) {
             ResolveInfo info = resolveInfos.get(i);
-            final boolean isEphemeralApp = info.activityInfo.applicationInfo.isEphemeralApp();
+            final boolean isEphemeralApp = info.activityInfo.applicationInfo.isInstantApp();
             // allow activities that are defined in the provided package
             if (isEphemeralApp && ephemeralPkgName.equals(info.activityInfo.packageName)) {
                 continue;
@@ -7034,31 +7038,31 @@
     }
 
     @Override
-    public ParceledListSlice<EphemeralApplicationInfo> getEphemeralApplications(int userId) {
+    public ParceledListSlice<InstantAppInfo> getInstantApps(int userId) {
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
             return null;
         }
 
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS,
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
                 "getEphemeralApplications");
         enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getEphemeralApplications");
         synchronized (mPackages) {
-            List<EphemeralApplicationInfo> ephemeralApps = mEphemeralApplicationRegistry
-                    .getEphemeralApplicationsLPw(userId);
-            if (ephemeralApps != null) {
-                return new ParceledListSlice<>(ephemeralApps);
+            List<InstantAppInfo> instantApps = mInstantAppRegistry
+                    .getInstantAppsLPr(userId);
+            if (instantApps != null) {
+                return new ParceledListSlice<>(instantApps);
             }
         }
         return null;
     }
 
     @Override
-    public boolean isEphemeralApplication(String packageName, int userId) {
+    public boolean isInstantApp(String packageName, int userId) {
         enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
-                "isEphemeral");
+                "isInstantApp");
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
             return false;
         }
@@ -7069,62 +7073,63 @@
         synchronized (mPackages) {
             PackageParser.Package pkg = mPackages.get(packageName);
             if (pkg != null) {
-                return pkg.applicationInfo.isEphemeralApp();
+                return pkg.applicationInfo.isInstantApp();
             }
         }
         return false;
     }
 
     @Override
-    public byte[] getEphemeralApplicationCookie(String packageName, int userId) {
+    public byte[] getInstantAppCookie(String packageName, int userId) {
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
             return null;
         }
 
         enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
-                "getCookie");
+                "getInstantAppCookie");
         if (!isCallerSameApp(packageName)) {
             return null;
         }
         synchronized (mPackages) {
-            return mEphemeralApplicationRegistry.getEphemeralApplicationCookieLPw(
+            return mInstantAppRegistry.getInstantAppCookieLPw(
                     packageName, userId);
         }
     }
 
     @Override
-    public boolean setEphemeralApplicationCookie(String packageName, byte[] cookie, int userId) {
+    public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId) {
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
             return true;
         }
 
         enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, true /* checkShell */,
-                "setCookie");
+                "setInstantAppCookie");
         if (!isCallerSameApp(packageName)) {
             return false;
         }
         synchronized (mPackages) {
-            return mEphemeralApplicationRegistry.setEphemeralApplicationCookieLPw(
+            return mInstantAppRegistry.setInstantAppCookieLPw(
                     packageName, cookie, userId);
         }
     }
 
     @Override
-    public Bitmap getEphemeralApplicationIcon(String packageName, int userId) {
+    public Bitmap getInstantAppIcon(String packageName, int userId) {
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
             return null;
         }
 
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS,
-                "getEphemeralApplicationIcon");
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
+                "getInstantAppIcon");
 
         enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
-                "getEphemeralApplicationIcon");
+                "getInstantAppIcon");
+
         synchronized (mPackages) {
-            return mEphemeralApplicationRegistry.getEphemeralApplicationIconLPw(
+            return mInstantAppRegistry.getInstantAppIconLPw(
                     packageName, userId);
         }
     }
@@ -9345,7 +9350,7 @@
                     (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
             if (isEphemeral(pkg)) {
                 final int userId = user == null ? 0 : user.getIdentifier();
-                mEphemeralApplicationRegistry.addEphemeralAppLPw(userId, pkgSetting.appId);
+                mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
             }
         }
         return pkg;
@@ -9460,7 +9465,7 @@
                 }
 
                 // Package declaring static a shared lib cannot be ephemeral
-                if (pkg.applicationInfo.isEphemeralApp()) {
+                if (pkg.applicationInfo.isInstantApp()) {
                     throw new PackageManagerException(
                             "Packages declaring static-shared libs cannot be ephemeral");
                 }
@@ -9994,7 +9999,7 @@
                 PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
                 final String curPackageName = cur == null ? null : cur.info.packageName;
                 // Dont allow ephemeral apps to define new permission groups.
-                if (pkg.applicationInfo.isEphemeralApp()) {
+                if (pkg.applicationInfo.isInstantApp()) {
                     Slog.w(TAG, "Permission group " + pg.info.name + " from package "
                             + pg.info.packageName
                             + " ignored: ephemeral apps cannot define new permission groups.");
@@ -10039,7 +10044,7 @@
                 PackageParser.Permission p = pkg.permissions.get(i);
 
                 // Dont allow ephemeral apps to define new permissions.
-                if (pkg.applicationInfo.isEphemeralApp()) {
+                if (pkg.applicationInfo.isInstantApp()) {
                     Slog.w(TAG, "Permission " + p.info.name + " from package "
                             + p.info.packageName
                             + " ignored: ephemeral apps cannot define new permissions.");
@@ -11242,7 +11247,7 @@
 
 
             // Limit ephemeral apps to ephemeral allowed permissions.
-            if (pkg.applicationInfo.isEphemeralApp() && !bp.isEphemeral()) {
+            if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
                 Log.i(TAG, "Denying non-ephemeral permission " + bp.name + " for package "
                         + pkg.packageName);
                 continue;
@@ -15752,7 +15757,7 @@
             }
 
             // don't allow an upgrade from full to ephemeral
-            final boolean oldIsEphemeral = oldPackage.applicationInfo.isEphemeralApp();
+            final boolean oldIsEphemeral = oldPackage.applicationInfo.isInstantApp();
             if (isEphemeral && !oldIsEphemeral) {
                 // can't downgrade from full to ephemeral
                 Slog.w(TAG, "Can't replace app with ephemeral: " + pkgName);
@@ -16776,10 +16781,11 @@
                     getOrCreateCompilerPackageStats(pkg));
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
-            // Notify BackgroundDexOptService that the package has been changed.
+            // Notify BackgroundDexOptJobService that the package has been changed.
             // If this is an update of a package which used to fail to compile,
             // BDOS will remove it from its blacklist.
-            BackgroundDexOptService.notifyPackageChanged(pkg.packageName);
+            // TODO: Layering violation
+            BackgroundDexOptJobService.notifyPackageChanged(pkg.packageName);
         }
 
         if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
@@ -16970,7 +16976,7 @@
     }
 
     private static boolean isEphemeral(PackageParser.Package pkg) {
-        return pkg.applicationInfo.isEphemeralApp();
+        return pkg.applicationInfo.isInstantApp();
     }
 
     private static boolean isEphemeral(PackageSetting ps) {
@@ -17407,7 +17413,8 @@
             }
             synchronized (mPackages) {
                 if (res) {
-                    mEphemeralApplicationRegistry.onPackageUninstalledLPw(uninstalledPs.pkg);
+                    mInstantAppRegistry.onPackageUninstalledLPw(uninstalledPs.pkg,
+                            info.removedUsers);
                 }
             }
         }
@@ -18260,6 +18267,10 @@
                         succeeded = clearApplicationUserDataLIF(packageName, userId);
                     }
                     clearExternalStorageDataSync(packageName, userId, true);
+                    synchronized (mPackages) {
+                        mInstantAppRegistry.deleteInstantApplicationMetadataLPw(
+                                packageName, userId);
+                    }
                 }
                 if (succeeded) {
                     // invoke DeviceStorageMonitor's update method to clear any notifications
@@ -22165,7 +22176,7 @@
             mUserNeedsBadging.delete(userHandle);
             mSettings.removeUserLPw(userHandle);
             mPendingBroadcasts.remove(userHandle);
-            mEphemeralApplicationRegistry.onUserRemovedLPw(userHandle);
+            mInstantAppRegistry.onUserRemovedLPw(userHandle);
             removeUnusedPackagesLPw(userManager, userHandle);
         }
     }
@@ -22746,7 +22757,7 @@
         public boolean isPackageEphemeral(int userId, String packageName) {
             synchronized (mPackages) {
                 PackageParser.Package p = mPackages.get(packageName);
-                return p != null ? p.applicationInfo.isEphemeralApp() : false;
+                return p != null ? p.applicationInfo.isInstantApp() : false;
             }
         }
 
@@ -22788,11 +22799,19 @@
         public void grantEphemeralAccess(int userId, Intent intent,
                 int targetAppId, int ephemeralAppId) {
             synchronized (mPackages) {
-                mEphemeralApplicationRegistry.grantEphemeralAccessLPw(userId, intent,
+                mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
                         targetAppId, ephemeralAppId);
             }
         }
 
+        @Override
+        public void pruneInstantApps() {
+            synchronized (mPackages) {
+                mInstantAppRegistry.pruneInstantAppsLPw();
+            }
+        }
+
+        @Override
         public String getSetupWizardPackageName() {
             return mSetupWizardPackage;
         }
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index b9bf1db..2781150 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -287,7 +287,7 @@
             }
         }
 
-        if (pkg.applicationInfo.isEphemeralApp())
+        if (pkg.applicationInfo.isInstantApp())
             pkg.applicationInfo.seinfo += EPHEMERAL_APP_STR;
 
         if (pkg.applicationInfo.targetSandboxVersion == 2)
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c0fc24c..d8857b7 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3081,7 +3081,7 @@
     }
 
     private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) {
-        return (ai != null) && ai.isEphemeralApp();
+        return (ai != null) && ai.isInstantApp();
     }
 
     private static boolean isInstalled(@Nullable PackageInfo pi) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e4eaf4a..70b0bf2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -83,7 +83,6 @@
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.SchedulingPolicyService;
-import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.Installer;
 import com.android.server.pm.LauncherAppsService;
 import com.android.server.pm.OtaDexoptService;
@@ -1380,11 +1379,19 @@
                     traceEnd();
                 }
 
-                traceBeginAndSlog("StartBackgroundDexOptService");
+                traceBeginAndSlog("StartBackgroundDexOptJobService");
                 try {
-                    BackgroundDexOptService.schedule(context);
+                    BackgroundDexOptJobService.schedule(context);
                 } catch (Throwable e) {
-                    reportWtf("starting BackgroundDexOptService", e);
+                    reportWtf("starting StartBackgroundDexOptJobService", e);
+                }
+                traceEnd();
+
+                traceBeginAndSlog("StartPruneInstantAppsJobService");
+                try {
+                    PruneInstantAppsJobService.schedule(context);
+                } catch (Throwable e) {
+                    reportWtf("StartPruneInstantAppsJobService", e);
                 }
                 traceEnd();
             }