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