OSDN Git Service

Cache the default launcher.
authorMakoto Onuki <omakoto@google.com>
Fri, 15 Jul 2016 01:14:08 +0000 (18:14 -0700)
committerMakoto Onuki <omakoto@google.com>
Fri, 15 Jul 2016 19:57:03 +0000 (12:57 -0700)
Originally we always checked with PM for the default launcher,
which would take ~2ms.

Now we cache the result, and clears the cache when (any) preferred
activities change.

Bug 30126557

Change-Id: Iceef288cd372c8bb9b119aa493e5173d894f2302

core/java/android/content/Intent.java
core/res/AndroidManifest.xml
services/core/java/com/android/server/pm/PackageManagerService.java
services/core/java/com/android/server/pm/ShortcutService.java
services/core/java/com/android/server/pm/ShortcutUser.java
services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java [new file with mode: 0644]

index c140f1b..4d9db98 100644 (file)
@@ -2219,6 +2219,22 @@ public class Intent implements Parcelable, Cloneable {
         "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
 
     /**
+     * Broadcast Action: preferred activities have changed *explicitly*.
+     *
+     * <p>Note there are cases where a preferred activity is invalidated *implicitly*, e.g.
+     * when an app is installed or uninstalled, but in such cases this broadcast will *not*
+     * be sent.
+     *
+     * {@link #EXTRA_USER_HANDLE} contains the user ID in question.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PREFERRED_ACTIVITY_CHANGED =
+            "android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED";
+
+
+    /**
      * Broadcast Action:  The current system wallpaper has changed.  See
      * {@link android.app.WallpaperManager} for retrieving the new wallpaper.
      * This should <em>only</em> be used to determine when the wallpaper
index 8388f05..4b66f70 100644 (file)
@@ -49,6 +49,7 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
     <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
     <protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+    <protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.UID_REMOVED" />
     <protected-broadcast android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
     <protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
index 78fa3a3..0061171 100644 (file)
@@ -16886,9 +16886,28 @@ public class PackageManagerService extends IPackageManager.Stub {
             filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
             pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
             scheduleWritePackageRestrictionsLocked(userId);
+            postPreferredActivityChangedBroadcast(userId);
         }
     }
 
+    private void postPreferredActivityChangedBroadcast(int userId) {
+        mHandler.post(() -> {
+            final IActivityManager am = ActivityManagerNative.getDefault();
+            if (am == null) {
+                return;
+            }
+
+            final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            try {
+                am.broadcastIntent(null, intent, null, null,
+                        0, null, null, null, android.app.AppOpsManager.OP_NONE,
+                        null, false, false, userId);
+            } catch (RemoteException e) {
+            }
+        });
+    }
+
     @Override
     public void replacePreferredActivity(IntentFilter filter, int match,
             ComponentName[] set, ComponentName activity, int userId) {
@@ -17040,6 +17059,9 @@ public class PackageManagerService extends IPackageManager.Stub {
                 changed = true;
             }
         }
+        if (changed) {
+            postPreferredActivityChangedBroadcast(userId);
+        }
         return changed;
     }
 
@@ -17153,6 +17175,7 @@ public class PackageManagerService extends IPackageManager.Stub {
             mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
                     new PersistentPreferredActivity(filter, activity));
             scheduleWritePackageRestrictionsLocked(userId);
+            postPreferredActivityChangedBroadcast(userId);
         }
     }
 
@@ -17195,6 +17218,7 @@ public class PackageManagerService extends IPackageManager.Stub {
 
             if (changed) {
                 scheduleWritePackageRestrictionsLocked(userId);
+                postPreferredActivityChangedBroadcast(userId);
             }
         }
     }
index 4286cd9..10f1b4b 100644 (file)
@@ -385,6 +385,12 @@ public class ShortcutService extends IShortcutService.Stub {
         mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL,
                 packageFilter, null, mHandler);
 
+        final IntentFilter preferedActivityFilter = new IntentFilter();
+        preferedActivityFilter.addAction(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
+        preferedActivityFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL,
+                preferedActivityFilter, null, mHandler);
+
         final IntentFilter localeFilter = new IntentFilter();
         localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
         localeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
@@ -1923,7 +1929,12 @@ public class ShortcutService extends IShortcutService.Stub {
 
     // We override this method in unit tests to do a simpler check.
     boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
-        return hasShortcutHostPermissionInner(callingPackage, userId);
+        final long start = injectElapsedRealtime();
+        try {
+            return hasShortcutHostPermissionInner(callingPackage, userId);
+        } finally {
+            logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
+        }
     }
 
     // This method is extracted so we can directly call this method from unit tests,
@@ -1931,15 +1942,22 @@ public class ShortcutService extends IShortcutService.Stub {
     @VisibleForTesting
     boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
         synchronized (mLock) {
-            final long start = injectElapsedRealtime();
-
             final ShortcutUser user = getUserShortcutsLocked(userId);
 
+            // Always trust the in-memory cache.
+            final ComponentName cached = user.getCachedLauncher();
+            if (cached != null) {
+                if (cached.getPackageName().equals(callingPackage)) {
+                    return true;
+                }
+            }
+            // If the cached one doesn't match, then go ahead
+
             final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
 
             // Default launcher from package manager.
             final long startGetHomeActivitiesAsUser = injectElapsedRealtime();
-            final ComponentName defaultLauncher = injectPackageManagerInternal()
+            final ComponentName defaultLauncher = mPackageManagerInternal
                     .getHomeActivitiesAsUser(allHomeCandidates, userId);
             logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
 
@@ -1950,7 +1968,7 @@ public class ShortcutService extends IShortcutService.Stub {
                     Slog.v(TAG, "Default launcher from PM: " + detected);
                 }
             } else {
-                detected = user.getDefaultLauncherComponent();
+                detected = user.getLastKnownLauncher();
 
                 if (detected != null) {
                     if (injectIsActivityEnabledAndExported(detected, userId)) {
@@ -1960,7 +1978,7 @@ public class ShortcutService extends IShortcutService.Stub {
                     } else {
                         Slog.w(TAG, "Cached launcher " + detected + " no longer exists");
                         detected = null;
-                        user.setDefaultLauncherComponent(null);
+                        user.clearLauncher();
                     }
                 }
             }
@@ -1991,13 +2009,13 @@ public class ShortcutService extends IShortcutService.Stub {
                     lastPriority = ri.priority;
                 }
             }
-            logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
 
+            // Update the cache.
+            user.setLauncher(detected);
             if (detected != null) {
                 if (DEBUG) {
                     Slog.v(TAG, "Detected launcher: " + detected);
                 }
-                user.setDefaultLauncherComponent(detected);
                 return detected.getPackageName().equals(callingPackage);
             } else {
                 // Default launcher not found.
@@ -2358,6 +2376,17 @@ public class ShortcutService extends IShortcutService.Stub {
                     return;
                 }
 
+                // Whenever we get one of those package broadcasts, or get
+                // ACTION_PREFERRED_ACTIVITY_CHANGED, we purge the default launcher cache.
+                synchronized (mLock) {
+                    final ShortcutUser user = getUserShortcutsLocked(userId);
+                    user.clearLauncher();
+                }
+                if (Intent.ACTION_PREFERRED_ACTIVITY_CHANGED.equals(action)) {
+                    // Nothing farther to do.
+                    return;
+                }
+
                 final Uri intentUri = intent.getData();
                 final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart()
                         : null;
@@ -3260,7 +3289,7 @@ public class ShortcutService extends IShortcutService.Stub {
 
         private void clearLauncher() {
             synchronized (mLock) {
-                getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null);
+                getUserShortcutsLocked(mUserId).forceClearLauncher();
             }
         }
 
@@ -3270,7 +3299,7 @@ public class ShortcutService extends IShortcutService.Stub {
                 hasShortcutHostPermissionInner("-", mUserId);
 
                 getOutPrintWriter().println("Launcher: "
-                        + getUserShortcutsLocked(mUserId).getDefaultLauncherComponent());
+                        + getUserShortcutsLocked(mUserId).getLastKnownLauncher());
             }
         }
 
@@ -3394,11 +3423,6 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
-    @VisibleForTesting
-    PackageManagerInternal injectPackageManagerInternal() {
-        return mPackageManagerInternal;
-    }
-
     File getUserBitmapFilePath(@UserIdInt int userId) {
         return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
     }
@@ -3482,7 +3506,7 @@ public class ShortcutService extends IShortcutService.Stub {
     }
 
     private void verifyStatesInner() {
-        synchronized (this) {
+        synchronized (mLock) {
             forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
         }
     }
index 9649641..3a43ece 100644 (file)
@@ -103,8 +103,15 @@ class ShortcutUser {
 
     private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
 
-    /** Default launcher that can access the launcher apps APIs. */
-    private ComponentName mDefaultLauncherComponent;
+    /**
+     * Last known launcher.  It's used when the default launcher isn't set in PM -- i.e.
+     * when getHomeActivitiesAsUser() return null.  We need it so that in this situation the
+     * previously default launcher can still access shortcuts.
+     */
+    private ComponentName mLastKnownLauncher;
+
+    /** In-memory-cached default launcher. */
+    private ComponentName mCachedLauncher;
 
     private String mKnownLocales;
 
@@ -304,8 +311,7 @@ class ShortcutUser {
         ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
                 mLastAppScanTime);
 
-        ShortcutService.writeTagValue(out, TAG_LAUNCHER,
-                mDefaultLauncherComponent);
+        ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
 
         // Can't use forEachPackageItem due to the checked exceptions.
         {
@@ -364,7 +370,7 @@ class ShortcutUser {
             if (depth == outerDepth + 1) {
                 switch (tag) {
                     case TAG_LAUNCHER: {
-                        ret.mDefaultLauncherComponent = ShortcutService.parseComponentNameAttribute(
+                        ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
                                 parser, ATTR_VALUE);
                         continue;
                     }
@@ -389,18 +395,44 @@ class ShortcutUser {
         return ret;
     }
 
-    public ComponentName getDefaultLauncherComponent() {
-        return mDefaultLauncherComponent;
+    public ComponentName getLastKnownLauncher() {
+        return mLastKnownLauncher;
+    }
+
+    public void setLauncher(ComponentName launcherComponent) {
+        setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
     }
 
-    public void setDefaultLauncherComponent(ComponentName launcherComponent) {
-        if (Objects.equal(mDefaultLauncherComponent, launcherComponent)) {
+    /** Clears the launcher information without clearing the last known one */
+    public void clearLauncher() {
+        setLauncher(null);
+    }
+
+    /**
+     * Clears the launcher information *with(* clearing the last known one; we do this witl
+     * "cmd shortcut clear-default-launcher".
+     */
+    public void forceClearLauncher() {
+        setLauncher(null, /* allowPurgeLastKnown */ true);
+    }
+
+    private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) {
+        mCachedLauncher = launcherComponent; // Always update the in-memory cache.
+
+        if (Objects.equal(mLastKnownLauncher, launcherComponent)) {
             return;
         }
-        mDefaultLauncherComponent = launcherComponent;
+        if (!allowPurgeLastKnown && launcherComponent == null) {
+            return;
+        }
+        mLastKnownLauncher = launcherComponent;
         mService.scheduleSaveUser(mUserId);
     }
 
+    public ComponentName getCachedLauncher() {
+        return mCachedLauncher;
+    }
+
     public void resetThrottling() {
         for (int i = mPackages.size() - 1; i >= 0; i--) {
             mPackages.valueAt(i).resetThrottling();
@@ -422,8 +454,13 @@ class ShortcutUser {
         prefix += prefix + "  ";
 
         pw.print(prefix);
-        pw.print("Default launcher: ");
-        pw.print(mDefaultLauncherComponent);
+        pw.print("Cached launcher: ");
+        pw.print(mCachedLauncher);
+        pw.println();
+
+        pw.print(prefix);
+        pw.print("Last known launcher: ");
+        pw.print(mLastKnownLauncher);
         pw.println();
 
         for (int i = 0; i < mLaunchers.size(); i++) {
index 2652b8f..037b24e 100644 (file)
@@ -303,11 +303,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
         }
 
         @Override
-        PackageManagerInternal injectPackageManagerInternal() {
-            return mMockPackageManagerInternal;
-        }
-
-        @Override
         boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
             return mDefaultLauncherChecker.test(callingPackage, userId);
         }
index d563185..6b54f6b 100644 (file)
@@ -3131,7 +3131,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
             assertEquals(2, mManager.getRemainingCallCount());
         });
 
-        mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setDefaultLauncherComponent(
+        mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncher(
                 new ComponentName("pkg1", "class"));
 
         // Restore.
@@ -3165,7 +3165,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
         });
 
         assertEquals("pkg1", mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM)
-                .getDefaultLauncherComponent().getPackageName());
+                .getLastKnownLauncher().getPackageName());
 
         // Start another user
         mService.handleUnlockUser(USER_10);
@@ -3181,7 +3181,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
             assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
             assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
         });
-        assertNull(mService.getShortcutsForTest().get(USER_10).getDefaultLauncherComponent());
+        assertNull(mService.getShortcutsForTest().get(USER_10).getLastKnownLauncher());
 
         // Try stopping the user
         mService.handleCleanupUser(USER_10);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java
new file mode 100644 (file)
index 0000000..ffb2953
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2016 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 static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.List;
+
+/**
+ * Tests for {@link ShortcutService#hasShortcutHostPermissionInner}.
+ */
+@SmallTest
+public class ShortcutManagerTest6 extends BaseShortcutManagerTest {
+
+    private static final String PACKAGE_SYSTEM_LAUNCHER = "com.android.systemlauncher";
+    private static final String PACKAGE_SYSTEM_LAUNCHER_NAME = "systemlauncher_name";
+    private static final int PACKAGE_SYSTEM_LAUNCHER_PRIORITY = 0;
+
+    private static final String PACKAGE_FALLBACK_LAUNCHER = "com.android.settings";
+    private static final String PACKAGE_FALLBACK_LAUNCHER_NAME = "fallback";
+    private static final int PACKAGE_FALLBACK_LAUNCHER_PRIORITY = -999;
+
+    private void prepareGetHomeActivitiesAsUser(ComponentName preferred,
+            List<ResolveInfo> candidates, int userId) {
+        doAnswer(inv -> {
+            ((List) inv.getArguments()[0]).addAll(candidates);
+            return preferred;
+        }).when(mMockPackageManagerInternal).getHomeActivitiesAsUser(any(List.class), eq(userId));
+    }
+
+    private static ComponentName cn(String packageName, String name) {
+        return new ComponentName(packageName, name);
+    }
+
+    private static ResolveInfo ri(String packageName, String name, boolean isSystem, int priority) {
+        final ResolveInfo ri = new ResolveInfo();
+        ri.activityInfo = new ActivityInfo();
+        ri.activityInfo.applicationInfo = new ApplicationInfo();
+
+        ri.activityInfo.packageName = packageName;
+        ri.activityInfo.name = name;
+        if (isSystem) {
+            ri.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        }
+        ri.priority = priority;
+        return ri;
+    }
+
+    private static ResolveInfo getSystemLauncher() {
+        return ri(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME, true,
+                PACKAGE_SYSTEM_LAUNCHER_PRIORITY);
+    }
+
+    private static ResolveInfo getFallbackLauncher() {
+        return ri(PACKAGE_FALLBACK_LAUNCHER, PACKAGE_FALLBACK_LAUNCHER_NAME, true,
+                PACKAGE_FALLBACK_LAUNCHER_PRIORITY);
+    }
+
+    public void testHasShortcutHostPermissionInner_systemLauncherOnly() {
+        // Preferred isn't set, use the system launcher.
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ null,
+                list(getSystemLauncher(), getFallbackLauncher()),
+                USER_0);
+        assertTrue(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_FALLBACK_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_0));
+
+        // Should be cached.
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+        // Also make sure the last known is saved, but the cached is not.
+
+        initService();
+
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(null,
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+    }
+
+    public void testHasShortcutHostPermissionInner_with3pLauncher() {
+        // Preferred isn't set, still use the system launcher.
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ null,
+                list(getSystemLauncher(), getFallbackLauncher(),
+                        ri(CALLING_PACKAGE_1, "name", false, 0),
+                        ri(CALLING_PACKAGE_2, "name", false, 0)
+                ),
+                USER_0);
+        assertTrue(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_FALLBACK_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_0));
+
+        // Should be cached.
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+    }
+
+    public void testHasShortcutHostPermissionInner_with3pLauncher_complicated() {
+        // Preferred is set.  That's the default launcher.
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ cn(CALLING_PACKAGE_2, "name"),
+                list(getSystemLauncher(), getFallbackLauncher(),
+                        ri(CALLING_PACKAGE_1, "name", false, 0),
+                        ri(CALLING_PACKAGE_2, "name", false, 0)
+                ),
+                USER_0);
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_FALLBACK_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+        assertTrue(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_0));
+
+        // Should be cached.
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+
+        // Once set, even after the preferred launcher is cleared, SM still allows it to access
+        // shortcuts.
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ null,
+                list(getSystemLauncher(), getFallbackLauncher(),
+                        ri(CALLING_PACKAGE_1, "name", false, 0),
+                        ri(CALLING_PACKAGE_2, "name", false, 0)
+                ),
+                USER_0);
+
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_FALLBACK_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+        assertTrue(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_0));
+
+        // Should be cached.
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+        // However, if the component has been disabled, then we'll recalculate it.
+        mEnabledActivityChecker = (comp, user) -> false;
+
+        assertTrue(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_FALLBACK_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_0));
+
+        mEnabledActivityChecker = (comp, user) -> true;
+
+        // Now the preferred changed.
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ cn(CALLING_PACKAGE_1, "xyz"),
+                list(getSystemLauncher(), getFallbackLauncher(),
+                        ri(CALLING_PACKAGE_1, "name", false, 0),
+                        ri(CALLING_PACKAGE_2, "name", false, 0)
+                ),
+                USER_0);
+
+        assertTrue(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+
+        // Should be cached.
+        assertEquals(cn(CALLING_PACKAGE_1, "xyz"),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(CALLING_PACKAGE_1, "xyz"),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+
+        // As long as there's the cached launcher set, even if getHomeActivitiesAsUser()
+        // returns different values, the cached one is still the default.
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ getSystemLauncher().activityInfo.getComponentName(),
+                list(getSystemLauncher(), getFallbackLauncher()),
+                USER_0);
+
+        assertTrue(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+
+        // Cached ones haven't changed.
+        assertEquals(cn(CALLING_PACKAGE_1, "xyz"),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(CALLING_PACKAGE_1, "xyz"),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+        // However, now the "real" default launcher is the system one.  So if the system
+        // launcher asks for shortcuts, we'll allow it.
+        assertTrue(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+
+        // Since the cache is updated, CALLING_PACKAGE_1 no longer has the permission.
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+
+        // Cached ones haven't changed.
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+    }
+
+    public void testHasShortcutHostPermissionInner_multiUser() {
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ null,
+                list(getSystemLauncher(), getFallbackLauncher()),
+                USER_0);
+
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ cn(CALLING_PACKAGE_2, "name"),
+                list(getSystemLauncher(), getFallbackLauncher(),
+                        ri(CALLING_PACKAGE_1, "name", false, 0),
+                        ri(CALLING_PACKAGE_2, "name", false, 0)
+                ),
+                USER_10);
+
+        assertTrue(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_FALLBACK_LAUNCHER, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_0));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_0));
+
+        // Check the cache.
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getLastKnownLauncher());
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_10));
+        assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_FALLBACK_LAUNCHER, USER_10));
+        assertFalse(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_1, USER_10));
+        assertTrue(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_10));
+
+        // Check the cache.
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_10).getLastKnownLauncher());
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_10).getCachedLauncher());
+    }
+
+    public void testHasShortcutHostPermissionInner_clearCache() {
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ null,
+                list(getSystemLauncher(), getFallbackLauncher()),
+                USER_0);
+
+        prepareGetHomeActivitiesAsUser(
+                /* preferred */ cn(CALLING_PACKAGE_2, "name"),
+                list(getSystemLauncher(), getFallbackLauncher(),
+                        ri(CALLING_PACKAGE_1, "name", false, 0),
+                        ri(CALLING_PACKAGE_2, "name", false, 0)
+                ),
+                USER_10);
+
+        assertTrue(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+        assertTrue(mService.hasShortcutHostPermissionInner(CALLING_PACKAGE_2, USER_10));
+
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_10).getCachedLauncher());
+
+        // Send ACTION_PREFERRED_ACTIVITY_CHANGED on user 10.
+        // But the user is not running, so will be ignored.
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED).putExtra(
+                        Intent.EXTRA_USER_HANDLE, USER_10));
+
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+        assertEquals(cn(CALLING_PACKAGE_2, "name"),
+                mService.getUserShortcutsLocked(USER_10).getCachedLauncher());
+
+         // Send it again after starting the user.
+        mRunningUsers.put(USER_10, true);
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED).putExtra(
+                        Intent.EXTRA_USER_HANDLE, USER_10));
+
+        assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+                mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+        // Only user-10's cache is cleared.
+        assertEquals(null,
+                mService.getUserShortcutsLocked(USER_10).getCachedLauncher());
+
+    }
+}