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