Pin compiled code of HOME app

Keeping the code in memory of the currently set home app is
important for latency as we don't have any kind of starting
window/splash screen when pressing the home app to hide any latency.

Memory impact:

Pinning dex/vdex:

In practical scenarios, this should be < 500kb.
The home app is usually profile-speed compiled, for which the
resulting dex/vdex files are about 2 mb. However, during regular
use, at least 1.5 MB of it is referenced in memory. This makes
sense: By definition profile-speed only compiles the things that
is usually frequently executed during regular execution.

Pinning apk:
With Launcher 3 in practical scenarios this should be about 3.7 MB,
as the APK is about 5.7 MB but 2 MB are usually referenced in any
case.

Bug: 111132016
Bug: 78585335
Test: Inspect "adb shell dumpsys pinner" after boot.
Test: Check for pinned files after updating camera/home.
Test: Check for pinned files after user switch with different
default apps.
Test: Check for pinned files after bg-dexopt.
Test: Check for pinned files after bg-dexopt + kill pid.

Change-Id: I6cdbc06d089efeb1c72a51216879ba0573502009
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7a9a553..2e4404c 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -223,6 +223,12 @@
      * intercept activity launches for work apps when the Work Challenge is present.
      */
     public abstract boolean shouldConfirmCredentials(int userId);
+
+    /**
+     * @return The intent used to launch the home activity.
+     */
+    public abstract Intent getHomeIntent();
+
     public abstract int[] getCurrentProfileIds();
     public abstract UserInfo getCurrentUser();
     public abstract void ensureNotSpecialUser(int userId);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e4ce2b1..ee81c7c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3182,6 +3182,9 @@
     <!-- True if camera app should be pinned via Pinner Service -->
     <bool name="config_pinnerCameraApp">false</bool>
 
+    <!-- True if home app should be pinned via Pinner Service -->
+    <bool name="config_pinnerHomeApp">false</bool>
+
     <!-- Number of days preloaded file cache should be preserved on a device before it can be
          deleted -->
     <integer name="config_keepPreloadsMinDays">7</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6a58b67..3d2ebf3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2922,6 +2922,7 @@
   <!-- Pinner Service -->
   <java-symbol type="array" name="config_defaultPinnerServiceFiles" />
   <java-symbol type="bool" name="config_pinnerCameraApp" />
+  <java-symbol type="bool" name="config_pinnerHomeApp" />
 
   <java-symbol type="string" name="config_doubleTouchGestureEnableFile" />
 
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 5a25f48..cec2028 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -16,8 +16,16 @@
 
 package com.android.server;
 
+import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
+import static android.app.ActivityManager.UID_OBSERVER_GONE;
+
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,18 +40,21 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.system.StructStat;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import dalvik.system.DexFile;
 import dalvik.system.VMRuntime;
@@ -53,12 +64,12 @@
 import java.io.InputStream;
 import java.io.DataInputStream;
 import java.io.IOException;
-import java.io.EOFException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 import java.util.zip.ZipFile;
-import java.util.zip.ZipException;
 import java.util.zip.ZipEntry;
 
 /**
@@ -70,16 +81,50 @@
 public final class PinnerService extends SystemService {
     private static final boolean DEBUG = false;
     private static final String TAG = "PinnerService";
-    private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max
+
     private static final String PIN_META_FILENAME = "pinlist.meta";
     private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
+    private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
+            | PackageManager.MATCH_DIRECT_BOOT_AWARE
+            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+    private static final int KEY_CAMERA = 0;
+    private static final int KEY_HOME = 1;
+
+    private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
+    private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
+
+    @IntDef({KEY_CAMERA, KEY_HOME})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AppKey {}
 
     private final Context mContext;
-    private final boolean mShouldPinCamera;
+    private final ActivityManagerInternal mAmInternal;
+    private final IActivityManager mAm;
 
-    /* These lists protected by PinnerService monitor lock */
-    private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>();
-    private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>();
+    /** The list of the statically pinned files. */
+    @GuardedBy("this")
+    private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
+
+    /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
+    @GuardedBy("this")
+    private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
+
+    /**
+     * The list of the pinned apps that need to be repinned as soon as the all processes of a given
+     * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
+     * loaded into the processes once it restarts. So in case background dex opt recompiled these
+     * files, we still need to keep the old ones pinned until the processes restart.
+     * <p>
+     * This is a map from uid to {@link AppKey}
+     */
+    @GuardedBy("this")
+    private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
+
+    /**
+     * A set of {@link AppKey} that are configured to be pinned.
+     */
+    private final ArraySet<Integer> mPinKeys = new ArraySet<>();
 
     private BinderService mBinderService;
     private PinnerHandler mPinnerHandler = null;
@@ -87,13 +132,13 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-          // If this user's camera app has been updated, update pinned files accordingly.
-          if (intent.getAction() == Intent.ACTION_PACKAGE_REPLACED) {
+          // If an app has updated, update pinned files accordingly.
+          if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
                 Uri packageUri = intent.getData();
                 String packageName = packageUri.getSchemeSpecificPart();
                 ArraySet<String> updatedPackages = new ArraySet<>();
                 updatedPackages.add(packageName);
-                update(updatedPackages);
+                update(updatedPackages, true /* force */);
             }
         }
     };
@@ -102,14 +147,26 @@
         super(context);
 
         mContext = context;
-        mShouldPinCamera = context.getResources().getBoolean(
+        boolean shouldPinCamera = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_pinnerCameraApp);
+        boolean shouldPinHome = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_pinnerHomeApp);
+        if (shouldPinCamera) {
+            mPinKeys.add(KEY_CAMERA);
+        }
+        if (shouldPinHome) {
+            mPinKeys.add(KEY_HOME);
+        }
         mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
 
+        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mAm = ActivityManager.getService();
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         filter.addDataScheme("package");
         mContext.registerReceiver(mBroadcastReceiver, filter);
+        registerUidListener();
     }
 
     @Override
@@ -122,32 +179,39 @@
         publishLocalService(PinnerService.class, this);
 
         mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
-        mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, UserHandle.USER_SYSTEM, 0)
-                .sendToTarget();
+        sendPinAppsMessage(UserHandle.USER_SYSTEM);
     }
 
     /**
-     * Pin camera on user switch.
-     * If more than one user is using the device
-     * each user may set a different preference for the camera app.
-     * Make sure that user's preference is pinned into memory.
+     * Repin apps on user switch.
+     * <p>
+     * If more than one user is using the device each user may set a different preference for the
+     * individual apps. Make sure that user's preference is pinned into memory.
      */
     @Override
     public void onSwitchUser(int userHandle) {
-        mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, userHandle, 0).sendToTarget();
+        sendPinAppsMessage(userHandle);
+    }
+
+    @Override
+    public void onUnlockUser(int userHandle) {
+        sendPinAppsMessage(userHandle);
     }
 
     /**
      * Update the currently pinned files.
-     * Specifically, this only updates camera pinning.
+     * Specifically, this only updates pinning for the apps that need to be pinned.
      * The other files pinned in onStart will not need to be updated.
      */
-    public void update(ArraySet<String> updatedPackages) {
-        ApplicationInfo cameraInfo = getCameraInfo(UserHandle.USER_SYSTEM);
-        if (cameraInfo != null && updatedPackages.contains(cameraInfo.packageName)) {
-            Slog.i(TAG, "Updating pinned files.");
-            mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, UserHandle.USER_SYSTEM, 0)
-                    .sendToTarget();
+    public void update(ArraySet<String> updatedPackages, boolean force) {
+        int currentUser = ActivityManager.getCurrentUser();
+        for (int i = mPinKeys.size() - 1; i >= 0; i--) {
+            int key = mPinKeys.valueAt(i);
+            ApplicationInfo info = getInfoForKey(key, currentUser);
+            if (info != null && updatedPackages.contains(info.packageName)) {
+                Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
+                sendPinAppMessage(key, currentUser, force);
+            }
         }
     }
 
@@ -174,25 +238,80 @@
         }
     }
 
-    /**
-     * Handler for camera pinning message
-     */
-    private void handlePinCamera(int userHandle) {
-        if (!mShouldPinCamera) return;
-        if (!pinCamera(userHandle)) {
-            if (DEBUG) {
-                Slog.v(TAG, "Failed to pin camera.");
+    private void registerUidListener() {
+        try {
+            mAm.registerUidObserver(new IUidObserver.Stub() {
+                @Override
+                public void onUidGone(int uid, boolean disabled) throws RemoteException {
+                    mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
+                            PinnerService::handleUidGone, PinnerService.this, uid));
+                }
+
+                @Override
+                public void onUidActive(int uid) throws RemoteException {
+                    mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
+                            PinnerService::handleUidActive, PinnerService.this, uid));
+                }
+
+                @Override
+                public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+                }
+
+                @Override
+                public void onUidStateChanged(int uid, int procState, long procStateSeq)
+                        throws RemoteException {
+                }
+
+                @Override
+                public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
+                }
+            }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system");
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to register uid observer", e);
+        }
+    }
+
+    private void handleUidGone(int uid) {
+        updateActiveState(uid, false /* active */);
+        int key;
+        synchronized (this) {
+
+            // In case we have a pending repin, repin now. See mPendingRepin for more information.
+            key = mPendingRepin.getOrDefault(uid, -1);
+            if (key == -1) {
+                return;
+            }
+            mPendingRepin.remove(uid);
+        }
+        pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
+    }
+
+    private void handleUidActive(int uid) {
+        updateActiveState(uid, true /* active */);
+    }
+
+    private void updateActiveState(int uid, boolean active) {
+        synchronized (this) {
+            for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
+                PinnedApp app = mPinnedApps.valueAt(i);
+                if (app.uid == uid) {
+                    app.active = active;
+                }
             }
         }
     }
 
-    private void unpinCameraApp() {
-        ArrayList<PinnedFile> pinnedCameraFiles;
+    private void unpinApp(@AppKey int key) {
+        ArrayList<PinnedFile> pinnedAppFiles;
         synchronized (this) {
-            pinnedCameraFiles = new ArrayList<>(mPinnedCameraFiles);
-            mPinnedCameraFiles.clear();
+            PinnedApp app = mPinnedApps.get(key);
+            if (app == null) {
+                return;
+            }
+            mPinnedApps.remove(key);
+            pinnedAppFiles = new ArrayList<>(app.mFiles);
         }
-        for (PinnedFile pinnedFile : pinnedCameraFiles) {
+        for (PinnedFile pinnedFile : pinnedAppFiles) {
             pinnedFile.close();
         }
     }
@@ -206,64 +325,167 @@
         //  use INTENT_ACTION_STILL_IMAGE_CAMERA instead of _SECURE.  On a
         //  device without a fbe enabled, the _SECURE intent will never get set.
         Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
-        PackageManager pm = mContext.getPackageManager();
-        ResolveInfo cameraResolveInfo = pm.resolveActivityAsUser(cameraIntent,
-                PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                userHandle);
-        if (cameraResolveInfo == null ) {
-            //this is not necessarily an error
-            if (DEBUG) {
-              Slog.v(TAG, "Unable to resolve camera intent");
-            }
+        return getApplicationInfoForIntent(cameraIntent, userHandle);
+    }
+
+    private ApplicationInfo getHomeInfo(int userHandle) {
+        Intent intent = mAmInternal.getHomeIntent();
+        return getApplicationInfoForIntent(intent, userHandle);
+    }
+
+    private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle) {
+        if (intent == null) {
             return null;
         }
-
-        if (isResolverActivity(cameraResolveInfo.activityInfo))
-        {
-            if (DEBUG) {
-              Slog.v(TAG, "cameraIntent returned resolverActivity");
-            }
+        ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser(intent,
+                MATCH_FLAGS, userHandle);
+        if (info == null) {
             return null;
         }
+        if (isResolverActivity(info.activityInfo)) {
+            return null;
+        }
+        return info.activityInfo.applicationInfo;
+    }
 
-        return cameraResolveInfo.activityInfo.applicationInfo;
+    private void sendPinAppsMessage(int userHandle) {
+        mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
+                userHandle));
+    }
+
+    private void pinApps(int userHandle) {
+        for (int i = mPinKeys.size() - 1; i >= 0; i--) {
+            int key = mPinKeys.valueAt(i);
+            pinApp(key, userHandle, true /* force */);
+        }
     }
 
     /**
-     * If the camera app is already pinned, unpin and repin it.
+     * @see #pinApp(int, int, boolean)
      */
-    private boolean pinCamera(int userHandle){
-        ApplicationInfo cameraInfo = getCameraInfo(userHandle);
-        if (cameraInfo == null) {
-            return false;
+    private void sendPinAppMessage(int key, int userHandle, boolean force) {
+        mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
+                key, userHandle, force));
+    }
+
+    /**
+     * Pins an app of a specific type {@code key}.
+     *
+     * @param force If false, this will not repin the app if it's currently active. See
+     *              {@link #mPendingRepin}.
+     */
+    private void pinApp(int key, int userHandle, boolean force) {
+        int uid = getUidForKey(key);
+
+        // In case the app is currently active, don't repin until next process restart. See
+        // mPendingRepin for more information.
+        if (!force && uid != -1) {
+            synchronized (this) {
+                mPendingRepin.put(uid, key);
+            }
+            return;
+        }
+        unpinApp(key);
+        ApplicationInfo info = getInfoForKey(key, userHandle);
+        if (info != null) {
+            pinApp(key, info);
+        }
+    }
+
+    /**
+     * Checks whether the pinned package with {@code key} is active or not.
+
+     * @return The uid of the pinned app, or {@code -1} otherwise.
+     */
+    private int getUidForKey(@AppKey int key) {
+        synchronized (this) {
+            PinnedApp existing = mPinnedApps.get(key);
+            return existing != null && existing.active
+                    ? existing.uid
+                    : -1;
+        }
+    }
+
+    /**
+     * Retrieves the current application info for the given app type.
+     *
+     * @param key The app type to retrieve the info for.
+     * @param userHandle The user id of the current user.
+     */
+    private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
+        switch (key) {
+            case KEY_CAMERA:
+                return getCameraInfo(userHandle);
+            case KEY_HOME:
+                return getHomeInfo(userHandle);
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @return The app type name for {@code key}.
+     */
+    private String getNameForKey(@AppKey int key) {
+        switch (key) {
+            case KEY_CAMERA:
+                return "Camera";
+            case KEY_HOME:
+                return "Home";
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
+     */
+    private int getSizeLimitForKey(@AppKey int key) {
+        switch (key) {
+            case KEY_CAMERA:
+                return MAX_CAMERA_PIN_SIZE;
+            case KEY_HOME:
+                return MAX_HOME_PIN_SIZE;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Pins an application.
+     *
+     * @param key The key of the app to pin.
+     * @param appInfo The corresponding app info.
+     */
+    private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
+        if (appInfo == null) {
+            return;
         }
 
-        //unpin after checking that the camera intent has resolved
-        //this prevents us from thrashing when switching users with
-        //FBE enabled, because the intent won't resolve until the unlock
-        unpinCameraApp();
+        PinnedApp pinnedApp = new PinnedApp(appInfo);
+        synchronized (this) {
+            mPinnedApps.put(key, pinnedApp);
+        }
 
-        //pin APK
-        String camAPK = cameraInfo.sourceDir;
-        PinnedFile pf = pinFile(camAPK,
-                                MAX_CAMERA_PIN_SIZE,
-                                /*attemptPinIntrospection=*/true);
+        // pin APK
+        int pinSizeLimit = getSizeLimitForKey(key);
+        String apk = appInfo.sourceDir;
+        PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true);
         if (pf == null) {
-            Slog.e(TAG, "Failed to pin " + camAPK);
-            return false;
+            Slog.e(TAG, "Failed to pin " + apk);
+            return;
         }
         if (DEBUG) {
             Slog.i(TAG, "Pinned " + pf.fileName);
         }
         synchronized (this) {
-            mPinnedCameraFiles.add(pf);
+            pinnedApp.mFiles.add(pf);
         }
 
         // determine the ABI from either ApplicationInfo or Build
         String arch = "arm";
-        if (cameraInfo.primaryCpuAbi != null) {
-            if (VMRuntime.is64BitAbi(cameraInfo.primaryCpuAbi)) {
+        if (appInfo.primaryCpuAbi != null) {
+            if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) {
                 arch = arch + "64";
             }
         } else {
@@ -273,32 +495,29 @@
         }
 
         // get the path to the odex or oat file
-        String baseCodePath = cameraInfo.getBaseCodePath();
+        String baseCodePath = appInfo.getBaseCodePath();
         String[] files = null;
         try {
             files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
         } catch (IOException ioe) {}
         if (files == null) {
-            return true;
+            return;
         }
 
         //not pinning the oat/odex is not a fatal error
         for (String file : files) {
-            pf = pinFile(file, MAX_CAMERA_PIN_SIZE, /*attemptPinIntrospection=*/false);
+            pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
             if (pf != null) {
                 synchronized (this) {
-                    mPinnedCameraFiles.add(pf);
+                    pinnedApp.mFiles.add(pf);
                 }
                 if (DEBUG) {
                     Slog.i(TAG, "Pinned " + pf.fileName);
                 }
             }
         }
-
-        return true;
     }
 
-
     /** mlock length bytes of fileToPin in memory
      *
      * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
@@ -581,24 +800,38 @@
         }
     }
 
-    private synchronized ArrayList<PinnedFile> snapshotPinnedFiles() {
-        int nrPinnedFiles = mPinnedFiles.size() + mPinnedCameraFiles.size();
-        ArrayList<PinnedFile> pinnedFiles = new ArrayList<>(nrPinnedFiles);
-        pinnedFiles.addAll(mPinnedFiles);
-        pinnedFiles.addAll(mPinnedCameraFiles);
-        return pinnedFiles;
-    }
-
     private final class BinderService extends Binder {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-            long totalSize = 0;
-            for (PinnedFile pinnedFile : snapshotPinnedFiles()) {
-                pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
-                totalSize += pinnedFile.bytesPinned;
+            synchronized (PinnerService.this) {
+                long totalSize = 0;
+                for (PinnedFile pinnedFile : mPinnedFiles) {
+                    pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
+                    totalSize += pinnedFile.bytesPinned;
+                }
+                pw.println();
+                for (int key : mPinnedApps.keySet()) {
+                    PinnedApp app = mPinnedApps.get(key);
+                    pw.print(getNameForKey(key));
+                    pw.print(" uid="); pw.print(app.uid);
+                    pw.print(" active="); pw.print(app.active);
+                    pw.println();
+                    for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
+                        pw.print("  "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
+                        totalSize += pf.bytesPinned;
+                    }
+                }
+                pw.format("Total size: %s\n", totalSize);
+                pw.println();
+                if (!mPendingRepin.isEmpty()) {
+                    pw.print("Pending repin: ");
+                    for (int key : mPendingRepin.values()) {
+                        pw.print(getNameForKey(key)); pw.print(' ');
+                    }
+                    pw.println();
+                }
             }
-            pw.format("Total size: %s\n", totalSize);
         }
     }
 
@@ -634,8 +867,30 @@
         int length;
     }
 
+    /**
+     * Represents an app that was pinned.
+     */
+    private final class PinnedApp {
+
+        /**
+         * The uid of the package being pinned. This stays constant while the package stays
+         * installed.
+         */
+        final int uid;
+
+        /** Whether it is currently active, i.e. there is a running process from that package. */
+        boolean active;
+
+        /** List of pinned files. */
+        final ArrayList<PinnedFile> mFiles = new ArrayList<>();
+
+        private PinnedApp(ApplicationInfo appInfo) {
+            uid = appInfo.uid;
+            active = mAmInternal.isUidActive(uid);
+        }
+    }
+
     final class PinnerHandler extends Handler {
-        static final int PIN_CAMERA_MSG  = 4000;
         static final int PIN_ONSTART_MSG = 4001;
 
         public PinnerHandler(Looper looper) {
@@ -645,13 +900,6 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-
-                case PIN_CAMERA_MSG:
-                {
-                    handlePinCamera(msg.arg1);
-                }
-                break;
-
                 case PIN_ONSTART_MSG:
                 {
                     handlePinOnStart();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 14eeb78..b873d39 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22465,6 +22465,13 @@
         public void finishUserSwitch(Object uss) {
             mUserController.finishUserSwitch((UserState) uss);
         }
+
+        @Override
+        public Intent getHomeIntent() {
+            synchronized (ActivityManagerService.this) {
+                return ActivityManagerService.this.getHomeIntent();
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 0774672..d6ab5f7 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -489,7 +489,7 @@
         PinnerService pinnerService = LocalServices.getService(PinnerService.class);
         if (pinnerService != null) {
             Log.i(TAG, "Pinning optimized code " + updatedPackages);
-            pinnerService.update(updatedPackages);
+            pinnerService.update(updatedPackages, false /* force */);
         }
     }