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