diff --git a/api/test-current.txt b/api/test-current.txt index e837cd06aa352..679347df0febe 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -9505,6 +9505,7 @@ package android.content.pm { } public class LauncherApps { + ctor public LauncherApps(android.content.Context); method public java.util.List getActivityList(java.lang.String, android.os.UserHandle); method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle); method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo); @@ -10092,6 +10093,7 @@ package android.content.pm { } public class ShortcutManager { + ctor public ShortcutManager(android.content.Context); method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); method public void deleteAllDynamicShortcuts(); method public void deleteDynamicShortcut(java.lang.String); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index bdc4404057e5d..d5d4ca7ad4015 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -589,9 +589,7 @@ final class SystemServiceRegistry { new CachedServiceFetcher() { @Override public LauncherApps createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE); - ILauncherApps service = ILauncherApps.Stub.asInterface(b); - return new LauncherApps(ctx, service); + return new LauncherApps(ctx); }}); registerService(Context.RESTRICTIONS_SERVICE, RestrictionsManager.class, @@ -758,8 +756,7 @@ final class SystemServiceRegistry { new CachedServiceFetcher() { @Override public ShortcutManager createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(Context.SHORTCUT_SERVICE); - return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b)); + return new ShortcutManager(ctx); }}); registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class, diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index abe1aaf96c36e..a0d2339566a5a 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -19,6 +19,7 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -30,6 +31,7 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -261,6 +263,13 @@ public class LauncherApps { mPm = context.getPackageManager(); } + /** @hide */ + @TestApi + public LauncherApps(Context context) { + this(context, ILauncherApps.Stub.asInterface( + ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE))); + } + /** * Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and * {@link Intent#CATEGORY_LAUNCHER}, for a specified user. diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index e4a98b5f37e36..919ccda91ba91 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -16,8 +16,11 @@ package android.content.pm; import android.annotation.NonNull; +import android.annotation.TestApi; import android.content.Context; +import android.os.IBinder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; @@ -96,6 +99,15 @@ public class ShortcutManager { mService = service; } + /** + * @hide + */ + @TestApi + public ShortcutManager(Context context) { + this(context, IShortcutService.Stub.asInterface( + ServiceManager.getService(Context.SHORTCUT_SERVICE))); + } + /** * Publish a list of shortcuts. All existing dynamic shortcuts from the caller application * will be replaced. diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 79d9c869d2b47..5ee7cc9ce3f3f 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -147,6 +147,7 @@ public class LauncherAppsService extends SystemService { @Override public void addOnAppsChangedListener(String callingPackage, IOnAppsChangedListener listener) throws RemoteException { + verifyCallingPackage(callingPackage); synchronized (mListeners) { if (DEBUG) { Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle()); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c12425575485a..1641e1dce169f 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -419,26 +419,26 @@ public class ShortcutService extends IShortcutService.Stub { result = false; } - mSaveDelayMillis = (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, - DEFAULT_SAVE_DELAY_MS); + mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, + DEFAULT_SAVE_DELAY_MS)); - mResetInterval = parser.getLong( + mResetInterval = Math.max(1, parser.getLong( ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) - * 1000L; + * 1000L); - mMaxDailyUpdates = (int) parser.getLong( - ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES); + mMaxDailyUpdates = Math.max(0, (int) parser.getLong( + ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES)); - mMaxDynamicShortcuts = (int) parser.getLong( - ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP); + mMaxDynamicShortcuts = Math.max(0, (int) parser.getLong( + ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP)); - final int iconDimensionDp = injectIsLowRamDevice() + final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() ? (int) parser.getLong( ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) : (int) parser.getLong( ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, - DEFAULT_MAX_ICON_DIMENSION_DP); + DEFAULT_MAX_ICON_DIMENSION_DP)); mMaxIconDimension = injectDipToPixel(iconDimensionDp); @@ -1128,7 +1128,7 @@ public class ShortcutService extends IShortcutService.Stub { if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) { return; // Caller is valid. } - throw new SecurityException("Caller UID= doesn't own " + packageName); + throw new SecurityException("Calling package name mismatch"); } void postToHandler(Runnable r) { @@ -1425,6 +1425,8 @@ public class ShortcutService extends IShortcutService.Stub { @Override public int getIconMaxDimensions(String packageName, int userId) throws RemoteException { + verifyCaller(packageName, userId); + synchronized (mLock) { return mMaxIconDimension; } @@ -1445,7 +1447,15 @@ public class ShortcutService extends IShortcutService.Stub { getUserShortcutsLocked(userId).resetThrottling(); } scheduleSaveUser(userId); - Slog.i(TAG, "ShortcutManager: throttling counter reset"); + Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); + } + + void resetAllThrottlingInner() { + synchronized (mLock) { + mRawLastResetTime = injectCurrentTimeMillis(); + } + scheduleSaveBaseState(); + Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); } // We override this method in unit tests to do a simpler check. @@ -1528,14 +1538,20 @@ public class ShortcutService extends IShortcutService.Stub { // === House keeping === + @VisibleForTesting + void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { + cleanUpPackageLocked(packageName, owningUserId, packageUserId, + /* forceForCommandLine= */ false); + } + /** * Remove all the information associated with a package. This will really remove all the * information, including the restore information (i.e. it'll remove packages even if they're * shadow). */ - @VisibleForTesting - void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { - if (isPackageInstalled(packageName, packageUserId)) { + private void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, + boolean forceForCommandLine) { + if (!forceForCommandLine && isPackageInstalled(packageName, packageUserId)) { wtf("Package " + packageName + " is still installed for user " + packageUserId); return; } @@ -1863,9 +1879,15 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, packageUserId)); } + handlePackageRemovedInner(packageName, packageUserId, /* forceForCommandLine =*/ false); + } + + private void handlePackageRemovedInner(String packageName, @UserIdInt int packageUserId, + boolean forceForCommandLine) { synchronized (mLock) { forEachLoadedUserLocked(user -> - cleanUpPackageLocked(packageName, user.getUserId(), packageUserId)); + cleanUpPackageLocked(packageName, user.getUserId(), packageUserId, + forceForCommandLine)); } } @@ -2046,17 +2068,26 @@ public class ShortcutService extends IShortcutService.Stub { pw.print(formatTime(next)); pw.println(); - pw.print(" Max icon dim: "); - pw.print(mMaxIconDimension); - pw.print(" Icon format: "); - pw.print(mIconPersistFormat); - pw.print(" Icon quality: "); + pw.print(" Config:"); + pw.print(" Max icon dim: "); + pw.println(mMaxIconDimension); + pw.print(" Icon format: "); + pw.println(mIconPersistFormat); + pw.print(" Icon quality: "); pw.println(mIconPersistQuality); + pw.print(" saveDelayMillis:"); + pw.println(mSaveDelayMillis); + pw.print(" resetInterval:"); + pw.println(mResetInterval); + pw.print(" maxDailyUpdates:"); + pw.println(mMaxDailyUpdates); + pw.print(" maxDynamicShortcuts:"); + pw.println(mMaxDynamicShortcuts); pw.println(); pw.println(" Stats:"); synchronized (mStatLock) { - final String p = " "; + final String p = " "; dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()"); dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check"); @@ -2142,6 +2173,9 @@ public class ShortcutService extends IShortcutService.Stub { case "reset-throttling": handleResetThrottling(); break; + case "reset-all-throttling": + handleResetAllThrottling(); + break; case "override-config": handleOverrideConfig(); break; @@ -2160,6 +2194,9 @@ public class ShortcutService extends IShortcutService.Stub { case "unload-user": handleUnloadUser(); break; + case "clear-shortcuts": + handleClearShortcuts(); + break; default: return handleDefaultCommands(cmd); } @@ -2179,9 +2216,12 @@ public class ShortcutService extends IShortcutService.Stub { pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE"); pw.println(" Reset throttling for a package"); pw.println(); - pw.println("cmd shortcut reset-throttling"); + pw.println("cmd shortcut reset-throttling [--user USER_ID]"); pw.println(" Reset throttling for all packages and users"); pw.println(); + pw.println("cmd shortcut reset-all-throttling"); + pw.println(" Reset the throttling state for all users"); + pw.println(); pw.println("cmd shortcut override-config CONFIG"); pw.println(" Override the configuration for testing (will last until reboot)"); pw.println(); @@ -2201,13 +2241,23 @@ public class ShortcutService extends IShortcutService.Stub { pw.println(" Unload a user from the memory"); pw.println(" (This should not affect any observable behavior)"); pw.println(); + pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); + pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); + pw.println(); } - private int handleResetThrottling() throws CommandException { + private void handleResetThrottling() throws CommandException { parseOptions(/* takeUser =*/ true); + Slog.i(TAG, "cmd: handleResetThrottling"); + resetThrottlingInner(mUserId); - return 0; + } + + private void handleResetAllThrottling() { + Slog.i(TAG, "cmd: handleResetAllThrottling"); + + resetAllThrottlingInner(); } private void handleResetPackageThrottling() throws CommandException { @@ -2215,6 +2265,8 @@ public class ShortcutService extends IShortcutService.Stub { final String packageName = getNextArgRequired(); + Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName); + synchronized (mLock) { getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine(); saveUserLocked(mUserId); @@ -2224,6 +2276,8 @@ public class ShortcutService extends IShortcutService.Stub { private void handleOverrideConfig() throws CommandException { final String config = getNextArgRequired(); + Slog.i(TAG, "cmd: handleOverrideConfig: " + config); + synchronized (mLock) { if (!updateConfigurationLocked(config)) { throw new CommandException("override-config failed. See logcat for details."); @@ -2232,6 +2286,8 @@ public class ShortcutService extends IShortcutService.Stub { } private void handleResetConfig() { + Slog.i(TAG, "cmd: handleResetConfig"); + synchronized (mLock) { loadConfigurationLocked(); } @@ -2276,8 +2332,20 @@ public class ShortcutService extends IShortcutService.Stub { private void handleUnloadUser() throws CommandException { parseOptions(/* takeUser =*/ true); + Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId); + ShortcutService.this.handleCleanupUser(mUserId); } + + private void handleClearShortcuts() throws CommandException { + parseOptions(/* takeUser =*/ true); + final String packageName = getNextArgRequired(); + + Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName); + + ShortcutService.this.handlePackageRemovedInner(packageName, mUserId, + /* forceForCommandLine= */ true); + } } // === Unit test support === diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java index a14c6145c5483..ab404dbad9105 100644 --- a/services/core/java/com/android/server/policy/ShortcutManager.java +++ b/services/core/java/com/android/server/policy/ShortcutManager.java @@ -27,7 +27,9 @@ import android.util.Log; import android.util.SparseArray; import android.view.KeyCharacterMap; import android.view.KeyEvent; + import com.android.internal.util.XmlUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index e6dd6a4205e72..7ffdb35bc2773 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -19,7 +19,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ easymocklib \ guava \ android-support-test \ - mockito-target + mockito-target \ + ShortcutManagerTestUtils LOCAL_JAVA_LIBRARIES := android.test.runner 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 b08bd724a985b..2fd11da3adf34 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.*; import android.annotation.NonNull; import android.annotation.Nullable; @@ -608,14 +609,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { addPackage(packageName, uid, version, packageName); } - private List list(T... array) { - return Arrays.asList(array); - } - - private Set set(Set in) { - return new ArraySet(in); - } - private Signature[] genSignatures(String... signatures) { final Signature[] sigs = new Signature[signatures.length]; for (int i = 0; i < signatures.length; i++){ @@ -799,33 +792,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { runTestOnUiThread(() -> {}); } - public static Bundle makeBundle(Object... keysAndValues) { - Preconditions.checkState((keysAndValues.length % 2) == 0); - - if (keysAndValues.length == 0) { - return null; - } - final Bundle ret = new Bundle(); - - for (int i = keysAndValues.length - 2; i >= 0; i -= 2) { - final String key = keysAndValues[i].toString(); - final Object value = keysAndValues[i + 1]; - - if (value == null) { - ret.putString(key, null); - } else if (value instanceof Integer) { - ret.putInt(key, (Integer) value); - } else if (value instanceof String) { - ret.putString(key, (String) value); - } else if (value instanceof Bundle) { - ret.putBundle(key, (Bundle) value); - } else { - fail("Type not supported yet: " + value.getClass().getName()); - } - } - return ret; - } - /** * Make a shortcut with an ID. */ @@ -923,20 +889,6 @@ 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; - } - - private static void resetAll(Collection mocks) { - for (Object o : mocks) { - reset(o); - } - } - @NonNull private ShortcutInfo findById(List list, String id) { for (ShortcutInfo s : list) { @@ -957,94 +909,14 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked()); } - @NonNull - private List assertShortcutIds(@NonNull List actualShortcuts, - String... expectedIds) { - final HashSet expected = new HashSet<>(list(expectedIds)); - final HashSet actual = new HashSet<>(); - for (ShortcutInfo s : actualShortcuts) { - actual.add(s.getId()); - } - - // Compare the sets. - assertEquals(expected, actual); - return actualShortcuts; - } - - @NonNull - private List assertAllHaveIntents( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNotNull("ID " + s.getId(), s.getIntent()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllNotHaveIntents( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNull("ID " + s.getId(), s.getIntent()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllHaveTitle( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNotNull("ID " + s.getId(), s.getTitle()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllNotHaveTitle( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNull("ID " + s.getId(), s.getTitle()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllNotHaveIcon( - @NonNull List actualShortcuts) { + public static List assertAllNotHaveIcon( + List actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNull("ID " + s.getId(), s.getIcon()); } return actualShortcuts; } - @NonNull - private List assertAllHaveIconResId( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource()); - assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllHaveIconFile( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource()); - assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllHaveIcon( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); - } - return actualShortcuts; - } - @NonNull private List assertAllHaveFlags(@NonNull List actualShortcuts, int shortcutFlags) { @@ -1055,87 +927,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return actualShortcuts; } - @NonNull - private List assertAllKeyFieldsOnly( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllNotKeyFieldsOnly( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly()); - } - return actualShortcuts; - } - - @NonNull - private List assertAllDynamic(@NonNull List actualShortcuts) { - return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_DYNAMIC); - } - - @NonNull - private List assertAllPinned(@NonNull List actualShortcuts) { - return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_PINNED); - } - - @NonNull - private List assertAllDynamicOrPinned( - @NonNull List actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned()); - } - return actualShortcuts; - } - - private void assertDynamicOnly(ShortcutInfo si) { - assertTrue(si.isDynamic()); - assertFalse(si.isPinned()); - } - - private void assertPinnedOnly(ShortcutInfo si) { - assertFalse(si.isDynamic()); - assertTrue(si.isPinned()); - } - - private void assertDynamicAndPinned(ShortcutInfo si) { - assertTrue(si.isDynamic()); - assertTrue(si.isPinned()); - } - - private void assertBitmapSize(int expectedWidth, int expectedHeight, @NonNull Bitmap bitmap) { - assertEquals("width", expectedWidth, bitmap.getWidth()); - assertEquals("height", expectedHeight, bitmap.getHeight()); - } - - private void assertAllUnique(Collection list) { - final Set set = new HashSet<>(); - for (T item : list) { - if (set.contains(item)) { - fail("Duplicate item found: " + item + " (in the list: " + list + ")"); - } - set.add(item); - } - } - - @NonNull - private Bitmap pfdToBitmap(@NonNull ParcelFileDescriptor pfd) { - Preconditions.checkNotNull(pfd); - try { - return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); - } finally { - IoUtils.closeQuietly(pfd); - } - } - - private void assertBundleEmpty(BaseBundle b) { - assertTrue(b == null || b.size() == 0); - } - private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) { return mService.getPackageShortcutForTest(packageName, shortcutId, userId); } @@ -1718,7 +1509,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertFalse(mManager.setDynamicShortcuts(list(si2))); } - public void testIcons() { + public void testIcons() throws IOException { final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512); @@ -2242,7 +2033,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { getCallingUser())), "s1", "s3"); - TestUtils.assertExpectException( + assertExpectException( IllegalArgumentException.class, "package name must also be set", () -> { mLauncherApps.getShortcuts(buildQuery( /* time =*/ 0, /* package= */ null, list("id"), @@ -3341,20 +3132,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(0, shortcuts.getValue().size()); } - private void assertCallbackNotReceived(LauncherApps.Callback mock) { - verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(), - any(UserHandle.class)); - } - - private void assertCallbackReceived(LauncherApps.Callback mock, - UserHandle user, String packageName, String... ids) { - ArgumentCaptor shortcutsCaptor = ArgumentCaptor.forClass(List.class); - - verify(mock, times(1)).onShortcutsChanged(eq(packageName), shortcutsCaptor.capture(), - eq(user)); - assertShortcutIds(shortcutsCaptor.getValue(), ids); - } - public void testLauncherCallback_crossProfile() throws Throwable { prepareCrossProfileDataSet(); @@ -3724,18 +3501,18 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Check the registered packages. dumpsysOnLogcat(); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_1), + set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3756,18 +3533,18 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.cleanUpPackageLocked("abc", USER_0, USER_0); // No changes. - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_1), + set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3787,18 +3564,18 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_0, CALLING_PACKAGE_1); mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_1), + set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3818,17 +3595,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, LAUNCHER_1); mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + set(PackageWithUser.of(USER_10, LAUNCHER_2)), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3846,17 +3623,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, CALLING_PACKAGE_2); mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + set(PackageWithUser.of(USER_10, LAUNCHER_2)), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3874,17 +3651,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, LAUNCHER_2); mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(), - set(user10.getAllLaunchers().keySet())); + set(), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3902,16 +3679,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, CALLING_PACKAGE_1); mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); - assertEquals(makeSet(), - set(user10.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); + assertEquals(set(), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -5052,7 +4829,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutIds( mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0) /* empty */); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10); @@ -5131,7 +4908,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutIds( mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0), "s1", "s4"); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "unrelated profile", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10); @@ -5147,12 +4924,12 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutIds( mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10) /* empty */); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "unrelated profile", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0); }); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "unrelated profile", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0); @@ -5163,16 +4940,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // ShortcutInfo tests public void testShortcutInfoMissingMandatoryFields() { - TestUtils.assertExpectException( + assertExpectException( IllegalArgumentException.class, "ID must be provided", () -> new ShortcutInfo.Builder(getTestContext()).build()); - TestUtils.assertExpectException( + assertExpectException( IllegalArgumentException.class, "title must be provided", () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build() .enforceMandatoryFields()); - TestUtils.assertExpectException( + assertExpectException( NullPointerException.class, "Intent must be provided", () -> new ShortcutInfo.Builder(getTestContext()).setId("id").setTitle("x").build() @@ -5493,7 +5270,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { dumpsysOnLogcat("test1", /* force= */ true); } - public void testDumpsys_withIcons() { + public void testDumpsys_withIcons() throws IOException { testIcons(); // Dump after having some icons. dumpsysOnLogcat("test1", /* force= */ true); diff --git a/services/tests/shortcutmanagerutils/Android.mk b/services/tests/shortcutmanagerutils/Android.mk new file mode 100644 index 0000000000000..701e058571426 --- /dev/null +++ b/services/tests/shortcutmanagerutils/Android.mk @@ -0,0 +1,31 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + mockito-target + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := ShortcutManagerTestUtils + +LOCAL_SDK_VERSION := current + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java new file mode 100644 index 0000000000000..d09b62c2dec65 --- /dev/null +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -0,0 +1,506 @@ +/* + * 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.shortcutmanagertest; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyList; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.Instrumentation; +import android.content.Context; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.BaseBundle; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.test.MoreAsserts; +import android.util.Log; + +import junit.framework.Assert; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.mockito.Mockito; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Predicate; + +public class ShortcutManagerTestUtils { + private static final String TAG = "ShortcutManagerUtils"; + + private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true + + private static final int STANDARD_TIMEOUT_SEC = 5; + + private ShortcutManagerTestUtils() { + } + + private static List readAll(ParcelFileDescriptor pfd) { + try { + try { + final ArrayList ret = new ArrayList<>(); + try (BufferedReader r = new BufferedReader( + new FileReader(pfd.getFileDescriptor()))) { + String line; + while ((line = r.readLine()) != null) { + ret.add(line); + } + r.readLine(); + } + return ret; + } finally { + pfd.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String concatResult(List result) { + final StringBuilder sb = new StringBuilder(); + for (String s : result) { + sb.append(s); + sb.append("\n"); + } + return sb.toString(); + } + + private static List runCommand(Instrumentation instrumentation, String command) { + return runCommand(instrumentation, command, null); + } + private static List runCommand(Instrumentation instrumentation, String command, + Predicate> resultAsserter) { + Log.d(TAG, "Running command: " + command); + final List result; + try { + result = readAll( + instrumentation.getUiAutomation().executeShellCommand(command)); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (resultAsserter != null && !resultAsserter.test(result)) { + fail("Command '" + command + "' failed, output was:\n" + concatResult(result)); + } + return result; + } + + private static void runCommandForNoOutput(Instrumentation instrumentation, String command) { + runCommand(instrumentation, command, result -> result.size() == 0); + } + + private static List runShortcutCommand(Instrumentation instrumentation, String command, + Predicate> resultAsserter) { + return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter); + } + + public static List runShortcutCommandForSuccess(Instrumentation instrumentation, + String command) { + return runShortcutCommand(instrumentation, command, result -> result.contains("Success")); + } + + public static String getDefaultLauncher(Instrumentation instrumentation) { + final String PREFIX = "Launcher: ComponentInfo{"; + final String POSTFIX = "}"; + final List result = runShortcutCommandForSuccess( + instrumentation, "get-default-launcher"); + for (String s : result) { + if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) { + return s.substring(PREFIX.length(), s.length() - POSTFIX.length()); + } + } + fail("Default launcher not found"); + return null; + } + + public static void setDefaultLauncher(Instrumentation instrumentation, String component) { + runCommandForNoOutput(instrumentation, "cmd package set-home-activity " + component); + } + + public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) { + setDefaultLauncher(instrumentation, packageContext.getPackageName() + + "/android.content.pm.cts.shortcutmanager.packages.Launcher"); + } + + public static void overrideConfig(Instrumentation instrumentation, String config) { + runShortcutCommandForSuccess(instrumentation, "override-config " + config); + } + + public static void resetConfig(Instrumentation instrumentation) { + runShortcutCommandForSuccess(instrumentation, "reset-config"); + } + + public static void resetThrottling(Instrumentation instrumentation) { + runShortcutCommandForSuccess(instrumentation, "reset-throttling"); + } + + public static void resetAllThrottling(Instrumentation instrumentation) { + runShortcutCommandForSuccess(instrumentation, "reset-all-throttling"); + } + + public static void clearShortcuts(Instrumentation instrumentation, int userId, + String packageName) { + runShortcutCommandForSuccess(instrumentation, "clear-shortcuts " + + " --user " + userId + " " + packageName); + } + + public static void dumpsysShortcut(Instrumentation instrumentation) { + if (!ENABLE_DUMPSYS) { + return; + } + for (String s : runCommand(instrumentation, "dumpsys shortcut")) { + Log.e(TAG, s); + } + } + + public static Bundle makeBundle(Object... keysAndValues) { + assertTrue((keysAndValues.length % 2) == 0); + + if (keysAndValues.length == 0) { + return null; + } + final Bundle ret = new Bundle(); + + for (int i = keysAndValues.length - 2; i >= 0; i -= 2) { + final String key = keysAndValues[i].toString(); + final Object value = keysAndValues[i + 1]; + + if (value == null) { + ret.putString(key, null); + } else if (value instanceof Integer) { + ret.putInt(key, (Integer) value); + } else if (value instanceof String) { + ret.putString(key, (String) value); + } else if (value instanceof Bundle) { + ret.putBundle(key, (Bundle) value); + } else { + fail("Type not supported yet: " + value.getClass().getName()); + } + } + return ret; + } + + public static List list(T... array) { + return Arrays.asList(array); + } + + public static Set hashSet(Set in) { + return new HashSet(in); + } + + public static Set set(T... values) { + return set(v -> v, values); + } + + public static Set set(Function converter, V... values) { + return set(converter, Arrays.asList(values)); + } + + public static Set set(Function converter, List values) { + final HashSet ret = new HashSet<>(); + for (V v : values) { + ret.add(converter.apply(v)); + } + return ret; + } + + public static void resetAll(Collection mocks) { + for (Object o : mocks) { + reset(o); + } + } + public static void assertExpectException(Class expectedExceptionType, + String expectedExceptionMessageRegex, Runnable r) { + assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r); + } + + public static void assertDynamicShortcutCountExceeded(Runnable r) { + assertExpectException(IllegalArgumentException.class, + "Max number of dynamic shortcuts exceeded", r); + } + + public static void assertExpectException(String message, + Class expectedExceptionType, + String expectedExceptionMessageRegex, Runnable r) { + try { + r.run(); + Assert.fail("Expected exception type " + expectedExceptionType.getName() + + " was not thrown (message=" + message + ")"); + } catch (Throwable e) { + Assert.assertTrue( + "Expected exception type was " + expectedExceptionType.getName() + + " but caught " + e + " (message=" + message + ")", + expectedExceptionType.isAssignableFrom(e.getClass())); + if (expectedExceptionMessageRegex != null) { + MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage()); + } + } + } + + public static List assertShortcutIds(List actualShortcuts, + String... expectedIds) { + final HashSet expected = new HashSet<>(list(expectedIds)); + final HashSet actual = new HashSet<>(); + for (ShortcutInfo s : actualShortcuts) { + actual.add(s.getId()); + } + + // Compare the sets. + assertEquals(expected, actual); + return actualShortcuts; + } + + public static List assertAllHaveIntents( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNotNull("ID " + s.getId(), s.getIntent()); + } + return actualShortcuts; + } + + public static List assertAllNotHaveIntents( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNull("ID " + s.getId(), s.getIntent()); + } + return actualShortcuts; + } + + public static List assertAllHaveTitle( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNotNull("ID " + s.getId(), s.getTitle()); + } + return actualShortcuts; + } + + public static List assertAllNotHaveTitle( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNull("ID " + s.getId(), s.getTitle()); + } + return actualShortcuts; + } + + public static List assertAllHaveIconResId( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource()); + assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile()); + } + return actualShortcuts; + } + + public static List assertAllHaveIconFile( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource()); + assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile()); + } + return actualShortcuts; + } + + public static List assertAllHaveIcon( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); + } + return actualShortcuts; + } + + public static List assertAllKeyFieldsOnly( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly()); + } + return actualShortcuts; + } + + public static List assertAllNotKeyFieldsOnly( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly()); + } + return actualShortcuts; + } + + public static List assertAllDynamic(List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.isDynamic()); + } + return actualShortcuts; + } + + public static List assertAllPinned(List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.isPinned()); + } + return actualShortcuts; + } + + public static List assertAllDynamicOrPinned( + List actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned()); + } + return actualShortcuts; + } + + public static void assertDynamicOnly(ShortcutInfo si) { + assertTrue(si.isDynamic()); + assertFalse(si.isPinned()); + } + + public static void assertPinnedOnly(ShortcutInfo si) { + assertFalse(si.isDynamic()); + assertTrue(si.isPinned()); + } + + public static void assertDynamicAndPinned(ShortcutInfo si) { + assertTrue(si.isDynamic()); + assertTrue(si.isPinned()); + } + + public static void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) { + assertEquals("width", expectedWidth, bitmap.getWidth()); + assertEquals("height", expectedHeight, bitmap.getHeight()); + } + + public static void assertAllUnique(Collection list) { + final Set set = new HashSet<>(); + for (T item : list) { + if (set.contains(item)) { + fail("Duplicate item found: " + item + " (in the list: " + list + ")"); + } + set.add(item); + } + } + + public static Bitmap pfdToBitmap(ParcelFileDescriptor pfd) { + assertNotNull(pfd); + try { + try { + return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); + } finally { + pfd.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void assertBundleEmpty(BaseBundle b) { + assertTrue(b == null || b.size() == 0); + } + + public static void assertCallbackNotReceived(LauncherApps.Callback mock) { + verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(), + any(UserHandle.class)); + } + + public static void assertCallbackReceived(LauncherApps.Callback mock, + UserHandle user, String packageName, String... ids) { + verify(mock).onShortcutsChanged(eq(packageName), checkShortcutIds(ids), + eq(user)); + } + + public static boolean checkAssertSuccess(Runnable r) { + try { + r.run(); + return true; + } catch (AssertionError e) { + return false; + } + } + + public static T checkArgument(Predicate checker, String description, + List matchedCaptor) { + final Matcher m = new BaseMatcher() { + @Override + public boolean matches(Object item) { + if (item == null) { + return false; + } + final T value = (T) item; + if (!checker.test(value)) { + return false; + } + + if (matchedCaptor != null) { + matchedCaptor.add(value); + } + return true; + } + + @Override + public void describeTo(Description d) { + d.appendText(description); + } + }; + return Mockito.argThat(m); + } + + public static List checkShortcutIds(String... ids) { + return checkArgument((List list) -> { + final Set actualSet = set(si -> si.getId(), list); + return actualSet.equals(set(ids)); + + }, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null); + } + + public static void waitUntil(String message, BooleanSupplier condition) { + waitUntil(message, condition, STANDARD_TIMEOUT_SEC); + } + + public static void waitUntil(String message, BooleanSupplier condition, int timeoutSeconds) { + final long timeout = System.currentTimeMillis() + (timeoutSeconds * 1000L); + while (System.currentTimeMillis() < timeout) { + if (condition.getAsBoolean()) { + return; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + fail("Timed out for: " + message); + } +}