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