/* * 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.mock; 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.ComponentName; import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.Callback; import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.BaseBundle; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; 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.json.JSONException; import org.json.JSONObject; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; /** * Common utility methods for ShortcutManager tests. This is used by both CTS and the unit tests. * Because it's used by CTS too, it can only access the public APIs. */ 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 static final String[] EMPTY_STRINGS = new String[0]; private ShortcutManagerTestUtils() { } public static List<String> readAll(File file) throws FileNotFoundException { return readAll(ParcelFileDescriptor.open( file.getAbsoluteFile(), ParcelFileDescriptor.MODE_READ_ONLY)); } public static List<String> readAll(ParcelFileDescriptor pfd) { try { try { final ArrayList<String> 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); } } public static String concatResult(List<String> result) { final StringBuilder sb = new StringBuilder(); for (String s : result) { sb.append(s); sb.append("\n"); } return sb.toString(); } public static boolean resultContains(List<String> result, String expected) { for (String line : result) { if (line.contains(expected)) { return true; } } return false; } public static List<String> assertSuccess(List<String> result) { if (!resultContains(result, "Success")) { fail("Command failed. Result was:\n" + concatResult(result)); } return result; } public static List<String> assertContains(List<String> result, String expected) { if (!resultContains(result, expected)) { fail("Didn't contain expected string=" + expected + "\nActual:\n" + concatResult(result)); } return result; } public static List<String> runCommand(Instrumentation instrumentation, String command) { return runCommand(instrumentation, command, null); } public static List<String> runCommand(Instrumentation instrumentation, String command, Predicate<List<String>> resultAsserter) { Log.d(TAG, "Running command: " + command); final List<String> 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; } public static void runCommandForNoOutput(Instrumentation instrumentation, String command) { runCommand(instrumentation, command, result -> result.size() == 0); } public static List<String> runShortcutCommand(Instrumentation instrumentation, String command, Predicate<List<String>> resultAsserter) { return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter); } public static List<String> 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<String> 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) { runCommand(instrumentation, "cmd package set-home-activity --user " + instrumentation.getContext().getUserId() + " " + component, result -> result.contains("Success")); } 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 anyContains(List<String> result, String expected) { for (String l : result) { if (l.contains(expected)) { return; } } fail("Result didn't contain '" + expected + "': was\n" + result); } public static void enableComponent(Instrumentation instrumentation, ComponentName cn, boolean enable) { final String word = (enable ? "enable" : "disable"); runCommand(instrumentation, "pm " + word + " " + cn.flattenToString() , result ->concatResult(result).contains(word)); } public static void appOps(Instrumentation instrumentation, String packageName, String op, String mode) { runCommand(instrumentation, "appops set " + packageName + " " + op + " " + mode); } public static void dumpsysShortcut(Instrumentation instrumentation) { if (!ENABLE_DUMPSYS) { return; } Log.e(TAG, "Dumpsys shortcut"); for (String s : runCommand(instrumentation, "dumpsys shortcut")) { Log.e(TAG, s); } } public static JSONObject getCheckinDump(Instrumentation instrumentation) throws JSONException { return new JSONObject(concatResult(runCommand(instrumentation, "dumpsys shortcut -c"))); } public static boolean isLowRamDevice(Instrumentation instrumentation) throws JSONException { return getCheckinDump(instrumentation).getBoolean("lowRam"); } public static int getIconSize(Instrumentation instrumentation) throws JSONException { return getCheckinDump(instrumentation).getInt("iconSize"); } 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 PersistableBundle makePersistableBundle(Object... keysAndValues) { assertTrue((keysAndValues.length % 2) == 0); if (keysAndValues.length == 0) { return null; } final PersistableBundle ret = new PersistableBundle(); 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 PersistableBundle) { ret.putPersistableBundle(key, (PersistableBundle) value); } else { fail("Type not supported yet: " + value.getClass().getName()); } } return ret; } public static <T> List<T> list(T... array) { return Arrays.asList(array); } public static <T> Set<T> hashSet(Set<T> in) { return new LinkedHashSet<>(in); } public static <T> Set<T> set(T... values) { return set(v -> v, values); } public static <T, V> Set<T> set(Function<V, T> converter, V... values) { return set(converter, Arrays.asList(values)); } public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) { final LinkedHashSet<T> ret = new LinkedHashSet<>(); 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 <T extends Collection<?>> T assertEmpty(T collection) { if (collection == null) { return collection; // okay. } assertEquals(0, collection.size()); return collection; } public static List<ShortcutInfo> filter(List<ShortcutInfo> list, Predicate<ShortcutInfo> p) { final ArrayList<ShortcutInfo> ret = new ArrayList<>(list); ret.removeIf(si -> !p.test(si)); return ret; } public static List<ShortcutInfo> filterByActivity(List<ShortcutInfo> list, ComponentName activity) { return filter(list, si -> (si.getActivity().equals(activity) && (si.isDeclaredInManifest() || si.isDynamic()))); } public static List<ShortcutInfo> changedSince(List<ShortcutInfo> list, long time) { return filter(list, si -> si.getLastChangedTimestamp() >= time); } @FunctionalInterface public interface ExceptionRunnable { void run() throws Exception; } public static void assertExpectException(Class<? extends Throwable> expectedExceptionType, String expectedExceptionMessageRegex, ExceptionRunnable r) { assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r); } public static void assertCannotUpdateImmutable(Runnable r) { assertExpectException( IllegalArgumentException.class, "may not be manipulated via APIs", r::run); } public static void assertDynamicShortcutCountExceeded(Runnable r) { assertExpectException(IllegalArgumentException.class, "Max number of dynamic shortcuts exceeded", r::run); } public static void assertExpectException(String message, Class<? extends Throwable> expectedExceptionType, String expectedExceptionMessageRegex, ExceptionRunnable r) { try { r.run(); } 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()); } return; // Pass } Assert.fail("Expected exception type " + expectedExceptionType.getName() + " was not thrown"); } public static List<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts, String... expectedIds) { final SortedSet<String> expected = new TreeSet<>(list(expectedIds)); final SortedSet<String> actual = new TreeSet<>(); for (ShortcutInfo s : actualShortcuts) { actual.add(s.getId()); } // Compare the sets. assertEquals(expected, actual); return actualShortcuts; } public static List<ShortcutInfo> assertShortcutIdsOrdered(List<ShortcutInfo> actualShortcuts, String... expectedIds) { final ArrayList<String> expected = new ArrayList<>(list(expectedIds)); final ArrayList<String> actual = new ArrayList<>(); for (ShortcutInfo s : actualShortcuts) { actual.add(s.getId()); } assertEquals(expected, actual); return actualShortcuts; } public static List<ShortcutInfo> assertAllHaveIntents( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNotNull("ID " + s.getId(), s.getIntent()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllNotHaveIntents( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNull("ID " + s.getId(), s.getIntent()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllHaveTitle( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNotNull("ID " + s.getId(), s.getShortLabel()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllNotHaveTitle( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNull("ID " + s.getId(), s.getShortLabel()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllKeyFieldsOnly( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllNotKeyFieldsOnly( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllDynamic(List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.isDynamic()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllPinned(List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.isPinned()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllDynamicOrPinned( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllManifest( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.isDeclaredInManifest()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllNotManifest( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertFalse("ID " + s.getId(), s.isDeclaredInManifest()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllDisabled( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), !s.isEnabled()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllEnabled( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.isEnabled()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllImmutable( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.isImmutable()); } return actualShortcuts; } public static void assertDynamicOnly(ShortcutInfo si) { assertTrue(si.isDynamic()); assertFalse(si.isPinned()); } public static void assertPinnedOnly(ShortcutInfo si) { assertFalse(si.isDynamic()); assertFalse(si.isDeclaredInManifest()); 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 <T> void assertAllUnique(Collection<T> list) { final Set<Object> set = new LinkedHashSet<>(); for (T item : list) { if (set.contains(item)) { fail("Duplicate item found: " + item + " (in the list: " + list + ")"); } set.add(item); } } public static ShortcutInfo findShortcut(List<ShortcutInfo> list, String id) { for (ShortcutInfo si : list) { if (si.getId().equals(id)) { return si; } } fail("Shortcut " + id + " not found in the list"); return null; } 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> T checkArgument(Predicate<T> checker, String description, List<T> matchedCaptor) { final Matcher<T> m = new BaseMatcher<T>() { @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<ShortcutInfo> checkShortcutIds(String... ids) { return checkArgument((List<ShortcutInfo> list) -> { final Set<String> actualSet = set(si -> si.getId(), list); return actualSet.equals(set(ids)); }, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null); } public static ShortcutInfo parceled(ShortcutInfo si) { Parcel p = Parcel.obtain(); p.writeParcelable(si, 0); p.setDataPosition(0); ShortcutInfo si2 = p.readParcelable(ShortcutManagerTestUtils.class.getClassLoader()); p.recycle(); return si2; } public static List<ShortcutInfo> cloneShortcutList(List<ShortcutInfo> list) { if (list == null) { return null; } final List<ShortcutInfo> ret = new ArrayList<>(list.size()); for (ShortcutInfo si : list) { ret.add(parceled(si)); } return ret; } private static final Comparator<ShortcutInfo> sRankComparator = (ShortcutInfo a, ShortcutInfo b) -> Integer.compare(a.getRank(), b.getRank()); public static List<ShortcutInfo> sortedByRank(List<ShortcutInfo> shortcuts) { final ArrayList<ShortcutInfo> ret = new ArrayList<>(shortcuts); Collections.sort(ret, sRankComparator); return ret; } 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); } public static ShortcutListAsserter assertWith(List<ShortcutInfo> list) { return new ShortcutListAsserter(list); } /** * New style assertion that allows chained calls. */ public static class ShortcutListAsserter { private final ShortcutListAsserter mOriginal; private final List<ShortcutInfo> mList; ShortcutListAsserter(List<ShortcutInfo> list) { this(null, list); } private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> list) { mOriginal = (original == null) ? this : original; mList = (list == null) ? new ArrayList<>(0) : new ArrayList<>(list); } public ShortcutListAsserter revertToOriginalList() { return mOriginal; } public ShortcutListAsserter selectDynamic() { return new ShortcutListAsserter(this, filter(mList, ShortcutInfo::isDynamic)); } public ShortcutListAsserter selectManifest() { return new ShortcutListAsserter(this, filter(mList, ShortcutInfo::isDeclaredInManifest)); } public ShortcutListAsserter selectPinned() { return new ShortcutListAsserter(this, filter(mList, ShortcutInfo::isPinned)); } public ShortcutListAsserter selectByActivity(ComponentName activity) { return new ShortcutListAsserter(this, ShortcutManagerTestUtils.filterByActivity(mList, activity)); } public ShortcutListAsserter selectByChangedSince(long time) { return new ShortcutListAsserter(this, ShortcutManagerTestUtils.changedSince(mList, time)); } public ShortcutListAsserter selectByIds(String... ids) { final Set<String> idSet = set(ids); final ArrayList<ShortcutInfo> selected = new ArrayList<>(); for (ShortcutInfo si : mList) { if (idSet.contains(si.getId())) { selected.add(si); idSet.remove(si.getId()); } } if (idSet.size() > 0) { fail("Shortcuts not found for IDs=" + idSet); } return new ShortcutListAsserter(this, selected); } public ShortcutListAsserter toSortByRank() { return new ShortcutListAsserter(this, ShortcutManagerTestUtils.sortedByRank(mList)); } public ShortcutListAsserter call(Consumer<List<ShortcutInfo>> c) { c.accept(mList); return this; } public ShortcutListAsserter haveIds(String... expectedIds) { assertShortcutIds(mList, expectedIds); return this; } public ShortcutListAsserter haveIdsOrdered(String... expectedIds) { assertShortcutIdsOrdered(mList, expectedIds); return this; } private ShortcutListAsserter haveSequentialRanks() { for (int i = 0; i < mList.size(); i++) { final ShortcutInfo si = mList.get(i); assertEquals("Rank not sequential: id=" + si.getId(), i, si.getRank()); } return this; } public ShortcutListAsserter haveRanksInOrder(String... expectedIds) { toSortByRank() .haveSequentialRanks() .haveIdsOrdered(expectedIds); return this; } public ShortcutListAsserter isEmpty() { assertEquals(0, mList.size()); return this; } public ShortcutListAsserter areAllDynamic() { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isDynamic())); return this; } public ShortcutListAsserter areAllNotDynamic() { forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isDynamic())); return this; } public ShortcutListAsserter areAllPinned() { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isPinned())); return this; } public ShortcutListAsserter areAllNotPinned() { forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isPinned())); return this; } public ShortcutListAsserter areAllManifest() { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isDeclaredInManifest())); return this; } public ShortcutListAsserter areAllNotManifest() { forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isDeclaredInManifest())); return this; } public ShortcutListAsserter areAllImmutable() { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isImmutable())); return this; } public ShortcutListAsserter areAllMutable() { forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isImmutable())); return this; } public ShortcutListAsserter areAllEnabled() { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isEnabled())); return this; } public ShortcutListAsserter areAllDisabled() { forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isEnabled())); return this; } public ShortcutListAsserter areAllWithKeyFieldsOnly() { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.hasKeyFieldsOnly())); return this; } public ShortcutListAsserter areAllNotWithKeyFieldsOnly() { forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.hasKeyFieldsOnly())); return this; } public ShortcutListAsserter areAllWithActivity(ComponentName activity) { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.getActivity().equals(activity))); return this; } public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) { boolean found = false; for (int i = 0; i < mList.size(); i++) { final ShortcutInfo si = mList.get(i); found = true; sa.accept(si); } assertTrue("No shortcuts found.", found); return this; } public ShortcutListAsserter forShortcut(Predicate<ShortcutInfo> p, Consumer<ShortcutInfo> sa) { boolean found = false; for (int i = 0; i < mList.size(); i++) { final ShortcutInfo si = mList.get(i); if (p.test(si)) { found = true; try { sa.accept(si); } catch (Throwable e) { throw new AssertionError("Assertion failed for shortcut " + si.getId(), e); } } } assertTrue("Shortcut with the given condition not found.", found); return this; } public ShortcutListAsserter forShortcutWithId(String id, Consumer<ShortcutInfo> sa) { forShortcut(si -> si.getId().equals(id), sa); return this; } } public static void assertBundlesEqual(BaseBundle b1, BaseBundle b2) { if (b1 == null && b2 == null) { return; // pass } assertNotNull("b1 is null but b2 is not", b1); assertNotNull("b2 is null but b1 is not", b2); // HashSet makes the error message readable. assertEquals(set(b1.keySet()), set(b2.keySet())); for (String key : b1.keySet()) { final Object v1 = b1.get(key); final Object v2 = b2.get(key); if (v1 == null) { if (v2 == null) { return; } } if (v1.equals(v2)) { return; } assertTrue("Only either value is null: key=" + key + " b1=" + b1 + " b2=" + b2, v1 != null && v2 != null); assertEquals("Class mismatch: key=" + key, v1.getClass(), v2.getClass()); if (v1 instanceof BaseBundle) { assertBundlesEqual((BaseBundle) v1, (BaseBundle) v2); } else if (v1 instanceof boolean[]) { assertTrue(Arrays.equals((boolean[]) v1, (boolean[]) v2)); } else if (v1 instanceof int[]) { MoreAsserts.assertEquals((int[]) v1, (int[]) v2); } else if (v1 instanceof double[]) { MoreAsserts.assertEquals((double[]) v1, (double[]) v2); } else if (v1 instanceof String[]) { MoreAsserts.assertEquals((String[]) v1, (String[]) v2); } else if (v1 instanceof Double) { if (((Double) v1).isNaN()) { assertTrue(((Double) v2).isNaN()); } else { assertEquals(v1, v2); } } else { assertEquals(v1, v2); } } } public static void waitOnMainThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); new Handler(Looper.getMainLooper()).post(() -> latch.countDown()); latch.await(); } public static class LauncherCallbackAsserter { private final LauncherApps.Callback mCallback = mock(LauncherApps.Callback.class); private Callback getMockCallback() { return mCallback; } public LauncherCallbackAsserter assertNoCallbackCalled() { verify(mCallback, times(0)).onShortcutsChanged( anyString(), any(List.class), any(UserHandle.class)); return this; } public LauncherCallbackAsserter assertNoCallbackCalledForPackage( String publisherPackageName) { verify(mCallback, times(0)).onShortcutsChanged( eq(publisherPackageName), any(List.class), any(UserHandle.class)); return this; } public LauncherCallbackAsserter assertNoCallbackCalledForPackageAndUser( String publisherPackageName, UserHandle publisherUserHandle) { verify(mCallback, times(0)).onShortcutsChanged( eq(publisherPackageName), any(List.class), eq(publisherUserHandle)); return this; } public ShortcutListAsserter assertCallbackCalledForPackageAndUser( String publisherPackageName, UserHandle publisherUserHandle) { final ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class); verify(mCallback, times(1)).onShortcutsChanged( eq(publisherPackageName), shortcuts.capture(), eq(publisherUserHandle)); return new ShortcutListAsserter(shortcuts.getValue()); } } public static LauncherCallbackAsserter assertForLauncherCallback( LauncherApps launcherApps, Runnable body) throws InterruptedException { final LauncherCallbackAsserter asserter = new LauncherCallbackAsserter(); launcherApps.registerCallback(asserter.getMockCallback(), new Handler(Looper.getMainLooper())); body.run(); waitOnMainThread(); // TODO unregister doesn't work well during unit tests. Figure out and fix it. // launcherApps.unregisterCallback(asserter.getMockCallback()); return asserter; } public static void retryUntil(BooleanSupplier checker, String message) { retryUntil(checker, message, 30); } public static void retryUntil(BooleanSupplier checker, String message, long timeoutSeconds) { final long timeOut = System.currentTimeMillis() + timeoutSeconds * 1000; while (!checker.getAsBoolean()) { if (System.currentTimeMillis() > timeOut) { break; } try { Thread.sleep(200); } catch (InterruptedException ignore) { } } assertTrue(message, checker.getAsBoolean()); } }