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