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