| /* |
| * 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.atLeastOnce; |
| 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.ArgumentMatcher; |
| import org.mockito.ArgumentMatchers; |
| import org.mockito.Mockito; |
| import org.mockito.hamcrest.MockitoHamcrest; |
| |
| 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 MockitoHamcrest.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 final <T> T anyOrNull(Class<T> clazz) { |
| return ArgumentMatchers.argThat(value -> true); |
| } |
| |
| public static final String anyStringOrNull() { |
| return ArgumentMatchers.argThat(value -> true); |
| } |
| |
| public static ShortcutListAsserter assertWith(List<ShortcutInfo> list) { |
| return new ShortcutListAsserter(list); |
| } |
| |
| public static ShortcutListAsserter assertWith(ShortcutInfo... list) { |
| return assertWith(list(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 selectFloating() { |
| return new ShortcutListAsserter(this, |
| filter(mList, (si -> si.isPinned() |
| && !(si.isDynamic() || si.isDeclaredInManifest())))); |
| } |
| |
| 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 areAllFloating() { |
| forAllShortcuts(s -> assertTrue("id=" + s.getId(), |
| s.isPinned() && !s.isDeclaredInManifest() && !s.isDynamic())); |
| return this; |
| } |
| |
| public ShortcutListAsserter areAllNotFloating() { |
| forAllShortcuts(s -> assertTrue("id=" + s.getId(), |
| !(s.isPinned() && !s.isDeclaredInManifest() && !s.isDynamic()))); |
| return this; |
| } |
| |
| public ShortcutListAsserter areAllOrphan() { |
| forAllShortcuts(s -> assertTrue("id=" + s.getId(), |
| !s.isPinned() && !s.isDeclaredInManifest() && !s.isDynamic())); |
| return this; |
| } |
| |
| public ShortcutListAsserter areAllNotOrphan() { |
| forAllShortcuts(s -> assertTrue("id=" + s.getId(), |
| s.isPinned() || s.isDeclaredInManifest() || s.isDynamic())); |
| 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 -> assertEquals("id=" + s.getId(), activity, s.getActivity())); |
| return this; |
| } |
| |
| public ShortcutListAsserter areAllWithNoActivity() { |
| forAllShortcuts(s -> assertNull("id=" + s.getId(), s.getActivity())); |
| return this; |
| } |
| |
| public ShortcutListAsserter areAllWithIntent() { |
| forAllShortcuts(s -> assertNotNull("id=" + s.getId(), s.getIntent())); |
| return this; |
| } |
| |
| public ShortcutListAsserter areAllWithNoIntent() { |
| forAllShortcuts(s -> assertNull("id=" + s.getId(), s.getIntent())); |
| 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, atLeastOnce()).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 LauncherCallbackAsserter assertForLauncherCallbackNoThrow( |
| LauncherApps launcherApps, Runnable body) { |
| try { |
| return assertForLauncherCallback(launcherApps, body); |
| } catch (InterruptedException e) { |
| fail("Caught InterruptedException"); |
| return null; // Never happens. |
| } |
| } |
| |
| 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()); |
| } |
| } |