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 android.content.pm.cts.shortcutmanager; 17 18 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.*; 19 20 import android.app.Activity; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.ContextWrapper; 24 import android.content.Intent; 25 import android.content.pm.LauncherApps; 26 import android.content.pm.LauncherApps.ShortcutQuery; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ShortcutInfo; 29 import android.content.pm.ShortcutManager; 30 import android.content.res.Resources; 31 import android.graphics.drawable.AdaptiveIconDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.graphics.drawable.Icon; 34 import android.os.Bundle; 35 import android.os.PersistableBundle; 36 import android.os.StrictMode; 37 import android.os.StrictMode.ThreadPolicy; 38 import android.os.UserHandle; 39 import androidx.annotation.NonNull; 40 import android.test.InstrumentationTestCase; 41 import android.text.TextUtils; 42 43 import java.util.ArrayList; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.concurrent.atomic.AtomicReference; 48 49 public abstract class ShortcutManagerCtsTestsBase extends InstrumentationTestCase { 50 protected static final String TAG = "ShortcutCTS"; 51 52 private static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true 53 54 /** 55 * Whether to enable strict mode or not. 56 * 57 * TODO Enable it after fixing b/68051728. Somehow violations would happen on the dashboard 58 * only and can't reproduce it locally. 59 */ 60 private static final boolean ENABLE_STRICT_MODE = false; 61 62 private static class SpoofingContext extends ContextWrapper { 63 private final String mPackageName; 64 SpoofingContext(Context base, String packageName)65 public SpoofingContext(Context base, String packageName) { 66 super(base); 67 mPackageName = packageName; 68 } 69 70 @Override getPackageName()71 public String getPackageName() { 72 return mPackageName; 73 } 74 } 75 76 private Context mCurrentCallerPackage; 77 private int mUserId; 78 private UserHandle mUserHandle; 79 80 private String mOriginalLauncher; 81 82 protected Context mPackageContext1; 83 protected Context mPackageContext2; 84 protected Context mPackageContext3; 85 protected Context mPackageContext4; 86 87 protected Context mLauncherContext1; 88 protected Context mLauncherContext2; 89 protected Context mLauncherContext3; 90 protected Context mLauncherContext4; 91 92 private LauncherApps mLauncherApps1; 93 private LauncherApps mLauncherApps2; 94 private LauncherApps mLauncherApps3; 95 private LauncherApps mLauncherApps4; 96 97 private Map<Context, ShortcutManager> mManagers = new HashMap<>(); 98 private Map<Context, LauncherApps> mLauncherAppses = new HashMap<>(); 99 100 private ShortcutManager mCurrentManager; 101 private LauncherApps mCurrentLauncherApps; 102 103 private static final String[] ACTIVITIES_WITH_MANIFEST_SHORTCUTS = { 104 "Launcher_manifest_1", 105 "Launcher_manifest_2", 106 "Launcher_manifest_3", 107 "Launcher_manifest_4a", 108 "Launcher_manifest_4b", 109 "Launcher_manifest_error_1", 110 "Launcher_manifest_error_2", 111 "Launcher_manifest_error_3" 112 }; 113 114 private ComponentName mTargetActivityOverride; 115 116 private static class ShortcutActivity extends Activity { 117 } 118 119 @Override setUp()120 protected void setUp() throws Exception { 121 super.setUp(); 122 123 mUserId = getTestContext().getUserId(); 124 mUserHandle = android.os.Process.myUserHandle(); 125 126 resetConfig(getInstrumentation()); 127 final String config = getOverrideConfig(); 128 if (config != null) { 129 overrideConfig(getInstrumentation(), config); 130 } 131 mOriginalLauncher = getDefaultLauncher(getInstrumentation()); 132 133 mPackageContext1 = new SpoofingContext(getTestContext(), 134 "android.content.pm.cts.shortcutmanager.packages.package1"); 135 mPackageContext2 = new SpoofingContext(getTestContext(), 136 "android.content.pm.cts.shortcutmanager.packages.package2"); 137 mPackageContext3 = new SpoofingContext(getTestContext(), 138 "android.content.pm.cts.shortcutmanager.packages.package3"); 139 mPackageContext4 = new SpoofingContext(getTestContext(), 140 "android.content.pm.cts.shortcutmanager.packages.package4"); 141 mLauncherContext1 = new SpoofingContext(getTestContext(), 142 "android.content.pm.cts.shortcutmanager.packages.launcher1"); 143 mLauncherContext2 = new SpoofingContext(getTestContext(), 144 "android.content.pm.cts.shortcutmanager.packages.launcher2"); 145 mLauncherContext3 = new SpoofingContext(getTestContext(), 146 "android.content.pm.cts.shortcutmanager.packages.launcher3"); 147 mLauncherContext4 = new SpoofingContext(getTestContext(), 148 "android.content.pm.cts.shortcutmanager.packages.launcher4"); 149 150 mLauncherApps1 = new LauncherApps(mLauncherContext1); 151 mLauncherApps2 = new LauncherApps(mLauncherContext2); 152 mLauncherApps3 = new LauncherApps(mLauncherContext3); 153 mLauncherApps4 = new LauncherApps(mLauncherContext4); 154 155 clearShortcuts(getInstrumentation(), mUserId, mPackageContext1.getPackageName()); 156 clearShortcuts(getInstrumentation(), mUserId, mPackageContext2.getPackageName()); 157 clearShortcuts(getInstrumentation(), mUserId, mPackageContext3.getPackageName()); 158 clearShortcuts(getInstrumentation(), mUserId, mPackageContext4.getPackageName()); 159 160 setCurrentCaller(mPackageContext1); 161 162 // Make sure shortcuts are removed. 163 withCallers(getAllPublishers(), () -> { 164 // Clear all shortcuts. 165 clearShortcuts(getInstrumentation(), mUserId, getCurrentCallingPackage()); 166 167 disableActivitiesWithManifestShortucts(); 168 169 assertEquals("for " + getCurrentCallingPackage(), 170 0, getManager().getDynamicShortcuts().size()); 171 assertEquals("for " + getCurrentCallingPackage(), 172 0, getManager().getPinnedShortcuts().size()); 173 assertEquals("for " + getCurrentCallingPackage(), 174 0, getManager().getManifestShortcuts().size()); 175 }); 176 } 177 178 @Override tearDown()179 protected void tearDown() throws Exception { 180 if (DUMPSYS_IN_TEARDOWN) { 181 dumpsysShortcut(getInstrumentation()); 182 } 183 184 withCallers(getAllPublishers(), () -> disableActivitiesWithManifestShortucts()); 185 186 resetConfig(getInstrumentation()); 187 188 if (!TextUtils.isEmpty(mOriginalLauncher)) { 189 setDefaultLauncher(getInstrumentation(), mOriginalLauncher); 190 } 191 192 super.tearDown(); 193 } 194 getTestContext()195 protected Context getTestContext() { 196 return getInstrumentation().getContext(); 197 } 198 getUserHandle()199 protected UserHandle getUserHandle() { 200 return mUserHandle; 201 } 202 getAllPublishers()203 protected List<Context> getAllPublishers() { 204 // 4 has a different signature, so we can't call for it. 205 return list(mPackageContext1, mPackageContext2, mPackageContext3); 206 } 207 getAllLaunchers()208 protected List<Context> getAllLaunchers() { 209 // 4 has a different signature, so we can't call for it. 210 return list(mLauncherContext1, mLauncherContext2, mLauncherContext3); 211 } 212 getAllCallers()213 protected List<Context> getAllCallers() { 214 return list( 215 mPackageContext1, mPackageContext2, mPackageContext3, mPackageContext4, 216 mLauncherContext1, mLauncherContext2, mLauncherContext3, mLauncherContext4); 217 } 218 getActivity(String className)219 protected ComponentName getActivity(String className) { 220 return new ComponentName(getCurrentCallingPackage(), 221 "android.content.pm.cts.shortcutmanager.packages." + className); 222 223 } 224 disableActivitiesWithManifestShortucts()225 protected void disableActivitiesWithManifestShortucts() { 226 if (getManager().getManifestShortcuts().size() > 0) { 227 // Disable DISABLED_ACTIVITIES 228 for (String className : ACTIVITIES_WITH_MANIFEST_SHORTCUTS) { 229 enableManifestActivity(className, false); 230 } 231 } 232 } 233 enableManifestActivity(String className, boolean enabled)234 protected void enableManifestActivity(String className, boolean enabled) { 235 getTestContext().getPackageManager().setComponentEnabledSetting(getActivity(className), 236 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 237 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 238 PackageManager.DONT_KILL_APP); 239 } 240 setTargetActivityOverride(String className)241 protected void setTargetActivityOverride(String className) { 242 mTargetActivityOverride = getActivity(className); 243 } 244 245 withCallers(List<Context> callers, Runnable r)246 protected void withCallers(List<Context> callers, Runnable r) { 247 for (Context c : callers) { 248 runWithCaller(c, r); 249 } 250 } 251 getOverrideConfig()252 protected String getOverrideConfig() { 253 return null; 254 } 255 setCurrentCaller(Context callerContext)256 protected void setCurrentCaller(Context callerContext) { 257 mCurrentCallerPackage = callerContext; 258 259 if (!mManagers.containsKey(mCurrentCallerPackage)) { 260 mManagers.put(mCurrentCallerPackage, new ShortcutManager(mCurrentCallerPackage)); 261 } 262 mCurrentManager = mManagers.get(mCurrentCallerPackage); 263 264 if (!mLauncherAppses.containsKey(mCurrentCallerPackage)) { 265 mLauncherAppses.put(mCurrentCallerPackage, new LauncherApps(mCurrentCallerPackage)); 266 } 267 mCurrentLauncherApps = mLauncherAppses.get(mCurrentCallerPackage); 268 269 mTargetActivityOverride = null; 270 } 271 getCurrentCallerContext()272 protected Context getCurrentCallerContext() { 273 return mCurrentCallerPackage; 274 } 275 getCurrentCallingPackage()276 protected String getCurrentCallingPackage() { 277 return getCurrentCallerContext().getPackageName(); 278 } 279 getManager()280 protected ShortcutManager getManager() { 281 return mCurrentManager; 282 } 283 getLauncherApps()284 protected LauncherApps getLauncherApps() { 285 return mCurrentLauncherApps; 286 } 287 runWithStrictMode(Runnable r)288 protected void runWithStrictMode(Runnable r) { 289 final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 290 try { 291 if (ENABLE_STRICT_MODE) { 292 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 293 .detectAll() 294 .penaltyDeath() 295 .build()); 296 } 297 r.run(); 298 } finally { 299 StrictMode.setThreadPolicy(oldPolicy); 300 } 301 } 302 runWithNoStrictMode(Runnable r)303 protected void runWithNoStrictMode(Runnable r) { 304 final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 305 try { 306 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 307 .permitAll() 308 .build()); 309 r.run(); 310 } finally { 311 StrictMode.setThreadPolicy(oldPolicy); 312 } 313 } 314 runWithCaller(Context callerContext, Runnable r)315 protected void runWithCaller(Context callerContext, Runnable r) { 316 final Context prev = mCurrentCallerPackage; 317 318 setCurrentCaller(callerContext); 319 320 r.run(); 321 322 setCurrentCaller(prev); 323 } 324 runWithCallerWithStrictMode(Context callerContext, Runnable r)325 protected void runWithCallerWithStrictMode(Context callerContext, Runnable r) { 326 runWithCaller(callerContext, () -> runWithStrictMode(r)); 327 } 328 runWithCallerWithNoStrictMode(Context callerContext, Runnable r)329 protected void runWithCallerWithNoStrictMode(Context callerContext, Runnable r) { 330 runWithCaller(callerContext, () -> runWithNoStrictMode(r)); 331 } 332 makeBundle(Object... keysAndValues)333 public static Bundle makeBundle(Object... keysAndValues) { 334 assertTrue((keysAndValues.length % 2) == 0); 335 336 if (keysAndValues.length == 0) { 337 return null; 338 } 339 final Bundle ret = new Bundle(); 340 341 for (int i = keysAndValues.length - 2; i >= 0; i -= 2) { 342 final String key = keysAndValues[i].toString(); 343 final Object value = keysAndValues[i + 1]; 344 345 if (value == null) { 346 ret.putString(key, null); 347 } else if (value instanceof Integer) { 348 ret.putInt(key, (Integer) value); 349 } else if (value instanceof String) { 350 ret.putString(key, (String) value); 351 } else if (value instanceof Bundle) { 352 ret.putBundle(key, (Bundle) value); 353 } else { 354 fail("Type not supported yet: " + value.getClass().getName()); 355 } 356 } 357 return ret; 358 } 359 makePersistableBundle(Object... keysAndValues)360 public static PersistableBundle makePersistableBundle(Object... keysAndValues) { 361 assertTrue((keysAndValues.length % 2) == 0); 362 363 if (keysAndValues.length == 0) { 364 return null; 365 } 366 final PersistableBundle ret = new PersistableBundle(); 367 368 for (int i = keysAndValues.length - 2; i >= 0; i -= 2) { 369 final String key = keysAndValues[i].toString(); 370 final Object value = keysAndValues[i + 1]; 371 372 if (value == null) { 373 ret.putString(key, null); 374 } else if (value instanceof Integer) { 375 ret.putInt(key, (Integer) value); 376 } else if (value instanceof String) { 377 ret.putString(key, (String) value); 378 } else if (value instanceof PersistableBundle) { 379 ret.putPersistableBundle(key, (PersistableBundle) value); 380 } else { 381 fail("Type not supported yet: " + value.getClass().getName()); 382 } 383 } 384 return ret; 385 } 386 387 /** 388 * Make a shortcut with an ID. 389 */ makeShortcut(String id)390 protected ShortcutInfo makeShortcut(String id) { 391 return makeShortcut(id, "Title-" + id); 392 } 393 makeShortcutWithRank(String id, int rank)394 protected ShortcutInfo makeShortcutWithRank(String id, int rank) { 395 return makeShortcut( 396 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, 397 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank); 398 } 399 400 /** 401 * Make a shortcut with an ID and a title. 402 */ makeShortcut(String id, String shortLabel)403 protected ShortcutInfo makeShortcut(String id, String shortLabel) { 404 return makeShortcut( 405 id, shortLabel, /* activity =*/ null, /* icon =*/ null, 406 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 407 } 408 makeShortcut(String id, ComponentName activity)409 protected ShortcutInfo makeShortcut(String id, ComponentName activity) { 410 return makeShortcut( 411 id, "Title-" + id, activity, /* icon =*/ null, 412 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 413 } 414 415 /** 416 * Make a shortcut with an ID and icon. 417 */ makeShortcutWithIcon(String id, Icon icon)418 protected ShortcutInfo makeShortcutWithIcon(String id, Icon icon) { 419 return makeShortcut( 420 id, "Title-" + id, /* activity =*/ null, icon, 421 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 422 } 423 424 /** 425 * Make multiple shortcuts with IDs. 426 */ makeShortcuts(String... ids)427 protected List<ShortcutInfo> makeShortcuts(String... ids) { 428 final ArrayList<ShortcutInfo> ret = new ArrayList(); 429 for (String id : ids) { 430 ret.add(makeShortcut(id)); 431 } 432 return ret; 433 } 434 makeShortcutBuilder(String id)435 protected ShortcutInfo.Builder makeShortcutBuilder(String id) { 436 return new ShortcutInfo.Builder(getCurrentCallerContext(), id); 437 } 438 439 /** 440 * Make a shortcut with details. 441 */ makeShortcut(String id, String shortLabel, ComponentName activity, Icon icon, Intent intent, int rank)442 protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity, 443 Icon icon, Intent intent, int rank) { 444 final ShortcutInfo.Builder b = makeShortcutBuilder(id) 445 .setShortLabel(shortLabel) 446 .setRank(rank) 447 .setIntent(intent); 448 if (activity != null) { 449 b.setActivity(activity); 450 } else if (mTargetActivityOverride != null) { 451 b.setActivity(mTargetActivityOverride); 452 } 453 if (icon != null) { 454 b.setIcon(icon); 455 } 456 return b.build(); 457 } 458 459 /** 460 * Make an intent. 461 */ makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues)462 protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) { 463 final Intent intent = new Intent(action); 464 intent.setComponent(makeComponent(clazz)); 465 intent.replaceExtras(makeBundle(bundleKeysAndValues)); 466 return intent; 467 } 468 469 /** 470 * Make an component name, with the client context. 471 */ 472 @NonNull makeComponent(Class<?> clazz)473 protected ComponentName makeComponent(Class<?> clazz) { 474 return new ComponentName(getCurrentCallerContext(), clazz); 475 } 476 getIconAsLauncher(Context launcherContext, String packageName, String shortcutId)477 protected Drawable getIconAsLauncher(Context launcherContext, String packageName, 478 String shortcutId) { 479 return getIconAsLauncher(launcherContext, packageName, shortcutId, /* withBadge=*/ true); 480 } 481 getIconAsLauncher(Context launcherContext, String packageName, String shortcutId, boolean withBadge)482 protected Drawable getIconAsLauncher(Context launcherContext, String packageName, 483 String shortcutId, boolean withBadge) { 484 runWithNoStrictMode(() -> setDefaultLauncher(getInstrumentation(), launcherContext)); 485 486 final AtomicReference<Drawable> ret = new AtomicReference<>(); 487 488 runWithCallerWithNoStrictMode(launcherContext, () -> { 489 final ShortcutQuery q = new ShortcutQuery() 490 .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC 491 | ShortcutQuery.FLAG_MATCH_MANIFEST 492 | ShortcutQuery.FLAG_MATCH_PINNED 493 | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) 494 .setPackage(packageName) 495 .setShortcutIds(list(shortcutId)); 496 final List<ShortcutInfo> found = getLauncherApps().getShortcuts(q, getUserHandle()); 497 498 assertEquals("Shortcut not found", 1, found.size()); 499 500 if (withBadge) { 501 ret.set(getLauncherApps().getShortcutBadgedIconDrawable(found.get(0), 0)); 502 } else { 503 ret.set(getLauncherApps().getShortcutIconDrawable(found.get(0), 0)); 504 } 505 }); 506 return ret.get(); 507 } 508 assertIconDimensions(Context launcherContext, String packageName, String shortcutId, Icon expectedIcon)509 protected void assertIconDimensions(Context launcherContext, String packageName, 510 String shortcutId, Icon expectedIcon) { 511 final Drawable actual = getIconAsLauncher(launcherContext, packageName, shortcutId); 512 if (actual == null && expectedIcon == null) { 513 return; // okay 514 } 515 final Drawable expected = expectedIcon.loadDrawable(getTestContext()); 516 assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth()); 517 assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight()); 518 } 519 assertIconDimensions(Icon expectedIcon, Drawable actual)520 protected void assertIconDimensions(Icon expectedIcon, Drawable actual) { 521 if (actual == null && expectedIcon == null) { 522 return; // okay 523 } 524 final Drawable expected = expectedIcon.loadDrawable(getTestContext()); 525 526 if (expected instanceof AdaptiveIconDrawable) { 527 assertTrue(actual instanceof AdaptiveIconDrawable); 528 } 529 assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth()); 530 assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight()); 531 } 532 loadPackageDrawableIcon(Context packageContext, String resName)533 protected Icon loadPackageDrawableIcon(Context packageContext, String resName) 534 throws Exception { 535 final Resources res = getTestContext().getPackageManager().getResourcesForApplication( 536 packageContext.getPackageName()); 537 538 // Note the resource package names don't have the numbers. 539 final int id = res.getIdentifier(resName, "drawable", 540 "android.content.pm.cts.shortcutmanager.packages"); 541 if (id == 0) { 542 fail("Drawable " + resName + " is not found in package " 543 + packageContext.getPackageName()); 544 } 545 return Icon.createWithResource(packageContext, id); 546 } 547 loadCallerDrawableIcon(String resName)548 protected Icon loadCallerDrawableIcon(String resName) throws Exception { 549 return loadPackageDrawableIcon(getCurrentCallerContext(), resName); 550 } 551 getShortcutsAsLauncher(int flags, String packageName)552 protected List<ShortcutInfo> getShortcutsAsLauncher(int flags, String packageName) { 553 return getShortcutsAsLauncher(flags, packageName, null, 0, null); 554 } 555 getShortcutsAsLauncher( int flags, String packageName, String activityName, long changedSince, List<String> ids)556 protected List<ShortcutInfo> getShortcutsAsLauncher( 557 int flags, String packageName, String activityName, 558 long changedSince, List<String> ids) { 559 final ShortcutQuery q = new ShortcutQuery(); 560 q.setQueryFlags(flags); 561 if (packageName != null) { 562 q.setPackage(packageName); 563 if (activityName != null) { 564 q.setActivity(new ComponentName(packageName, 565 "android.content.pm.cts.shortcutmanager.packages." + activityName)); 566 } 567 } 568 q.setChangedSince(changedSince); 569 if (ids != null && ids.size() > 0) { 570 q.setShortcutIds(ids); 571 } 572 return getLauncherApps().getShortcuts(q, getUserHandle()); 573 } 574 } 575