Home | History | Annotate | Download | only in shortcutmanagertest
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.server.pm.shortcutmanagertest;
     17 
     18 import static junit.framework.Assert.assertEquals;
     19 import static junit.framework.Assert.assertFalse;
     20 import static junit.framework.Assert.assertNotNull;
     21 import static junit.framework.Assert.assertNull;
     22 import static junit.framework.Assert.assertTrue;
     23 import static junit.framework.Assert.fail;
     24 
     25 import static org.mockito.Matchers.any;
     26 import static org.mockito.Matchers.anyList;
     27 import static org.mockito.Matchers.anyString;
     28 import static org.mockito.Matchers.eq;
     29 import static org.mockito.Mockito.mock;
     30 import static org.mockito.Mockito.reset;
     31 import static org.mockito.Mockito.times;
     32 import static org.mockito.Mockito.verify;
     33 
     34 import android.app.Instrumentation;
     35 import android.content.ComponentName;
     36 import android.content.Context;
     37 import android.content.pm.LauncherApps;
     38 import android.content.pm.LauncherApps.Callback;
     39 import android.content.pm.ShortcutInfo;
     40 import android.graphics.Bitmap;
     41 import android.graphics.BitmapFactory;
     42 import android.os.BaseBundle;
     43 import android.os.Bundle;
     44 import android.os.Handler;
     45 import android.os.Looper;
     46 import android.os.Parcel;
     47 import android.os.ParcelFileDescriptor;
     48 import android.os.PersistableBundle;
     49 import android.os.UserHandle;
     50 import android.test.MoreAsserts;
     51 import android.util.Log;
     52 
     53 import junit.framework.Assert;
     54 
     55 import org.hamcrest.BaseMatcher;
     56 import org.hamcrest.Description;
     57 import org.hamcrest.Matcher;
     58 import org.json.JSONException;
     59 import org.json.JSONObject;
     60 import org.mockito.ArgumentCaptor;
     61 import org.mockito.Mockito;
     62 
     63 import java.io.BufferedReader;
     64 import java.io.File;
     65 import java.io.FileNotFoundException;
     66 import java.io.FileReader;
     67 import java.io.IOException;
     68 import java.util.ArrayList;
     69 import java.util.Arrays;
     70 import java.util.Collection;
     71 import java.util.Collections;
     72 import java.util.Comparator;
     73 import java.util.LinkedHashSet;
     74 import java.util.List;
     75 import java.util.Set;
     76 import java.util.SortedSet;
     77 import java.util.TreeSet;
     78 import java.util.concurrent.CountDownLatch;
     79 import java.util.function.BooleanSupplier;
     80 import java.util.function.Consumer;
     81 import java.util.function.Function;
     82 import java.util.function.Predicate;
     83 
     84 /**
     85  * Common utility methods for ShortcutManager tests.  This is used by both CTS and the unit tests.
     86  * Because it's used by CTS too, it can only access the public APIs.
     87  */
     88 public class ShortcutManagerTestUtils {
     89     private static final String TAG = "ShortcutManagerUtils";
     90 
     91     private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true
     92 
     93     private static final int STANDARD_TIMEOUT_SEC = 5;
     94 
     95     private static final String[] EMPTY_STRINGS = new String[0];
     96 
     97     private ShortcutManagerTestUtils() {
     98     }
     99 
    100     public static List<String> readAll(File file) throws FileNotFoundException {
    101         return readAll(ParcelFileDescriptor.open(
    102                 file.getAbsoluteFile(), ParcelFileDescriptor.MODE_READ_ONLY));
    103     }
    104 
    105     public static List<String> readAll(ParcelFileDescriptor pfd) {
    106         try {
    107             try {
    108                 final ArrayList<String> ret = new ArrayList<>();
    109                 try (BufferedReader r = new BufferedReader(
    110                         new FileReader(pfd.getFileDescriptor()))) {
    111                     String line;
    112                     while ((line = r.readLine()) != null) {
    113                         ret.add(line);
    114                     }
    115                     r.readLine();
    116                 }
    117                 return ret;
    118             } finally {
    119                 pfd.close();
    120             }
    121         } catch (IOException e) {
    122             throw new RuntimeException(e);
    123         }
    124     }
    125 
    126     public static String concatResult(List<String> result) {
    127         final StringBuilder sb = new StringBuilder();
    128         for (String s : result) {
    129             sb.append(s);
    130             sb.append("\n");
    131         }
    132         return sb.toString();
    133     }
    134 
    135     public static boolean resultContains(List<String> result, String expected) {
    136         for (String line : result) {
    137             if (line.contains(expected)) {
    138                 return true;
    139             }
    140         }
    141         return false;
    142     }
    143 
    144     public static List<String> assertSuccess(List<String> result) {
    145         if (!resultContains(result, "Success")) {
    146             fail("Command failed.  Result was:\n" + concatResult(result));
    147         }
    148         return result;
    149     }
    150 
    151     public static List<String> assertContains(List<String> result, String expected) {
    152         if (!resultContains(result, expected)) {
    153             fail("Didn't contain expected string=" + expected
    154                     + "\nActual:\n" + concatResult(result));
    155         }
    156         return result;
    157     }
    158 
    159     public static List<String> runCommand(Instrumentation instrumentation, String command) {
    160         return runCommand(instrumentation, command, null);
    161     }
    162     public static List<String> runCommand(Instrumentation instrumentation, String command,
    163             Predicate<List<String>> resultAsserter) {
    164         Log.d(TAG, "Running command: " + command);
    165         final List<String> result;
    166         try {
    167             result = readAll(
    168                     instrumentation.getUiAutomation().executeShellCommand(command));
    169         } catch (Exception e) {
    170             throw new RuntimeException(e);
    171         }
    172         if (resultAsserter != null && !resultAsserter.test(result)) {
    173             fail("Command '" + command + "' failed, output was:\n" + concatResult(result));
    174         }
    175         return result;
    176     }
    177 
    178     public static void runCommandForNoOutput(Instrumentation instrumentation, String command) {
    179         runCommand(instrumentation, command, result -> result.size() == 0);
    180     }
    181 
    182     public static List<String> runShortcutCommand(Instrumentation instrumentation, String command,
    183             Predicate<List<String>> resultAsserter) {
    184         return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter);
    185     }
    186 
    187     public static List<String> runShortcutCommandForSuccess(Instrumentation instrumentation,
    188             String command) {
    189         return runShortcutCommand(instrumentation, command, result -> result.contains("Success"));
    190     }
    191 
    192     public static String getDefaultLauncher(Instrumentation instrumentation) {
    193         final String PREFIX = "Launcher: ComponentInfo{";
    194         final String POSTFIX = "}";
    195         final List<String> result = runShortcutCommandForSuccess(
    196                 instrumentation, "get-default-launcher");
    197         for (String s : result) {
    198             if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
    199                 return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
    200             }
    201         }
    202         fail("Default launcher not found");
    203         return null;
    204     }
    205 
    206     public static void setDefaultLauncher(Instrumentation instrumentation, String component) {
    207         runCommand(instrumentation, "cmd package set-home-activity --user "
    208                 + instrumentation.getContext().getUserId() + " " + component,
    209                 result -> result.contains("Success"));
    210     }
    211 
    212     public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
    213         setDefaultLauncher(instrumentation, packageContext.getPackageName()
    214                 + "/android.content.pm.cts.shortcutmanager.packages.Launcher");
    215     }
    216 
    217     public static void overrideConfig(Instrumentation instrumentation, String config) {
    218         runShortcutCommandForSuccess(instrumentation, "override-config " + config);
    219     }
    220 
    221     public static void resetConfig(Instrumentation instrumentation) {
    222         runShortcutCommandForSuccess(instrumentation, "reset-config");
    223     }
    224 
    225     public static void resetThrottling(Instrumentation instrumentation) {
    226         runShortcutCommandForSuccess(instrumentation, "reset-throttling");
    227     }
    228 
    229     public static void resetAllThrottling(Instrumentation instrumentation) {
    230         runShortcutCommandForSuccess(instrumentation, "reset-all-throttling");
    231     }
    232 
    233     public static void clearShortcuts(Instrumentation instrumentation, int userId,
    234             String packageName) {
    235         runShortcutCommandForSuccess(instrumentation, "clear-shortcuts "
    236                 + " --user " + userId + " " + packageName);
    237     }
    238 
    239     public static void anyContains(List<String> result, String expected) {
    240         for (String l : result) {
    241             if (l.contains(expected)) {
    242                 return;
    243             }
    244         }
    245         fail("Result didn't contain '" + expected + "': was\n" + result);
    246     }
    247 
    248     public static void enableComponent(Instrumentation instrumentation, ComponentName cn,
    249             boolean enable) {
    250 
    251         final String word = (enable ? "enable" : "disable");
    252         runCommand(instrumentation,
    253                 "pm " + word + " " + cn.flattenToString()
    254                 , result ->concatResult(result).contains(word));
    255     }
    256 
    257     public static void appOps(Instrumentation instrumentation, String packageName,
    258             String op, String mode) {
    259         runCommand(instrumentation, "appops set " + packageName + " " + op + " " + mode);
    260     }
    261 
    262     public static void dumpsysShortcut(Instrumentation instrumentation) {
    263         if (!ENABLE_DUMPSYS) {
    264             return;
    265         }
    266         Log.e(TAG, "Dumpsys shortcut");
    267         for (String s : runCommand(instrumentation, "dumpsys shortcut")) {
    268             Log.e(TAG, s);
    269         }
    270     }
    271 
    272     public static JSONObject getCheckinDump(Instrumentation instrumentation) throws JSONException {
    273         return new JSONObject(concatResult(runCommand(instrumentation, "dumpsys shortcut -c")));
    274     }
    275 
    276     public static boolean isLowRamDevice(Instrumentation instrumentation) throws JSONException {
    277         return getCheckinDump(instrumentation).getBoolean("lowRam");
    278     }
    279 
    280     public static int getIconSize(Instrumentation instrumentation) throws JSONException {
    281         return getCheckinDump(instrumentation).getInt("iconSize");
    282     }
    283 
    284     public static Bundle makeBundle(Object... keysAndValues) {
    285         assertTrue((keysAndValues.length % 2) == 0);
    286 
    287         if (keysAndValues.length == 0) {
    288             return null;
    289         }
    290         final Bundle ret = new Bundle();
    291 
    292         for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
    293             final String key = keysAndValues[i].toString();
    294             final Object value = keysAndValues[i + 1];
    295 
    296             if (value == null) {
    297                 ret.putString(key, null);
    298             } else if (value instanceof Integer) {
    299                 ret.putInt(key, (Integer) value);
    300             } else if (value instanceof String) {
    301                 ret.putString(key, (String) value);
    302             } else if (value instanceof Bundle) {
    303                 ret.putBundle(key, (Bundle) value);
    304             } else {
    305                 fail("Type not supported yet: " + value.getClass().getName());
    306             }
    307         }
    308         return ret;
    309     }
    310 
    311     public static PersistableBundle makePersistableBundle(Object... keysAndValues) {
    312         assertTrue((keysAndValues.length % 2) == 0);
    313 
    314         if (keysAndValues.length == 0) {
    315             return null;
    316         }
    317         final PersistableBundle ret = new PersistableBundle();
    318 
    319         for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
    320             final String key = keysAndValues[i].toString();
    321             final Object value = keysAndValues[i + 1];
    322 
    323             if (value == null) {
    324                 ret.putString(key, null);
    325             } else if (value instanceof Integer) {
    326                 ret.putInt(key, (Integer) value);
    327             } else if (value instanceof String) {
    328                 ret.putString(key, (String) value);
    329             } else if (value instanceof PersistableBundle) {
    330                 ret.putPersistableBundle(key, (PersistableBundle) value);
    331             } else {
    332                 fail("Type not supported yet: " + value.getClass().getName());
    333             }
    334         }
    335         return ret;
    336     }
    337 
    338     public static <T> List<T> list(T... array) {
    339         return Arrays.asList(array);
    340     }
    341 
    342     public static <T> Set<T> hashSet(Set<T> in) {
    343         return new LinkedHashSet<>(in);
    344     }
    345 
    346     public static <T> Set<T> set(T... values) {
    347         return set(v -> v, values);
    348     }
    349 
    350     public static <T, V> Set<T> set(Function<V, T> converter, V... values) {
    351         return set(converter, Arrays.asList(values));
    352     }
    353 
    354     public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) {
    355         final LinkedHashSet<T> ret = new LinkedHashSet<>();
    356         for (V v : values) {
    357             ret.add(converter.apply(v));
    358         }
    359         return ret;
    360     }
    361 
    362     public static void resetAll(Collection<?> mocks) {
    363         for (Object o : mocks) {
    364             reset(o);
    365         }
    366     }
    367 
    368     public static <T extends Collection<?>> T assertEmpty(T collection) {
    369         if (collection == null) {
    370             return collection; // okay.
    371         }
    372         assertEquals(0, collection.size());
    373         return collection;
    374     }
    375 
    376     public static List<ShortcutInfo> filter(List<ShortcutInfo> list, Predicate<ShortcutInfo> p) {
    377         final ArrayList<ShortcutInfo> ret = new ArrayList<>(list);
    378         ret.removeIf(si -> !p.test(si));
    379         return ret;
    380     }
    381 
    382     public static List<ShortcutInfo> filterByActivity(List<ShortcutInfo> list,
    383             ComponentName activity) {
    384         return filter(list, si ->
    385                 (si.getActivity().equals(activity)
    386                         && (si.isDeclaredInManifest() || si.isDynamic())));
    387     }
    388 
    389     public static List<ShortcutInfo> changedSince(List<ShortcutInfo> list, long time) {
    390         return filter(list, si -> si.getLastChangedTimestamp() >= time);
    391     }
    392 
    393     @FunctionalInterface
    394     public interface ExceptionRunnable {
    395         void run() throws Exception;
    396     }
    397 
    398     public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
    399             String expectedExceptionMessageRegex, ExceptionRunnable r) {
    400         assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
    401     }
    402 
    403     public static void assertCannotUpdateImmutable(Runnable r) {
    404         assertExpectException(
    405                 IllegalArgumentException.class, "may not be manipulated via APIs", r::run);
    406     }
    407 
    408     public static void assertDynamicShortcutCountExceeded(Runnable r) {
    409         assertExpectException(IllegalArgumentException.class,
    410                 "Max number of dynamic shortcuts exceeded", r::run);
    411     }
    412 
    413     public static void assertExpectException(String message,
    414             Class<? extends Throwable> expectedExceptionType,
    415             String expectedExceptionMessageRegex, ExceptionRunnable r) {
    416         try {
    417             r.run();
    418         } catch (Throwable e) {
    419             Assert.assertTrue(
    420                     "Expected exception type was " + expectedExceptionType.getName()
    421                             + " but caught " + e + " (message=" + message + ")",
    422                     expectedExceptionType.isAssignableFrom(e.getClass()));
    423             if (expectedExceptionMessageRegex != null) {
    424                 MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
    425             }
    426             return; // Pass
    427         }
    428         Assert.fail("Expected exception type " + expectedExceptionType.getName()
    429                 + " was not thrown");
    430     }
    431 
    432     public static List<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts,
    433             String... expectedIds) {
    434         final SortedSet<String> expected = new TreeSet<>(list(expectedIds));
    435         final SortedSet<String> actual = new TreeSet<>();
    436         for (ShortcutInfo s : actualShortcuts) {
    437             actual.add(s.getId());
    438         }
    439 
    440         // Compare the sets.
    441         assertEquals(expected, actual);
    442         return actualShortcuts;
    443     }
    444 
    445     public static List<ShortcutInfo> assertShortcutIdsOrdered(List<ShortcutInfo> actualShortcuts,
    446             String... expectedIds) {
    447         final ArrayList<String> expected = new ArrayList<>(list(expectedIds));
    448         final ArrayList<String> actual = new ArrayList<>();
    449         for (ShortcutInfo s : actualShortcuts) {
    450             actual.add(s.getId());
    451         }
    452         assertEquals(expected, actual);
    453         return actualShortcuts;
    454     }
    455 
    456     public static List<ShortcutInfo> assertAllHaveIntents(
    457             List<ShortcutInfo> actualShortcuts) {
    458         for (ShortcutInfo s : actualShortcuts) {
    459             assertNotNull("ID " + s.getId(), s.getIntent());
    460         }
    461         return actualShortcuts;
    462     }
    463 
    464     public static List<ShortcutInfo> assertAllNotHaveIntents(
    465             List<ShortcutInfo> actualShortcuts) {
    466         for (ShortcutInfo s : actualShortcuts) {
    467             assertNull("ID " + s.getId(), s.getIntent());
    468         }
    469         return actualShortcuts;
    470     }
    471 
    472     public static List<ShortcutInfo> assertAllHaveTitle(
    473             List<ShortcutInfo> actualShortcuts) {
    474         for (ShortcutInfo s : actualShortcuts) {
    475             assertNotNull("ID " + s.getId(), s.getShortLabel());
    476         }
    477         return actualShortcuts;
    478     }
    479 
    480     public static List<ShortcutInfo> assertAllNotHaveTitle(
    481             List<ShortcutInfo> actualShortcuts) {
    482         for (ShortcutInfo s : actualShortcuts) {
    483             assertNull("ID " + s.getId(), s.getShortLabel());
    484         }
    485         return actualShortcuts;
    486     }
    487 
    488     public static List<ShortcutInfo> assertAllKeyFieldsOnly(
    489             List<ShortcutInfo> actualShortcuts) {
    490         for (ShortcutInfo s : actualShortcuts) {
    491             assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly());
    492         }
    493         return actualShortcuts;
    494     }
    495 
    496     public static List<ShortcutInfo> assertAllNotKeyFieldsOnly(
    497             List<ShortcutInfo> actualShortcuts) {
    498         for (ShortcutInfo s : actualShortcuts) {
    499             assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly());
    500         }
    501         return actualShortcuts;
    502     }
    503 
    504     public static List<ShortcutInfo> assertAllDynamic(List<ShortcutInfo> actualShortcuts) {
    505         for (ShortcutInfo s : actualShortcuts) {
    506             assertTrue("ID " + s.getId(), s.isDynamic());
    507         }
    508         return actualShortcuts;
    509     }
    510 
    511     public static List<ShortcutInfo> assertAllPinned(List<ShortcutInfo> actualShortcuts) {
    512         for (ShortcutInfo s : actualShortcuts) {
    513             assertTrue("ID " + s.getId(), s.isPinned());
    514         }
    515         return actualShortcuts;
    516     }
    517 
    518     public static List<ShortcutInfo> assertAllDynamicOrPinned(
    519             List<ShortcutInfo> actualShortcuts) {
    520         for (ShortcutInfo s : actualShortcuts) {
    521             assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned());
    522         }
    523         return actualShortcuts;
    524     }
    525 
    526     public static List<ShortcutInfo> assertAllManifest(
    527             List<ShortcutInfo> actualShortcuts) {
    528         for (ShortcutInfo s : actualShortcuts) {
    529             assertTrue("ID " + s.getId(), s.isDeclaredInManifest());
    530         }
    531         return actualShortcuts;
    532     }
    533 
    534     public static List<ShortcutInfo> assertAllNotManifest(
    535             List<ShortcutInfo> actualShortcuts) {
    536         for (ShortcutInfo s : actualShortcuts) {
    537             assertFalse("ID " + s.getId(), s.isDeclaredInManifest());
    538         }
    539         return actualShortcuts;
    540     }
    541 
    542     public static List<ShortcutInfo> assertAllDisabled(
    543             List<ShortcutInfo> actualShortcuts) {
    544         for (ShortcutInfo s : actualShortcuts) {
    545             assertTrue("ID " + s.getId(), !s.isEnabled());
    546         }
    547         return actualShortcuts;
    548     }
    549 
    550     public static List<ShortcutInfo> assertAllEnabled(
    551             List<ShortcutInfo> actualShortcuts) {
    552         for (ShortcutInfo s : actualShortcuts) {
    553             assertTrue("ID " + s.getId(), s.isEnabled());
    554         }
    555         return actualShortcuts;
    556     }
    557 
    558     public static List<ShortcutInfo> assertAllImmutable(
    559             List<ShortcutInfo> actualShortcuts) {
    560         for (ShortcutInfo s : actualShortcuts) {
    561             assertTrue("ID " + s.getId(), s.isImmutable());
    562         }
    563         return actualShortcuts;
    564     }
    565 
    566     public static void assertDynamicOnly(ShortcutInfo si) {
    567         assertTrue(si.isDynamic());
    568         assertFalse(si.isPinned());
    569     }
    570 
    571     public static void assertPinnedOnly(ShortcutInfo si) {
    572         assertFalse(si.isDynamic());
    573         assertFalse(si.isDeclaredInManifest());
    574         assertTrue(si.isPinned());
    575     }
    576 
    577     public static void assertDynamicAndPinned(ShortcutInfo si) {
    578         assertTrue(si.isDynamic());
    579         assertTrue(si.isPinned());
    580     }
    581 
    582     public static void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
    583         assertEquals("width", expectedWidth, bitmap.getWidth());
    584         assertEquals("height", expectedHeight, bitmap.getHeight());
    585     }
    586 
    587     public static <T> void assertAllUnique(Collection<T> list) {
    588         final Set<Object> set = new LinkedHashSet<>();
    589         for (T item : list) {
    590             if (set.contains(item)) {
    591                 fail("Duplicate item found: " + item + " (in the list: " + list + ")");
    592             }
    593             set.add(item);
    594         }
    595     }
    596 
    597     public static ShortcutInfo findShortcut(List<ShortcutInfo> list, String id) {
    598         for (ShortcutInfo si : list) {
    599             if (si.getId().equals(id)) {
    600                 return si;
    601             }
    602         }
    603         fail("Shortcut " + id + " not found in the list");
    604         return null;
    605     }
    606 
    607     public static Bitmap pfdToBitmap(ParcelFileDescriptor pfd) {
    608         assertNotNull(pfd);
    609         try {
    610             try {
    611                 return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
    612             } finally {
    613                 pfd.close();
    614             }
    615         } catch (IOException e) {
    616             throw new RuntimeException(e);
    617         }
    618     }
    619 
    620     public static void assertBundleEmpty(BaseBundle b) {
    621         assertTrue(b == null || b.size() == 0);
    622     }
    623 
    624     public static void assertCallbackNotReceived(LauncherApps.Callback mock) {
    625         verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(),
    626                 any(UserHandle.class));
    627     }
    628 
    629     public static void assertCallbackReceived(LauncherApps.Callback mock,
    630             UserHandle user, String packageName, String... ids) {
    631         verify(mock).onShortcutsChanged(eq(packageName), checkShortcutIds(ids),
    632                 eq(user));
    633     }
    634 
    635     public static boolean checkAssertSuccess(Runnable r) {
    636         try {
    637             r.run();
    638             return true;
    639         } catch (AssertionError e) {
    640             return false;
    641         }
    642     }
    643 
    644     public static <T> T checkArgument(Predicate<T> checker, String description,
    645             List<T> matchedCaptor) {
    646         final Matcher<T> m = new BaseMatcher<T>() {
    647             @Override
    648             public boolean matches(Object item) {
    649                 if (item == null) {
    650                     return false;
    651                 }
    652                 final T value = (T) item;
    653                 if (!checker.test(value)) {
    654                     return false;
    655                 }
    656 
    657                 if (matchedCaptor != null) {
    658                     matchedCaptor.add(value);
    659                 }
    660                 return true;
    661             }
    662 
    663             @Override
    664             public void describeTo(Description d) {
    665                 d.appendText(description);
    666             }
    667         };
    668         return Mockito.argThat(m);
    669     }
    670 
    671     public static List<ShortcutInfo> checkShortcutIds(String... ids) {
    672         return checkArgument((List<ShortcutInfo> list) -> {
    673             final Set<String> actualSet = set(si -> si.getId(), list);
    674             return actualSet.equals(set(ids));
    675 
    676         }, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null);
    677     }
    678 
    679     public static ShortcutInfo parceled(ShortcutInfo si) {
    680         Parcel p = Parcel.obtain();
    681         p.writeParcelable(si, 0);
    682         p.setDataPosition(0);
    683         ShortcutInfo si2 = p.readParcelable(ShortcutManagerTestUtils.class.getClassLoader());
    684         p.recycle();
    685         return si2;
    686     }
    687 
    688     public static List<ShortcutInfo> cloneShortcutList(List<ShortcutInfo> list) {
    689         if (list == null) {
    690             return null;
    691         }
    692         final List<ShortcutInfo> ret = new ArrayList<>(list.size());
    693         for (ShortcutInfo si : list) {
    694             ret.add(parceled(si));
    695         }
    696 
    697         return ret;
    698     }
    699 
    700     private static final Comparator<ShortcutInfo> sRankComparator =
    701             (ShortcutInfo a, ShortcutInfo b) -> Integer.compare(a.getRank(), b.getRank());
    702 
    703     public static List<ShortcutInfo> sortedByRank(List<ShortcutInfo> shortcuts) {
    704         final ArrayList<ShortcutInfo> ret = new ArrayList<>(shortcuts);
    705         Collections.sort(ret, sRankComparator);
    706         return ret;
    707     }
    708 
    709     public static void waitUntil(String message, BooleanSupplier condition) {
    710         waitUntil(message, condition, STANDARD_TIMEOUT_SEC);
    711     }
    712 
    713     public static void waitUntil(String message, BooleanSupplier condition, int timeoutSeconds) {
    714         final long timeout = System.currentTimeMillis() + (timeoutSeconds * 1000L);
    715         while (System.currentTimeMillis() < timeout) {
    716             if (condition.getAsBoolean()) {
    717                 return;
    718             }
    719             try {
    720                 Thread.sleep(100);
    721             } catch (InterruptedException e) {
    722                 throw new RuntimeException(e);
    723             }
    724         }
    725         fail("Timed out for: " + message);
    726     }
    727 
    728     public static ShortcutListAsserter assertWith(List<ShortcutInfo> list) {
    729         return new ShortcutListAsserter(list);
    730     }
    731 
    732     /**
    733      * New style assertion that allows chained calls.
    734      */
    735     public static class ShortcutListAsserter {
    736         private final ShortcutListAsserter mOriginal;
    737         private final List<ShortcutInfo> mList;
    738 
    739         ShortcutListAsserter(List<ShortcutInfo> list) {
    740             this(null, list);
    741         }
    742 
    743         private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> list) {
    744             mOriginal = (original == null) ? this : original;
    745             mList = (list == null) ? new ArrayList<>(0) : new ArrayList<>(list);
    746         }
    747 
    748         public ShortcutListAsserter revertToOriginalList() {
    749             return mOriginal;
    750         }
    751 
    752         public ShortcutListAsserter selectDynamic() {
    753             return new ShortcutListAsserter(this,
    754                     filter(mList, ShortcutInfo::isDynamic));
    755         }
    756 
    757         public ShortcutListAsserter selectManifest() {
    758             return new ShortcutListAsserter(this,
    759                     filter(mList, ShortcutInfo::isDeclaredInManifest));
    760         }
    761 
    762         public ShortcutListAsserter selectPinned() {
    763             return new ShortcutListAsserter(this,
    764                     filter(mList, ShortcutInfo::isPinned));
    765         }
    766 
    767         public ShortcutListAsserter selectByActivity(ComponentName activity) {
    768             return new ShortcutListAsserter(this,
    769                     ShortcutManagerTestUtils.filterByActivity(mList, activity));
    770         }
    771 
    772         public ShortcutListAsserter selectByChangedSince(long time) {
    773             return new ShortcutListAsserter(this,
    774                     ShortcutManagerTestUtils.changedSince(mList, time));
    775         }
    776 
    777         public ShortcutListAsserter selectByIds(String... ids) {
    778             final Set<String> idSet = set(ids);
    779             final ArrayList<ShortcutInfo> selected = new ArrayList<>();
    780             for (ShortcutInfo si : mList) {
    781                 if (idSet.contains(si.getId())) {
    782                     selected.add(si);
    783                     idSet.remove(si.getId());
    784                 }
    785             }
    786             if (idSet.size() > 0) {
    787                 fail("Shortcuts not found for IDs=" + idSet);
    788             }
    789 
    790             return new ShortcutListAsserter(this, selected);
    791         }
    792 
    793         public ShortcutListAsserter toSortByRank() {
    794             return new ShortcutListAsserter(this,
    795                     ShortcutManagerTestUtils.sortedByRank(mList));
    796         }
    797 
    798         public ShortcutListAsserter call(Consumer<List<ShortcutInfo>> c) {
    799             c.accept(mList);
    800             return this;
    801         }
    802 
    803         public ShortcutListAsserter haveIds(String... expectedIds) {
    804             assertShortcutIds(mList, expectedIds);
    805             return this;
    806         }
    807 
    808         public ShortcutListAsserter haveIdsOrdered(String... expectedIds) {
    809             assertShortcutIdsOrdered(mList, expectedIds);
    810             return this;
    811         }
    812 
    813         private ShortcutListAsserter haveSequentialRanks() {
    814             for (int i = 0; i < mList.size(); i++) {
    815                 final ShortcutInfo si = mList.get(i);
    816                 assertEquals("Rank not sequential: id=" + si.getId(), i, si.getRank());
    817             }
    818             return this;
    819         }
    820 
    821         public ShortcutListAsserter haveRanksInOrder(String... expectedIds) {
    822             toSortByRank()
    823                     .haveSequentialRanks()
    824                     .haveIdsOrdered(expectedIds);
    825             return this;
    826         }
    827 
    828         public ShortcutListAsserter isEmpty() {
    829             assertEquals(0, mList.size());
    830             return this;
    831         }
    832 
    833         public ShortcutListAsserter areAllDynamic() {
    834             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isDynamic()));
    835             return this;
    836         }
    837 
    838         public ShortcutListAsserter areAllNotDynamic() {
    839             forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isDynamic()));
    840             return this;
    841         }
    842 
    843         public ShortcutListAsserter areAllPinned() {
    844             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isPinned()));
    845             return this;
    846         }
    847 
    848         public ShortcutListAsserter areAllNotPinned() {
    849             forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isPinned()));
    850             return this;
    851         }
    852 
    853         public ShortcutListAsserter areAllManifest() {
    854             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isDeclaredInManifest()));
    855             return this;
    856         }
    857 
    858         public ShortcutListAsserter areAllNotManifest() {
    859             forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isDeclaredInManifest()));
    860             return this;
    861         }
    862 
    863         public ShortcutListAsserter areAllImmutable() {
    864             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isImmutable()));
    865             return this;
    866         }
    867 
    868         public ShortcutListAsserter areAllMutable() {
    869             forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isImmutable()));
    870             return this;
    871         }
    872 
    873         public ShortcutListAsserter areAllEnabled() {
    874             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isEnabled()));
    875             return this;
    876         }
    877 
    878         public ShortcutListAsserter areAllDisabled() {
    879             forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isEnabled()));
    880             return this;
    881         }
    882 
    883         public ShortcutListAsserter areAllWithKeyFieldsOnly() {
    884             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.hasKeyFieldsOnly()));
    885             return this;
    886         }
    887 
    888         public ShortcutListAsserter areAllNotWithKeyFieldsOnly() {
    889             forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.hasKeyFieldsOnly()));
    890             return this;
    891         }
    892 
    893         public ShortcutListAsserter areAllWithActivity(ComponentName activity) {
    894             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.getActivity().equals(activity)));
    895             return this;
    896         }
    897 
    898         public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
    899             boolean found = false;
    900             for (int i = 0; i < mList.size(); i++) {
    901                 final ShortcutInfo si = mList.get(i);
    902                 found = true;
    903                 sa.accept(si);
    904             }
    905             assertTrue("No shortcuts found.", found);
    906             return this;
    907         }
    908 
    909         public ShortcutListAsserter forShortcut(Predicate<ShortcutInfo> p,
    910                 Consumer<ShortcutInfo> sa) {
    911             boolean found = false;
    912             for (int i = 0; i < mList.size(); i++) {
    913                 final ShortcutInfo si = mList.get(i);
    914                 if (p.test(si)) {
    915                     found = true;
    916                     try {
    917                         sa.accept(si);
    918                     } catch (Throwable e) {
    919                         throw new AssertionError("Assertion failed for shortcut " + si.getId(), e);
    920                     }
    921                 }
    922             }
    923             assertTrue("Shortcut with the given condition not found.", found);
    924             return this;
    925         }
    926 
    927         public ShortcutListAsserter forShortcutWithId(String id, Consumer<ShortcutInfo> sa) {
    928             forShortcut(si -> si.getId().equals(id), sa);
    929 
    930             return this;
    931         }
    932     }
    933 
    934     public static void assertBundlesEqual(BaseBundle b1, BaseBundle b2) {
    935         if (b1 == null && b2 == null) {
    936             return; // pass
    937         }
    938         assertNotNull("b1 is null but b2 is not", b1);
    939         assertNotNull("b2 is null but b1 is not", b2);
    940 
    941         // HashSet makes the error message readable.
    942         assertEquals(set(b1.keySet()), set(b2.keySet()));
    943 
    944         for (String key : b1.keySet()) {
    945             final Object v1 = b1.get(key);
    946             final Object v2 = b2.get(key);
    947             if (v1 == null) {
    948                 if (v2 == null) {
    949                     return;
    950                 }
    951             }
    952             if (v1.equals(v2)) {
    953                 return;
    954             }
    955 
    956             assertTrue("Only either value is null: key=" + key
    957                     + " b1=" + b1 + " b2=" + b2, v1 != null && v2 != null);
    958             assertEquals("Class mismatch: key=" + key, v1.getClass(), v2.getClass());
    959 
    960             if (v1 instanceof BaseBundle) {
    961                 assertBundlesEqual((BaseBundle) v1, (BaseBundle) v2);
    962 
    963             } else if (v1 instanceof boolean[]) {
    964                 assertTrue(Arrays.equals((boolean[]) v1, (boolean[]) v2));
    965 
    966             } else if (v1 instanceof int[]) {
    967                 MoreAsserts.assertEquals((int[]) v1, (int[]) v2);
    968 
    969             } else if (v1 instanceof double[]) {
    970                 MoreAsserts.assertEquals((double[]) v1, (double[]) v2);
    971 
    972             } else if (v1 instanceof String[]) {
    973                 MoreAsserts.assertEquals((String[]) v1, (String[]) v2);
    974 
    975             } else if (v1 instanceof Double) {
    976                 if (((Double) v1).isNaN()) {
    977                     assertTrue(((Double) v2).isNaN());
    978                 } else {
    979                     assertEquals(v1, v2);
    980                 }
    981 
    982             } else {
    983                 assertEquals(v1, v2);
    984             }
    985         }
    986     }
    987 
    988     public static void waitOnMainThread() throws InterruptedException {
    989         final CountDownLatch latch = new CountDownLatch(1);
    990 
    991         new Handler(Looper.getMainLooper()).post(() -> latch.countDown());
    992 
    993         latch.await();
    994     }
    995 
    996     public static class LauncherCallbackAsserter {
    997         private final LauncherApps.Callback mCallback = mock(LauncherApps.Callback.class);
    998 
    999         private Callback getMockCallback() {
   1000             return mCallback;
   1001         }
   1002 
   1003         public LauncherCallbackAsserter assertNoCallbackCalled() {
   1004             verify(mCallback, times(0)).onShortcutsChanged(
   1005                     anyString(),
   1006                     any(List.class),
   1007                     any(UserHandle.class));
   1008             return this;
   1009         }
   1010 
   1011         public LauncherCallbackAsserter assertNoCallbackCalledForPackage(
   1012                 String publisherPackageName) {
   1013             verify(mCallback, times(0)).onShortcutsChanged(
   1014                     eq(publisherPackageName),
   1015                     any(List.class),
   1016                     any(UserHandle.class));
   1017             return this;
   1018         }
   1019 
   1020         public LauncherCallbackAsserter assertNoCallbackCalledForPackageAndUser(
   1021                 String publisherPackageName, UserHandle publisherUserHandle) {
   1022             verify(mCallback, times(0)).onShortcutsChanged(
   1023                     eq(publisherPackageName),
   1024                     any(List.class),
   1025                     eq(publisherUserHandle));
   1026             return this;
   1027         }
   1028 
   1029         public ShortcutListAsserter assertCallbackCalledForPackageAndUser(
   1030                 String publisherPackageName, UserHandle publisherUserHandle) {
   1031             final ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
   1032             verify(mCallback, times(1)).onShortcutsChanged(
   1033                     eq(publisherPackageName),
   1034                     shortcuts.capture(),
   1035                     eq(publisherUserHandle));
   1036             return new ShortcutListAsserter(shortcuts.getValue());
   1037         }
   1038     }
   1039 
   1040     public static LauncherCallbackAsserter assertForLauncherCallback(
   1041             LauncherApps launcherApps, Runnable body) throws InterruptedException {
   1042         final LauncherCallbackAsserter asserter = new LauncherCallbackAsserter();
   1043         launcherApps.registerCallback(asserter.getMockCallback(),
   1044                 new Handler(Looper.getMainLooper()));
   1045 
   1046         body.run();
   1047 
   1048         waitOnMainThread();
   1049 
   1050         // TODO unregister doesn't work well during unit tests.  Figure out and fix it.
   1051         // launcherApps.unregisterCallback(asserter.getMockCallback());
   1052 
   1053         return asserter;
   1054     }
   1055 
   1056     public static void retryUntil(BooleanSupplier checker, String message) {
   1057         retryUntil(checker, message, 30);
   1058     }
   1059 
   1060     public static void retryUntil(BooleanSupplier checker, String message, long timeoutSeconds) {
   1061         final long timeOut = System.currentTimeMillis() + timeoutSeconds * 1000;
   1062         while (!checker.getAsBoolean()) {
   1063             if (System.currentTimeMillis() > timeOut) {
   1064                 break;
   1065             }
   1066             try {
   1067                 Thread.sleep(200);
   1068             } catch (InterruptedException ignore) {
   1069             }
   1070         }
   1071         assertTrue(message, checker.getAsBoolean());
   1072     }
   1073 }
   1074