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