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