diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index e831bb1a67eec..64e76f0e7fbba 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -53,6 +53,7 @@ import android.os.ResultReceiver; import android.os.SELinux; import android.os.ShellCommand; import android.os.UserHandle; +import android.os.UserManager; import android.text.TextUtils; import android.text.format.Formatter; import android.text.format.Time; @@ -67,6 +68,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -99,6 +101,11 @@ import java.util.function.Predicate; * * - Default launcher check does take a few ms. Worth caching. * + * - Allow non-default launcher to start pinned shortcuts. (but not dynamic.) + * + * - Extract the user/package/launcher classes to their own files. Maybe rename so they all have + * the same "Shortcut" prefix. + * * - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res * -> Need to scan all packages when a user starts too. * -> Clear data -> remove all dynamic? but not the pinned? @@ -243,6 +250,7 @@ public class ShortcutService extends IShortcutService.Stub { private int mSaveDelayMillis; private final PackageManagerInternal mPackageManagerInternal; + private final UserManager mUserManager; @GuardedBy("mLock") private List mDirtyUserIds = new ArrayList<>(); @@ -257,6 +265,9 @@ public class ShortcutService extends IShortcutService.Stub { LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); mHandler = new Handler(looper); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mUserManager = context.getSystemService(UserManager.class); + + mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false); } /** @@ -282,16 +293,12 @@ public class ShortcutService extends IShortcutService.Stub { @Override public void onCleanupUser(int userHandle) { - synchronized (mService.mLock) { - mService.onCleanupUserLocked(userHandle); - } + mService.handleCleanupUser(userHandle); } @Override public void onUnlockUser(int userId) { - synchronized (mService.mLock) { - mService.onStartUserLocked(userId); - } + mService.handleUnlockUser(userId); } } @@ -308,13 +315,24 @@ public class ShortcutService extends IShortcutService.Stub { } /** lifecycle event */ - void onStartUserLocked(int userId) { - // Preload - getUserShortcutsLocked(userId); + void handleUnlockUser(int userId) { + synchronized (mLock) { + // Preload + getUserShortcutsLocked(userId); + } } /** lifecycle event */ - void onCleanupUserLocked(int userId) { + void handleCleanupUser(int userId) { + synchronized (mLock) { + unloadUserLocked(userId); + } + } + + private void unloadUserLocked(int userId) { + if (DEBUG) { + Slog.d(TAG, "unloadUserLocked: user=" + userId); + } // Save all dirty information. saveDirtyInfo(); @@ -752,6 +770,12 @@ public class ShortcutService extends IShortcutService.Stub { } } + @GuardedBy("mLock") + @NonNull + boolean isUserLoadedLocked(@UserIdInt int userId) { + return mUsers.get(userId) != null; + } + /** Return the per-user state. */ @GuardedBy("mLock") @NonNull @@ -1047,6 +1071,9 @@ public class ShortcutService extends IShortcutService.Stub { } private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { + if (!mUserManager.isUserRunning(userId)) { + return; + } postToHandler(() -> { final ArrayList copy; synchronized (mLock) { @@ -1420,6 +1447,44 @@ public class ShortcutService extends IShortcutService.Stub { } } + // === House keeping === + + @VisibleForTesting + void cleanUpPackageLocked(String packageName, int userId) { + final boolean wasUserLoaded = isUserLoadedLocked(userId); + + final UserShortcuts mUser = getUserShortcutsLocked(userId); + boolean doNotify = false; + + // First, remove the package from the package list (if the package is a publisher). + if (mUser.getPackages().remove(packageName) != null) { + doNotify = true; + } + // Also remove from the launcher list (if the package is a launcher). + mUser.getLaunchers().remove(packageName); + + // Then remove pinned shortcuts from all launchers. + for (int i = mUser.getLaunchers().size() - 1; i >= 0; i--) { + mUser.getLaunchers().valueAt(i).cleanUpPackage(packageName); + } + // Now there may be orphan shortcuts because we removed pinned shortucts at the previous + // step. Remove them too. + for (int i = mUser.getPackages().size() - 1; i >= 0; i--) { + mUser.getPackages().valueAt(i).refreshPinnedFlags(this); + } + + scheduleSaveUser(userId); + + if (doNotify) { + notifyListeners(packageName, userId); + } + + if (!wasUserLoaded) { + // Note this will execute the scheduled save. + unloadUserLocked(userId); + } + } + /** * Entry point from {@link LauncherApps}. */ @@ -1575,6 +1640,50 @@ public class ShortcutService extends IShortcutService.Stub { } } + private PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + handlePackageUpdateFinished(packageName, getChangingUserId()); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + handlePackageRemoved(packageName, getChangingUserId()); + } + + @Override + public void onPackageRemovedAllUsers(String packageName, int uid) { + handlePackageRemovedAllUsers(packageName, getChangingUserId()); + } + }; + + void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { + if (DEBUG) { + Slog.d(TAG, "onPackageUpdateFinished() userId=" + userId); + } + // TODO Update the version. + } + + void handlePackageRemoved(String packageName, @UserIdInt int userId) { + if (DEBUG) { + Slog.d(TAG, "onPackageRemoved() userId=" + userId); + } + synchronized (mLock) { + cleanUpPackageLocked(packageName, userId); + } + } + + void handlePackageRemovedAllUsers(String packageName, @UserIdInt int userId) { + if (DEBUG) { + Slog.d(TAG, "onPackageRemovedAllUsers() userId=" + userId); + } + synchronized (mLock) { + cleanUpPackageLocked(packageName, userId); + } + + // TODO Remove from all users, which we can't if the user is locked. + } + // === Dump === @Override @@ -1913,7 +2022,13 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { synchronized (mLock) { - return getPackageShortcutsLocked(packageName, userId).findShortcutById(shortcutId); + final UserShortcuts user = mUsers.get(userId); + if (user == null) return null; + + final PackageShortcuts pkg = user.getPackages().get(packageName); + if (pkg == null) return null; + + return pkg.findShortcutById(shortcutId); } } } @@ -2131,15 +2246,23 @@ class LauncherShortcuts { return mPinnedShortcuts.get(packageName); } + boolean cleanUpPackage(String packageName) { + return mPinnedShortcuts.remove(packageName) != null; + } + /** * Persist. */ public void saveToXml(XmlSerializer out) throws IOException { + final int size = mPinnedShortcuts.size(); + if (size == 0) { + return; // Nothing to write. + } + out.startTag(null, TAG_ROOT); ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, mPackageName); - final int size = mPinnedShortcuts.size(); for (int i = 0; i < size; i++) { out.startTag(null, TAG_PACKAGE); ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, @@ -2357,7 +2480,7 @@ class PackageShortcuts { removeList.add(si.getId()); } if (removeList != null) { - for (int i = removeList.size() - 1 ; i >= 0; i--) { + for (int i = removeList.size() - 1; i >= 0; i--) { deleteShortcut(s, removeList.get(i)); } } @@ -2569,6 +2692,12 @@ class PackageShortcuts { } public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException { + final int size = mShortcuts.size(); + + if (size == 0 && mApiCallCount == 0) { + return; // nothing to write. + } + out.startTag(null, TAG_ROOT); ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); @@ -2576,7 +2705,6 @@ class PackageShortcuts { ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); - final int size = mShortcuts.size(); for (int j = 0; j < size; j++) { saveShortcut(out, mShortcuts.valueAt(j)); } diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java index a47f250feea83..a14c6145c5483 100644 --- a/services/core/java/com/android/server/policy/ShortcutManager.java +++ b/services/core/java/com/android/server/policy/ShortcutManager.java @@ -78,7 +78,7 @@ class ShortcutManager { public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { ShortcutInfo shortcut = null; - // If the Shift key is preesed, then search for the shift shortcuts. + // If the Shift key is pressed, then search for the shift shortcuts. boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; SparseArray shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index ad86fd08cea97..68199b42ed69c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -56,6 +57,7 @@ import android.os.UserManager; import android.test.InstrumentationTestCase; import android.test.mock.MockContext; import android.test.suitebuilder.annotation.SmallTest; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -123,6 +125,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { throw new UnsupportedOperationException(); } + @Override + public String getSystemServiceName(Class serviceClass) { + return getTestContext().getSystemServiceName(serviceClass); + } + @Override public PackageManager getPackageManager() { return mMockPackageManager; @@ -231,7 +238,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { @Override boolean injectIsLowRamDevice() { - return mInjectdIsLowRamDevice; + return mInjectedIsLowRamDevice; } @Override @@ -343,7 +350,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private long mInjectedCurrentTimeLillis; - private boolean mInjectdIsLowRamDevice; + private boolean mInjectedIsLowRamDevice; private int mInjectedCallingUid; private String mInjectedClientPackage; @@ -657,6 +664,14 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return new ComponentName(mClientContext, clazz); } + private Set makeSet(T... values) { + final HashSet ret = new HashSet<>(); + for (T s : values) { + ret.add(s); + } + return ret; + } + @NonNull private ShortcutInfo findById(List list, String id) { for (ShortcutInfo s : list) { @@ -841,6 +856,14 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return mService.getPackageShortcutForTest(packageName, shortcutId, userId); } + private void assertShortcutExists(String packageName, String shortcutId, int userId) { + assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null); + } + + private void assertShortcutNotExists(String packageName, String shortcutId, int userId) { + assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null); + } + private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) { return getPackageShortcut(packageName, shortcutId, getCallingUserId()); } @@ -849,6 +872,27 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId()); } + private List getLauncherShortcuts(String launcher, int userId, int queryFlags) { + final List[] ret = new List[1]; + runWithCaller(launcher, userId, () -> { + final ShortcutQuery q = new ShortcutQuery(); + q.setQueryFlags(queryFlags); + ret[0] = mLauncherApps.getShortcuts(q, UserHandle.of(userId)); + }); + return ret[0]; + } + + private List getLauncherPinnedShortcuts(String launcher, int userId) { + return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED); + } + + /** + * Wrap a set in an ArraySet just to get a better toString. + */ + private Set set(Set in) { + return new ArraySet(in); + } + /** * Test for the first launch path, no settings file available. */ @@ -857,7 +901,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { } /** - * Test for {@link ShortcutService#updateTimes()} + * Test for {@link ShortcutService#getLastResetTimeLocked()} and + * {@link ShortcutService#getNextResetTimeLocked()}. */ public void testUpdateAndGetNextResetTimeLocked() { assertResetTimes(START_TIME, START_TIME + INTERVAL); @@ -928,7 +973,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(CompressFormat.WEBP, mService.getIconPersistFormatForTest()); assertEquals(75, mService.getIconPersistQualityForTest()); - mInjectdIsLowRamDevice = true; + mInjectedIsLowRamDevice = true; mService.updateConfigurationLocked( ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100," + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50," @@ -2205,6 +2250,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // TODO Add "multi" version -- run the test with two launchers and make sure the callback // argument only contains the ones that are actually visible to each launcher. + when(mMockUserManager.isUserRunning(eq(USER_0))).thenReturn(true); + LauncherApps.Callback c0 = mock(LauncherApps.Callback.class); // Set listeners @@ -2320,6 +2367,20 @@ public class ShortcutManagerTest extends InstrumentationTestCase { eq(UserHandle.of(USER_0)) ); assertEquals(0, shortcuts.getValue().size()); + + // Remove CALLING_PACKAGE_2 + reset(c0); + mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0); + + // Should get a callback with an empty list. + waitOnMainThread(); + shortcuts = ArgumentCaptor.forClass(List.class); + verify(c0).onShortcutsChanged( + eq(CALLING_PACKAGE_2), + shortcuts.capture(), + eq(UserHandle.of(USER_0)) + ); + assertEquals(0, shortcuts.getValue().size()); } // === Test for persisting === @@ -2436,7 +2497,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(0, mService.getShortcutsForTest().size()); // this will pre-load the per-user info. - mService.onStartUserLocked(UserHandle.USER_SYSTEM); + mService.handleUnlockUser(UserHandle.USER_SYSTEM); // Now it's loaded. assertEquals(1, mService.getShortcutsForTest().size()); @@ -2462,7 +2523,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { .getLauncherComponent().getPackageName()); // Start another user - mService.onStartUserLocked(USER_10); + mService.handleUnlockUser(USER_10); // Now the size is 2. assertEquals(2, mService.getShortcutsForTest().size()); @@ -2478,7 +2539,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(mService.getShortcutsForTest().get(USER_10).getLauncherComponent()); // Try stopping the user - mService.onCleanupUserLocked(USER_10); + mService.handleCleanupUser(USER_10); // Now it's unloaded. assertEquals(1, mService.getShortcutsForTest().size()); @@ -2486,6 +2547,248 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // TODO Check all other fields } + public void testCleanupPackage() { + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList( + makeShortcut("s0_1")))); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList( + makeShortcut("s0_2")))); + }); + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s0_1"), + UserHandle.of(USER_0)); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s0_2"), + UserHandle.of(USER_0)); + }); + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s0_1"), + UserHandle.of(USER_0)); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s0_2"), + UserHandle.of(USER_0)); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList( + makeShortcut("s10_1")))); + }); + runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList( + makeShortcut("s10_2")))); + }); + runWithCaller(LAUNCHER_1, USER_10, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s10_1"), + UserHandle.of(USER_10)); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s10_2"), + UserHandle.of(USER_10)); + }); + runWithCaller(LAUNCHER_2, USER_10, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s10_1"), + UserHandle.of(USER_10)); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s10_2"), + UserHandle.of(USER_10)); + }); + + // Remove all dynamic shortcuts; now all shortcuts are just pinned. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + mManager.deleteAllDynamicShortcuts(); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + mManager.deleteAllDynamicShortcuts(); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + mManager.deleteAllDynamicShortcuts(); + }); + runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { + mManager.deleteAllDynamicShortcuts(); + }); + + + final SparseArray users = mService.getShortcutsForTest(); + assertEquals(2, users.size()); + assertEquals(USER_0, users.keyAt(0)); + assertEquals(USER_10, users.keyAt(1)); + + final UserShortcuts user0 = users.get(USER_0); + final UserShortcuts user10 = users.get(USER_10); + + + // Check the registered packages. + + assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + set(user0.getPackages().keySet())); + assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + set(user10.getPackages().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user0.getLaunchers().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user10.getLaunchers().keySet())); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), + "s0_1", "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), + "s0_1", "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10), + "s10_1", "s10_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10), + "s10_1", "s10_2"); + assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0); + assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0); + assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); + assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); + + mService.saveDirtyInfo(); + + // Nonexistent package. + mService.cleanUpPackageLocked("abc", USER_0); + + // No changes. + assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + set(user0.getPackages().keySet())); + assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + set(user10.getPackages().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user0.getLaunchers().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user10.getLaunchers().keySet())); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), + "s0_1", "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), + "s0_1", "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10), + "s10_1", "s10_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10), + "s10_1", "s10_2"); + assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0); + assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0); + assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); + assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); + + mService.saveDirtyInfo(); + + // Remove a package. + mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0); + + assertEquals(makeSet(CALLING_PACKAGE_2), + set(user0.getPackages().keySet())); + assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + set(user10.getPackages().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user0.getLaunchers().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user10.getLaunchers().keySet())); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10), + "s10_1", "s10_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10), + "s10_1", "s10_2"); + assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0); + assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0); + assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); + assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); + + mService.saveDirtyInfo(); + + // Remove a launcher. + mService.cleanUpPackageLocked(LAUNCHER_1, USER_10); + + assertEquals(makeSet(CALLING_PACKAGE_2), + set(user0.getPackages().keySet())); + assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + set(user10.getPackages().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user0.getLaunchers().keySet())); + assertEquals(makeSet(LAUNCHER_2), + set(user10.getLaunchers().keySet())); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10), + "s10_1", "s10_2"); + assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0); + assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0); + assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); + assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); + + mService.saveDirtyInfo(); + + // Remove a package. + mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10); + + assertEquals(makeSet(CALLING_PACKAGE_2), + set(user0.getPackages().keySet())); + assertEquals(makeSet(CALLING_PACKAGE_1), + set(user10.getPackages().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user0.getLaunchers().keySet())); + assertEquals(makeSet(LAUNCHER_2), + set(user10.getLaunchers().keySet())); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10), + "s10_1"); + assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0); + assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0); + assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); + assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10); + + mService.saveDirtyInfo(); + + // Remove the other launcher from user 10 too. + mService.cleanUpPackageLocked(LAUNCHER_2, USER_10); + + assertEquals(makeSet(CALLING_PACKAGE_2), + set(user0.getPackages().keySet())); + assertEquals(makeSet(CALLING_PACKAGE_1), + set(user10.getPackages().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user0.getLaunchers().keySet())); + assertEquals(makeSet(), + set(user10.getLaunchers().keySet())); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), + "s0_2"); + + // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed. + assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0); + assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0); + assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10); + assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10); + + mService.saveDirtyInfo(); + + // More remove. + mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10); + + assertEquals(makeSet(CALLING_PACKAGE_2), + set(user0.getPackages().keySet())); + assertEquals(makeSet(), + set(user10.getPackages().keySet())); + assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2), + set(user0.getLaunchers().keySet())); + assertEquals(makeSet(), + set(user10.getLaunchers().keySet())); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), + "s0_2"); + assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), + "s0_2"); + + // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed. + assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0); + assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0); + assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10); + assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10); + + mService.saveDirtyInfo(); + } + // TODO Detailed test for hasShortcutPermissionInner(). // TODO Add tests for the command line functions too.