blob: 71878fdb10c478ef99dbfa30c3768bf58bc5a22f [file] [log] [blame]
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -07001/*
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 */
16package com.android.server.pm.shortcutmanagertest;
17
18import static junit.framework.Assert.assertEquals;
19import static junit.framework.Assert.assertFalse;
20import static junit.framework.Assert.assertNotNull;
21import static junit.framework.Assert.assertNull;
22import static junit.framework.Assert.assertTrue;
23import static junit.framework.Assert.fail;
24
25import static org.mockito.Matchers.any;
26import static org.mockito.Matchers.anyList;
27import static org.mockito.Matchers.anyString;
28import static org.mockito.Matchers.eq;
29import static org.mockito.Mockito.reset;
30import static org.mockito.Mockito.times;
31import static org.mockito.Mockito.verify;
32
33import android.app.Instrumentation;
Makoto Onuki7001a612016-05-27 13:24:28 -070034import android.content.ComponentName;
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -070035import android.content.Context;
36import android.content.pm.LauncherApps;
37import android.content.pm.ShortcutInfo;
38import android.graphics.Bitmap;
39import android.graphics.BitmapFactory;
40import android.os.BaseBundle;
41import android.os.Bundle;
Makoto Onuki7001a612016-05-27 13:24:28 -070042import android.os.Parcel;
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -070043import android.os.ParcelFileDescriptor;
44import android.os.UserHandle;
45import android.test.MoreAsserts;
46import android.util.Log;
47
48import junit.framework.Assert;
49
50import org.hamcrest.BaseMatcher;
51import org.hamcrest.Description;
52import org.hamcrest.Matcher;
53import org.mockito.Mockito;
54
55import java.io.BufferedReader;
56import java.io.FileReader;
57import java.io.IOException;
58import java.util.ArrayList;
59import java.util.Arrays;
60import java.util.Collection;
61import java.util.HashSet;
62import java.util.List;
63import java.util.Set;
64import java.util.function.BooleanSupplier;
65import java.util.function.Function;
66import java.util.function.Predicate;
67
Makoto Onuki51ab2b32016-06-02 11:03:51 -070068/**
69 * Common utility methods for ShortcutManager tests. This is used by both CTS and the unit tests.
70 * Because it's used by CTS too, it can only access the public APIs.
71 */
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -070072public class ShortcutManagerTestUtils {
73 private static final String TAG = "ShortcutManagerUtils";
74
75 private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true
76
77 private static final int STANDARD_TIMEOUT_SEC = 5;
78
79 private ShortcutManagerTestUtils() {
80 }
81
82 private static List<String> readAll(ParcelFileDescriptor pfd) {
83 try {
84 try {
85 final ArrayList<String> ret = new ArrayList<>();
86 try (BufferedReader r = new BufferedReader(
87 new FileReader(pfd.getFileDescriptor()))) {
88 String line;
89 while ((line = r.readLine()) != null) {
90 ret.add(line);
91 }
92 r.readLine();
93 }
94 return ret;
95 } finally {
96 pfd.close();
97 }
98 } catch (IOException e) {
99 throw new RuntimeException(e);
100 }
101 }
102
103 private static String concatResult(List<String> result) {
104 final StringBuilder sb = new StringBuilder();
105 for (String s : result) {
106 sb.append(s);
107 sb.append("\n");
108 }
109 return sb.toString();
110 }
111
112 private static List<String> runCommand(Instrumentation instrumentation, String command) {
113 return runCommand(instrumentation, command, null);
114 }
115 private static List<String> runCommand(Instrumentation instrumentation, String command,
116 Predicate<List<String>> resultAsserter) {
117 Log.d(TAG, "Running command: " + command);
118 final List<String> result;
119 try {
120 result = readAll(
121 instrumentation.getUiAutomation().executeShellCommand(command));
122 } catch (Exception e) {
123 throw new RuntimeException(e);
124 }
125 if (resultAsserter != null && !resultAsserter.test(result)) {
126 fail("Command '" + command + "' failed, output was:\n" + concatResult(result));
127 }
128 return result;
129 }
130
131 private static void runCommandForNoOutput(Instrumentation instrumentation, String command) {
132 runCommand(instrumentation, command, result -> result.size() == 0);
133 }
134
135 private static List<String> runShortcutCommand(Instrumentation instrumentation, String command,
136 Predicate<List<String>> resultAsserter) {
137 return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter);
138 }
139
140 public static List<String> runShortcutCommandForSuccess(Instrumentation instrumentation,
141 String command) {
142 return runShortcutCommand(instrumentation, command, result -> result.contains("Success"));
143 }
144
145 public static String getDefaultLauncher(Instrumentation instrumentation) {
146 final String PREFIX = "Launcher: ComponentInfo{";
147 final String POSTFIX = "}";
148 final List<String> result = runShortcutCommandForSuccess(
149 instrumentation, "get-default-launcher");
150 for (String s : result) {
151 if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
152 return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
153 }
154 }
155 fail("Default launcher not found");
156 return null;
157 }
158
159 public static void setDefaultLauncher(Instrumentation instrumentation, String component) {
160 runCommandForNoOutput(instrumentation, "cmd package set-home-activity " + component);
161 }
162
163 public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
164 setDefaultLauncher(instrumentation, packageContext.getPackageName()
165 + "/android.content.pm.cts.shortcutmanager.packages.Launcher");
166 }
167
168 public static void overrideConfig(Instrumentation instrumentation, String config) {
169 runShortcutCommandForSuccess(instrumentation, "override-config " + config);
170 }
171
172 public static void resetConfig(Instrumentation instrumentation) {
173 runShortcutCommandForSuccess(instrumentation, "reset-config");
174 }
175
176 public static void resetThrottling(Instrumentation instrumentation) {
177 runShortcutCommandForSuccess(instrumentation, "reset-throttling");
178 }
179
180 public static void resetAllThrottling(Instrumentation instrumentation) {
181 runShortcutCommandForSuccess(instrumentation, "reset-all-throttling");
182 }
183
184 public static void clearShortcuts(Instrumentation instrumentation, int userId,
185 String packageName) {
186 runShortcutCommandForSuccess(instrumentation, "clear-shortcuts "
187 + " --user " + userId + " " + packageName);
188 }
189
190 public static void dumpsysShortcut(Instrumentation instrumentation) {
191 if (!ENABLE_DUMPSYS) {
192 return;
193 }
194 for (String s : runCommand(instrumentation, "dumpsys shortcut")) {
195 Log.e(TAG, s);
196 }
197 }
198
199 public static Bundle makeBundle(Object... keysAndValues) {
200 assertTrue((keysAndValues.length % 2) == 0);
201
202 if (keysAndValues.length == 0) {
203 return null;
204 }
205 final Bundle ret = new Bundle();
206
207 for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
208 final String key = keysAndValues[i].toString();
209 final Object value = keysAndValues[i + 1];
210
211 if (value == null) {
212 ret.putString(key, null);
213 } else if (value instanceof Integer) {
214 ret.putInt(key, (Integer) value);
215 } else if (value instanceof String) {
216 ret.putString(key, (String) value);
217 } else if (value instanceof Bundle) {
218 ret.putBundle(key, (Bundle) value);
219 } else {
220 fail("Type not supported yet: " + value.getClass().getName());
221 }
222 }
223 return ret;
224 }
225
226 public static <T> List<T> list(T... array) {
227 return Arrays.asList(array);
228 }
229
230 public static <T> Set<T> hashSet(Set<T> in) {
231 return new HashSet<T>(in);
232 }
233
234 public static <T> Set<T> set(T... values) {
235 return set(v -> v, values);
236 }
237
238 public static <T, V> Set<T> set(Function<V, T> converter, V... values) {
239 return set(converter, Arrays.asList(values));
240 }
241
242 public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) {
243 final HashSet<T> ret = new HashSet<>();
244 for (V v : values) {
245 ret.add(converter.apply(v));
246 }
247 return ret;
248 }
249
250 public static void resetAll(Collection<?> mocks) {
251 for (Object o : mocks) {
252 reset(o);
253 }
254 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700255
256 public static <T> List<T> assertEmpty(List<T> list) {
257 assertEquals(0, list.size());
258 return list;
259 }
260
Makoto Onuki7001a612016-05-27 13:24:28 -0700261 public static List<ShortcutInfo> filterByActivity(List<ShortcutInfo> list,
262 ComponentName activity) {
263 final ArrayList<ShortcutInfo> ret = new ArrayList<>();
264 for (ShortcutInfo si : list) {
265 if (si.getActivity().equals(activity) && (si.isManifestShortcut() || si.isDynamic())) {
266 ret.add(si);
267 }
268 }
269 return ret;
270 }
271
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700272 public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
273 String expectedExceptionMessageRegex, Runnable r) {
274 assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
275 }
276
Makoto Onuki22fcc682016-05-17 14:52:19 -0700277 public static void assertCannotUpdateImmutable(Runnable r) {
278 assertExpectException(
279 IllegalArgumentException.class, "may not be manipulated via APIs", r);
280 }
281
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700282 public static void assertDynamicShortcutCountExceeded(Runnable r) {
283 assertExpectException(IllegalArgumentException.class,
284 "Max number of dynamic shortcuts exceeded", r);
285 }
286
287 public static void assertExpectException(String message,
288 Class<? extends Throwable> expectedExceptionType,
289 String expectedExceptionMessageRegex, Runnable r) {
290 try {
291 r.run();
292 Assert.fail("Expected exception type " + expectedExceptionType.getName()
293 + " was not thrown (message=" + message + ")");
294 } catch (Throwable e) {
295 Assert.assertTrue(
296 "Expected exception type was " + expectedExceptionType.getName()
297 + " but caught " + e + " (message=" + message + ")",
298 expectedExceptionType.isAssignableFrom(e.getClass()));
299 if (expectedExceptionMessageRegex != null) {
300 MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
301 }
302 }
303 }
304
305 public static List<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts,
306 String... expectedIds) {
307 final HashSet<String> expected = new HashSet<>(list(expectedIds));
308 final HashSet<String> actual = new HashSet<>();
309 for (ShortcutInfo s : actualShortcuts) {
310 actual.add(s.getId());
311 }
312
313 // Compare the sets.
314 assertEquals(expected, actual);
315 return actualShortcuts;
316 }
317
318 public static List<ShortcutInfo> assertAllHaveIntents(
319 List<ShortcutInfo> actualShortcuts) {
320 for (ShortcutInfo s : actualShortcuts) {
321 assertNotNull("ID " + s.getId(), s.getIntent());
322 }
323 return actualShortcuts;
324 }
325
326 public static List<ShortcutInfo> assertAllNotHaveIntents(
327 List<ShortcutInfo> actualShortcuts) {
328 for (ShortcutInfo s : actualShortcuts) {
329 assertNull("ID " + s.getId(), s.getIntent());
330 }
331 return actualShortcuts;
332 }
333
334 public static List<ShortcutInfo> assertAllHaveTitle(
335 List<ShortcutInfo> actualShortcuts) {
336 for (ShortcutInfo s : actualShortcuts) {
Makoto Onukieddbfec2016-05-31 17:04:34 -0700337 assertNotNull("ID " + s.getId(), s.getShortLabel());
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700338 }
339 return actualShortcuts;
340 }
341
342 public static List<ShortcutInfo> assertAllNotHaveTitle(
343 List<ShortcutInfo> actualShortcuts) {
344 for (ShortcutInfo s : actualShortcuts) {
Makoto Onukieddbfec2016-05-31 17:04:34 -0700345 assertNull("ID " + s.getId(), s.getShortLabel());
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700346 }
347 return actualShortcuts;
348 }
349
350 public static List<ShortcutInfo> assertAllHaveIconResId(
351 List<ShortcutInfo> actualShortcuts) {
352 for (ShortcutInfo s : actualShortcuts) {
353 assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource());
354 assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile());
355 }
356 return actualShortcuts;
357 }
358
359 public static List<ShortcutInfo> assertAllHaveIconFile(
360 List<ShortcutInfo> actualShortcuts) {
361 for (ShortcutInfo s : actualShortcuts) {
362 assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource());
363 assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile());
364 }
365 return actualShortcuts;
366 }
367
368 public static List<ShortcutInfo> assertAllHaveIcon(
369 List<ShortcutInfo> actualShortcuts) {
370 for (ShortcutInfo s : actualShortcuts) {
371 assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource());
372 }
373 return actualShortcuts;
374 }
375
376 public static List<ShortcutInfo> assertAllKeyFieldsOnly(
377 List<ShortcutInfo> actualShortcuts) {
378 for (ShortcutInfo s : actualShortcuts) {
379 assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly());
380 }
381 return actualShortcuts;
382 }
383
384 public static List<ShortcutInfo> assertAllNotKeyFieldsOnly(
385 List<ShortcutInfo> actualShortcuts) {
386 for (ShortcutInfo s : actualShortcuts) {
387 assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly());
388 }
389 return actualShortcuts;
390 }
391
392 public static List<ShortcutInfo> assertAllDynamic(List<ShortcutInfo> actualShortcuts) {
393 for (ShortcutInfo s : actualShortcuts) {
394 assertTrue("ID " + s.getId(), s.isDynamic());
395 }
396 return actualShortcuts;
397 }
398
399 public static List<ShortcutInfo> assertAllPinned(List<ShortcutInfo> actualShortcuts) {
400 for (ShortcutInfo s : actualShortcuts) {
401 assertTrue("ID " + s.getId(), s.isPinned());
402 }
403 return actualShortcuts;
404 }
405
406 public static List<ShortcutInfo> assertAllDynamicOrPinned(
407 List<ShortcutInfo> actualShortcuts) {
408 for (ShortcutInfo s : actualShortcuts) {
409 assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned());
410 }
411 return actualShortcuts;
412 }
413
Makoto Onuki22fcc682016-05-17 14:52:19 -0700414 public static List<ShortcutInfo> assertAllManifest(
415 List<ShortcutInfo> actualShortcuts) {
416 for (ShortcutInfo s : actualShortcuts) {
417 assertTrue("ID " + s.getId(), s.isManifestShortcut());
418 }
419 return actualShortcuts;
420 }
421
422 public static List<ShortcutInfo> assertAllNotManifest(
423 List<ShortcutInfo> actualShortcuts) {
424 for (ShortcutInfo s : actualShortcuts) {
425 assertFalse("ID " + s.getId(), s.isManifestShortcut());
426 }
427 return actualShortcuts;
428 }
429
430 public static List<ShortcutInfo> assertAllDisabled(
431 List<ShortcutInfo> actualShortcuts) {
432 for (ShortcutInfo s : actualShortcuts) {
433 assertTrue("ID " + s.getId(), !s.isEnabled());
434 }
435 return actualShortcuts;
436 }
437
438 public static List<ShortcutInfo> assertAllEnabled(
439 List<ShortcutInfo> actualShortcuts) {
440 for (ShortcutInfo s : actualShortcuts) {
441 assertTrue("ID " + s.getId(), s.isEnabled());
442 }
443 return actualShortcuts;
444 }
445
446 public static List<ShortcutInfo> assertAllImmutable(
447 List<ShortcutInfo> actualShortcuts) {
448 for (ShortcutInfo s : actualShortcuts) {
449 assertTrue("ID " + s.getId(), s.isImmutable());
450 }
451 return actualShortcuts;
452 }
453
Makoto Onuki20c95f82016-05-11 16:51:01 -0700454 public static List<ShortcutInfo> assertAllStringsResolved(
455 List<ShortcutInfo> actualShortcuts) {
456 for (ShortcutInfo s : actualShortcuts) {
457 assertTrue("ID " + s.getId(), s.hasStringResourcesResolved());
458 }
459 return actualShortcuts;
460 }
461
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700462 public static void assertDynamicOnly(ShortcutInfo si) {
463 assertTrue(si.isDynamic());
464 assertFalse(si.isPinned());
465 }
466
467 public static void assertPinnedOnly(ShortcutInfo si) {
468 assertFalse(si.isDynamic());
Makoto Onuki22fcc682016-05-17 14:52:19 -0700469 assertFalse(si.isManifestShortcut());
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700470 assertTrue(si.isPinned());
471 }
472
473 public static void assertDynamicAndPinned(ShortcutInfo si) {
474 assertTrue(si.isDynamic());
475 assertTrue(si.isPinned());
476 }
477
478 public static void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
479 assertEquals("width", expectedWidth, bitmap.getWidth());
480 assertEquals("height", expectedHeight, bitmap.getHeight());
481 }
482
483 public static <T> void assertAllUnique(Collection<T> list) {
484 final Set<Object> set = new HashSet<>();
485 for (T item : list) {
486 if (set.contains(item)) {
487 fail("Duplicate item found: " + item + " (in the list: " + list + ")");
488 }
489 set.add(item);
490 }
491 }
492
Makoto Onuki39686e82016-04-13 18:03:00 -0700493 public static ShortcutInfo findShortcut(List<ShortcutInfo> list, String id) {
494 for (ShortcutInfo si : list) {
495 if (si.getId().equals(id)) {
496 return si;
497 }
498 }
499 fail("Shortcut " + id + " not found in the list");
500 return null;
501 }
502
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700503 public static Bitmap pfdToBitmap(ParcelFileDescriptor pfd) {
504 assertNotNull(pfd);
505 try {
506 try {
507 return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
508 } finally {
509 pfd.close();
510 }
511 } catch (IOException e) {
512 throw new RuntimeException(e);
513 }
514 }
515
516 public static void assertBundleEmpty(BaseBundle b) {
517 assertTrue(b == null || b.size() == 0);
518 }
519
520 public static void assertCallbackNotReceived(LauncherApps.Callback mock) {
521 verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(),
522 any(UserHandle.class));
523 }
524
525 public static void assertCallbackReceived(LauncherApps.Callback mock,
526 UserHandle user, String packageName, String... ids) {
527 verify(mock).onShortcutsChanged(eq(packageName), checkShortcutIds(ids),
528 eq(user));
529 }
530
531 public static boolean checkAssertSuccess(Runnable r) {
532 try {
533 r.run();
534 return true;
535 } catch (AssertionError e) {
536 return false;
537 }
538 }
539
540 public static <T> T checkArgument(Predicate<T> checker, String description,
541 List<T> matchedCaptor) {
542 final Matcher<T> m = new BaseMatcher<T>() {
543 @Override
544 public boolean matches(Object item) {
545 if (item == null) {
546 return false;
547 }
548 final T value = (T) item;
549 if (!checker.test(value)) {
550 return false;
551 }
552
553 if (matchedCaptor != null) {
554 matchedCaptor.add(value);
555 }
556 return true;
557 }
558
559 @Override
560 public void describeTo(Description d) {
561 d.appendText(description);
562 }
563 };
564 return Mockito.argThat(m);
565 }
566
567 public static List<ShortcutInfo> checkShortcutIds(String... ids) {
568 return checkArgument((List<ShortcutInfo> list) -> {
569 final Set<String> actualSet = set(si -> si.getId(), list);
570 return actualSet.equals(set(ids));
571
572 }, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null);
573 }
574
Makoto Onuki7001a612016-05-27 13:24:28 -0700575 public static ShortcutInfo parceled(ShortcutInfo si) {
576 Parcel p = Parcel.obtain();
577 p.writeParcelable(si, 0);
578 p.setDataPosition(0);
579 ShortcutInfo si2 = p.readParcelable(ShortcutManagerTestUtils.class.getClassLoader());
580 p.recycle();
581 return si2;
582 }
583
584 public static List<ShortcutInfo> cloneShortcutList(List<ShortcutInfo> list) {
585 if (list == null) {
586 return null;
587 }
588 final List<ShortcutInfo> ret = new ArrayList<>(list.size());
589 for (ShortcutInfo si : list) {
590 ret.add(parceled(si));
591 }
592
593 return ret;
594 }
595
Makoto Onuki5ba0d3e2016-04-11 14:03:46 -0700596 public static void waitUntil(String message, BooleanSupplier condition) {
597 waitUntil(message, condition, STANDARD_TIMEOUT_SEC);
598 }
599
600 public static void waitUntil(String message, BooleanSupplier condition, int timeoutSeconds) {
601 final long timeout = System.currentTimeMillis() + (timeoutSeconds * 1000L);
602 while (System.currentTimeMillis() < timeout) {
603 if (condition.getAsBoolean()) {
604 return;
605 }
606 try {
607 Thread.sleep(100);
608 } catch (InterruptedException e) {
609 throw new RuntimeException(e);
610 }
611 }
612 fail("Timed out for: " + message);
613 }
614}