BaseShortcutManagerTest.java revision 4e6cef49ef11bbb5bfc0e9f0fb865188492d88b0
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 */ 16package com.android.server.pm; 17 18import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.cloneShortcutList; 19import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet; 20import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; 21import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle; 22import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set; 23 24import static org.mockito.Matchers.any; 25import static org.mockito.Matchers.anyInt; 26import static org.mockito.Matchers.anyString; 27import static org.mockito.Matchers.eq; 28import static org.mockito.Mockito.doAnswer; 29import static org.mockito.Mockito.mock; 30import static org.mockito.Mockito.reset; 31import static org.mockito.Mockito.spy; 32import static org.mockito.Mockito.times; 33import static org.mockito.Mockito.verify; 34import static org.mockito.Mockito.when; 35 36import android.annotation.NonNull; 37import android.annotation.Nullable; 38import android.annotation.RequiresPermission; 39import android.annotation.UserIdInt; 40import android.app.Activity; 41import android.app.ActivityManager; 42import android.app.ActivityManagerInternal; 43import android.app.IUidObserver; 44import android.app.usage.UsageStatsManagerInternal; 45import android.content.BroadcastReceiver; 46import android.content.ComponentName; 47import android.content.Context; 48import android.content.Intent; 49import android.content.IntentFilter; 50import android.content.pm.ActivityInfo; 51import android.content.pm.ApplicationInfo; 52import android.content.pm.ILauncherApps; 53import android.content.pm.LauncherApps; 54import android.content.pm.LauncherApps.ShortcutQuery; 55import android.content.pm.PackageInfo; 56import android.content.pm.PackageManager; 57import android.content.pm.PackageManagerInternal; 58import android.content.pm.ResolveInfo; 59import android.content.pm.ShortcutInfo; 60import android.content.pm.ShortcutManager; 61import android.content.pm.ShortcutServiceInternal; 62import android.content.pm.Signature; 63import android.content.pm.UserInfo; 64import android.content.res.Resources; 65import android.content.res.XmlResourceParser; 66import android.graphics.drawable.Icon; 67import android.net.Uri; 68import android.os.Bundle; 69import android.os.FileUtils; 70import android.os.Handler; 71import android.os.Looper; 72import android.os.PersistableBundle; 73import android.os.Process; 74import android.os.UserHandle; 75import android.os.UserManager; 76import android.test.InstrumentationTestCase; 77import android.test.mock.MockContext; 78import android.util.Log; 79import android.util.Pair; 80 81import com.android.internal.util.Preconditions; 82import com.android.server.LocalServices; 83import com.android.server.SystemService; 84import com.android.server.pm.LauncherAppsService.LauncherAppsImpl; 85import com.android.server.pm.ShortcutUser.PackageWithUser; 86 87import org.junit.Assert; 88import org.mockito.ArgumentCaptor; 89import org.mockito.invocation.InvocationOnMock; 90import org.mockito.stubbing.Answer; 91 92import java.io.BufferedReader; 93import java.io.ByteArrayOutputStream; 94import java.io.File; 95import java.io.FileReader; 96import java.io.PrintWriter; 97import java.util.ArrayList; 98import java.util.HashMap; 99import java.util.HashSet; 100import java.util.LinkedHashMap; 101import java.util.List; 102import java.util.Locale; 103import java.util.Map; 104import java.util.Set; 105import java.util.function.BiFunction; 106import java.util.function.BiPredicate; 107import java.util.function.Consumer; 108import java.util.function.Function; 109 110public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { 111 protected static final String TAG = "ShortcutManagerTest"; 112 113 protected static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true 114 115 /** 116 * Whether to enable dump or not. Should be only true when debugging to avoid bugs where 117 * dump affecting the behavior. 118 */ 119 protected static final boolean ENABLE_DUMP = false // DO NOT SUBMIT WITH true 120 || DUMP_IN_TEARDOWN || ShortcutService.DEBUG; 121 122 protected static final String[] EMPTY_STRINGS = new String[0]; // Just for readability. 123 124 protected static final String MAIN_ACTIVITY_CLASS = "MainActivity"; 125 126 // public for mockito 127 public class BaseContext extends MockContext { 128 @Override 129 public Object getSystemService(String name) { 130 switch (name) { 131 case Context.USER_SERVICE: 132 return mMockUserManager; 133 } 134 throw new UnsupportedOperationException(); 135 } 136 137 @Override 138 public String getSystemServiceName(Class<?> serviceClass) { 139 return getTestContext().getSystemServiceName(serviceClass); 140 } 141 142 @Override 143 public PackageManager getPackageManager() { 144 return mMockPackageManager; 145 } 146 147 @Override 148 public Resources getResources() { 149 return getTestContext().getResources(); 150 } 151 152 @Override 153 public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, 154 IntentFilter filter, String broadcastPermission, Handler scheduler) { 155 // ignore. 156 return null; 157 } 158 159 @Override 160 public void unregisterReceiver(BroadcastReceiver receiver) { 161 // ignore. 162 } 163 } 164 165 /** Context used in the client side */ 166 public class ClientContext extends BaseContext { 167 @Override 168 public String getPackageName() { 169 return mInjectedClientPackage; 170 } 171 172 @Override 173 public int getUserId() { 174 return getCallingUserId(); 175 } 176 } 177 178 /** Context used in the service side */ 179 public class ServiceContext extends BaseContext { 180 long injectClearCallingIdentity() { 181 final int prevCallingUid = mInjectedCallingUid; 182 mInjectedCallingUid = Process.SYSTEM_UID; 183 return prevCallingUid; 184 } 185 186 void injectRestoreCallingIdentity(long token) { 187 mInjectedCallingUid = (int) token; 188 } 189 190 @Override 191 public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options, 192 UserHandle userId) { 193 } 194 195 @Override 196 public int getUserId() { 197 return UserHandle.USER_SYSTEM; 198 } 199 200 public PackageInfo injectGetActivitiesWithMetadata( 201 String packageName, @UserIdInt int userId) { 202 return BaseShortcutManagerTest.this.injectGetActivitiesWithMetadata(packageName, userId); 203 } 204 205 public XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 206 return BaseShortcutManagerTest.this.injectXmlMetaData(activityInfo, key); 207 } 208 } 209 210 /** ShortcutService with injection override methods. */ 211 protected final class ShortcutServiceTestable extends ShortcutService { 212 final ServiceContext mContext; 213 IUidObserver mUidObserver; 214 215 public ShortcutServiceTestable(ServiceContext context, Looper looper) { 216 super(context, looper, /* onyForPackageManagerApis */ false); 217 mContext = context; 218 } 219 220 @Override 221 public String injectGetLocaleTagsForUser(@UserIdInt int userId) { 222 return mInjectedLocale.toLanguageTag(); 223 } 224 225 @Override 226 boolean injectShouldPerformVerification() { 227 return true; // Always verify during unit tests. 228 } 229 230 @Override 231 String injectShortcutManagerConstants() { 232 return ConfigConstants.KEY_RESET_INTERVAL_SEC + "=" + (INTERVAL / 1000) + "," 233 + ConfigConstants.KEY_MAX_SHORTCUTS + "=" + MAX_SHORTCUTS + "," 234 + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=" 235 + MAX_UPDATES_PER_INTERVAL + "," 236 + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=" + MAX_ICON_DIMENSION + "," 237 + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=" 238 + MAX_ICON_DIMENSION_LOWRAM + "," 239 + ConfigConstants.KEY_ICON_FORMAT + "=PNG," 240 + ConfigConstants.KEY_ICON_QUALITY + "=100"; 241 } 242 243 @Override 244 long injectClearCallingIdentity() { 245 return mContext.injectClearCallingIdentity(); 246 } 247 248 @Override 249 void injectRestoreCallingIdentity(long token) { 250 mContext.injectRestoreCallingIdentity(token); 251 } 252 253 @Override 254 int injectDipToPixel(int dip) { 255 return dip; 256 } 257 258 @Override 259 long injectCurrentTimeMillis() { 260 return mInjectedCurrentTimeMillis; 261 } 262 263 @Override 264 long injectElapsedRealtime() { 265 // TODO This should be kept separately from mInjectedCurrentTimeMillis, since 266 // this should increase even if we rewind mInjectedCurrentTimeMillis in some tests. 267 return mInjectedCurrentTimeMillis - START_TIME; 268 } 269 270 @Override 271 int injectBinderCallingUid() { 272 return mInjectedCallingUid; 273 } 274 275 @Override 276 int injectGetPackageUid(String packageName, int userId) { 277 return getInjectedPackageInfo(packageName, userId, false).applicationInfo.uid; 278 } 279 280 @Override 281 File injectSystemDataPath() { 282 return new File(mInjectedFilePathRoot, "system"); 283 } 284 285 @Override 286 File injectUserDataPath(@UserIdInt int userId) { 287 return new File(mInjectedFilePathRoot, "user-" + userId); 288 } 289 290 @Override 291 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { 292 // Can't check 293 } 294 295 @Override 296 boolean injectIsLowRamDevice() { 297 return mInjectedIsLowRamDevice; 298 } 299 300 @Override 301 void injectRegisterUidObserver(IUidObserver observer, int which) { 302 mUidObserver = observer; 303 } 304 305 @Override 306 PackageManagerInternal injectPackageManagerInternal() { 307 return mMockPackageManagerInternal; 308 } 309 310 @Override 311 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { 312 return mDefaultLauncherChecker.test(callingPackage, userId); 313 } 314 315 @Override 316 PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, 317 boolean getSignatures) { 318 return getInjectedPackageInfo(packageName, userId, getSignatures); 319 } 320 321 @Override 322 ApplicationInfo injectApplicationInfoWithUninstalled( 323 String packageName, @UserIdInt int userId) { 324 PackageInfo pi = injectPackageInfoWithUninstalled( 325 packageName, userId, /* getSignatures= */ false); 326 return pi != null ? pi.applicationInfo : null; 327 } 328 329 @Override 330 List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId) { 331 return BaseShortcutManagerTest.this.getInstalledPackagesWithUninstalled(userId); 332 } 333 334 @Override 335 ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled(ComponentName activity, 336 @UserIdInt int userId) { 337 final PackageInfo pi = mContext.injectGetActivitiesWithMetadata( 338 activity.getPackageName(), userId); 339 if (pi == null || pi.activities == null) { 340 return null; 341 } 342 for (ActivityInfo ai : pi.activities) { 343 if (!mEnabledActivityChecker.test(ai.getComponentName(), userId)) { 344 continue; 345 } 346 if (activity.equals(ai.getComponentName())) { 347 return ai; 348 } 349 } 350 return null; 351 } 352 353 @Override 354 boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { 355 if (!mEnabledActivityChecker.test(activity, userId)) { 356 return false; 357 } 358 return mMainActivityChecker.test(activity, userId); 359 } 360 361 @Override 362 List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) { 363 final PackageInfo pi = mContext.injectGetActivitiesWithMetadata( 364 packageName, userId); 365 if (pi == null || pi.activities == null) { 366 return null; 367 } 368 final ArrayList<ResolveInfo> ret = new ArrayList<>(pi.activities.length); 369 for (int i = 0; i < pi.activities.length; i++) { 370 if (!mEnabledActivityChecker.test(pi.activities[i].getComponentName(), userId)) { 371 continue; 372 } 373 final ResolveInfo ri = new ResolveInfo(); 374 ri.activityInfo = pi.activities[i]; 375 ret.add(ri); 376 } 377 378 return ret; 379 } 380 381 @Override 382 ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) { 383 return mMainActivityFetcher.apply(packageName, userId); 384 } 385 386 @Override 387 boolean injectIsActivityEnabledAndExported(ComponentName activity, @UserIdInt int userId) { 388 return mEnabledActivityChecker.test(activity, userId); 389 } 390 391 @Override 392 XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 393 return mContext.injectXmlMetaData(activityInfo, key); 394 } 395 396 @Override 397 void injectPostToHandler(Runnable r) { 398 runOnHandler(r); 399 } 400 401 @Override 402 void injectEnforceCallingPermission(String permission, String message) { 403 if (!mCallerPermissions.contains(permission)) { 404 throw new SecurityException("Missing permission: " + permission); 405 } 406 } 407 408 @Override 409 boolean injectIsSafeModeEnabled() { 410 return mSafeMode; 411 } 412 413 @Override 414 void wtf(String message, Throwable th) { 415 // During tests, WTF is fatal. 416 fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); 417 } 418 } 419 420 /** ShortcutManager with injection override methods. */ 421 protected class ShortcutManagerTestable extends ShortcutManager { 422 public ShortcutManagerTestable(Context context, ShortcutServiceTestable service) { 423 super(context, service); 424 } 425 426 @Override 427 protected int injectMyUserId() { 428 return UserHandle.getUserId(mInjectedCallingUid); 429 } 430 431 @Override 432 public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { 433 // Note to simulate the binder RPC, we need to clone the incoming arguments. 434 // Otherwise bad things will happen because they're mutable. 435 return super.setDynamicShortcuts(cloneShortcutList(shortcutInfoList)); 436 } 437 438 @Override 439 public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { 440 // Note to simulate the binder RPC, we need to clone the incoming arguments. 441 return super.addDynamicShortcuts(cloneShortcutList(shortcutInfoList)); 442 } 443 444 @Override 445 public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) { 446 // Note to simulate the binder RPC, we need to clone the incoming arguments. 447 return super.updateShortcuts(cloneShortcutList(shortcutInfoList)); 448 } 449 } 450 451 protected class LauncherAppImplTestable extends LauncherAppsImpl { 452 final ServiceContext mContext; 453 454 public LauncherAppImplTestable(ServiceContext context) { 455 super(context); 456 mContext = context; 457 } 458 459 @Override 460 public void verifyCallingPackage(String callingPackage) { 461 // SKIP 462 } 463 464 @Override 465 void postToPackageMonitorHandler(Runnable r) { 466 runOnHandler(r); 467 } 468 469 @Override 470 int injectBinderCallingUid() { 471 return mInjectedCallingUid; 472 } 473 474 @Override 475 long injectClearCallingIdentity() { 476 final int prevCallingUid = mInjectedCallingUid; 477 mInjectedCallingUid = Process.SYSTEM_UID; 478 return prevCallingUid; 479 } 480 481 @Override 482 void injectRestoreCallingIdentity(long token) { 483 mInjectedCallingUid = (int) token; 484 } 485 } 486 487 protected class LauncherAppsTestable extends LauncherApps { 488 public LauncherAppsTestable(Context context, ILauncherApps service) { 489 super(context, service); 490 } 491 } 492 493 public static class ShortcutActivity extends Activity { 494 } 495 496 public static class ShortcutActivity2 extends Activity { 497 } 498 499 public static class ShortcutActivity3 extends Activity { 500 } 501 502 protected Looper mLooper; 503 protected Handler mHandler; 504 505 protected ServiceContext mServiceContext; 506 protected ClientContext mClientContext; 507 508 protected ShortcutServiceTestable mService; 509 protected ShortcutManagerTestable mManager; 510 protected ShortcutServiceInternal mInternal; 511 512 protected LauncherAppImplTestable mLauncherAppImpl; 513 514 // LauncherApps has per-instace state, so we need a differnt instance for each launcher. 515 protected final Map<Pair<Integer, String>, LauncherAppsTestable> 516 mLauncherAppsMap = new HashMap<>(); 517 protected LauncherAppsTestable mLauncherApps; // Current one 518 519 protected File mInjectedFilePathRoot; 520 521 protected boolean mSafeMode; 522 523 protected long mInjectedCurrentTimeMillis; 524 525 protected boolean mInjectedIsLowRamDevice; 526 527 protected Locale mInjectedLocale = Locale.ENGLISH; 528 529 protected int mInjectedCallingUid; 530 protected String mInjectedClientPackage; 531 532 protected Map<String, PackageInfo> mInjectedPackages; 533 534 protected Set<PackageWithUser> mUninstalledPackages; 535 536 protected PackageManager mMockPackageManager; 537 protected PackageManagerInternal mMockPackageManagerInternal; 538 protected UserManager mMockUserManager; 539 protected UsageStatsManagerInternal mMockUsageStatsManagerInternal; 540 protected ActivityManagerInternal mMockActivityManagerInternal; 541 542 protected static final String CALLING_PACKAGE_1 = "com.android.test.1"; 543 protected static final int CALLING_UID_1 = 10001; 544 545 protected static final String CALLING_PACKAGE_2 = "com.android.test.2"; 546 protected static final int CALLING_UID_2 = 10002; 547 548 protected static final String CALLING_PACKAGE_3 = "com.android.test.3"; 549 protected static final int CALLING_UID_3 = 10003; 550 551 protected static final String CALLING_PACKAGE_4 = "com.android.test.4"; 552 protected static final int CALLING_UID_4 = 10004; 553 554 protected static final String LAUNCHER_1 = "com.android.launcher.1"; 555 protected static final int LAUNCHER_UID_1 = 10011; 556 557 protected static final String LAUNCHER_2 = "com.android.launcher.2"; 558 protected static final int LAUNCHER_UID_2 = 10012; 559 560 protected static final String LAUNCHER_3 = "com.android.launcher.3"; 561 protected static final int LAUNCHER_UID_3 = 10013; 562 563 protected static final String LAUNCHER_4 = "com.android.launcher.4"; 564 protected static final int LAUNCHER_UID_4 = 10014; 565 566 protected static final int USER_0 = UserHandle.USER_SYSTEM; 567 protected static final int USER_10 = 10; 568 protected static final int USER_11 = 11; 569 protected static final int USER_P0 = 20; // profile of user 0 570 571 protected static final UserHandle HANDLE_USER_0 = UserHandle.of(USER_0); 572 protected static final UserHandle HANDLE_USER_10 = UserHandle.of(USER_10); 573 protected static final UserHandle HANDLE_USER_11 = UserHandle.of(USER_11); 574 protected static final UserHandle HANDLE_USER_P0 = UserHandle.of(USER_P0); 575 576 protected static final UserInfo USER_INFO_0 = withProfileGroupId( 577 new UserInfo(USER_0, "user0", 578 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED), 10); 579 580 protected static final UserInfo USER_INFO_10 = 581 new UserInfo(USER_10, "user10", UserInfo.FLAG_INITIALIZED); 582 583 protected static final UserInfo USER_INFO_11 = 584 new UserInfo(USER_11, "user11", UserInfo.FLAG_INITIALIZED); 585 586 protected static final UserInfo USER_INFO_P0 = withProfileGroupId( 587 new UserInfo(USER_P0, "userP0", 588 UserInfo.FLAG_MANAGED_PROFILE), 10); 589 590 protected BiPredicate<String, Integer> mDefaultLauncherChecker = 591 (callingPackage, userId) -> 592 LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage) 593 || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage); 594 595 protected BiPredicate<ComponentName, Integer> mMainActivityChecker = 596 (activity, userId) -> true; 597 598 protected BiFunction<String, Integer, ComponentName> mMainActivityFetcher = 599 (packageName, userId) -> new ComponentName(packageName, MAIN_ACTIVITY_CLASS); 600 601 protected BiPredicate<ComponentName, Integer> mEnabledActivityChecker 602 = (activity, userId) -> true; // all activities are enabled. 603 604 protected static final long START_TIME = 1440000000101L; 605 606 protected static final long INTERVAL = 10000; 607 608 protected static final int MAX_SHORTCUTS = 10; 609 610 protected static final int MAX_UPDATES_PER_INTERVAL = 3; 611 612 protected static final int MAX_ICON_DIMENSION = 128; 613 614 protected static final int MAX_ICON_DIMENSION_LOWRAM = 32; 615 616 protected static final ShortcutQuery QUERY_ALL = new ShortcutQuery(); 617 618 protected final ArrayList<String> mCallerPermissions = new ArrayList<>(); 619 620 protected final HashMap<String, LinkedHashMap<ComponentName, Integer>> mActivityMetadataResId 621 = new HashMap<>(); 622 623 protected final Map<Integer, UserInfo> mUserInfos = new HashMap<>(); 624 protected final Map<Integer, Boolean> mRunningUsers = new HashMap<>(); 625 protected final Map<Integer, Boolean> mUnlockedUsers = new HashMap<>(); 626 627 static { 628 QUERY_ALL.setQueryFlags( 629 ShortcutQuery.FLAG_GET_ALL_KINDS); 630 } 631 632 @Override 633 protected void setUp() throws Exception { 634 super.setUp(); 635 636 mLooper = Looper.getMainLooper(); 637 mHandler = new Handler(mLooper); 638 639 mServiceContext = spy(new ServiceContext()); 640 mClientContext = new ClientContext(); 641 642 mMockPackageManager = mock(PackageManager.class); 643 mMockPackageManagerInternal = mock(PackageManagerInternal.class); 644 mMockUserManager = mock(UserManager.class); 645 mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class); 646 mMockActivityManagerInternal = mock(ActivityManagerInternal.class); 647 648 LocalServices.removeServiceForTest(PackageManagerInternal.class); 649 LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); 650 LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); 651 LocalServices.addService(UsageStatsManagerInternal.class, mMockUsageStatsManagerInternal); 652 LocalServices.removeServiceForTest(ActivityManagerInternal.class); 653 LocalServices.addService(ActivityManagerInternal.class, mMockActivityManagerInternal); 654 655 // Prepare injection values. 656 657 mInjectedCurrentTimeMillis = START_TIME; 658 659 mInjectedPackages = new HashMap<>(); 660 addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1); 661 addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2); 662 addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3); 663 addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10); 664 addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4); 665 addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5); 666 addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6); 667 addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10); 668 669 // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target. 670 updatePackageInfo(CALLING_PACKAGE_3, 671 pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); 672 updatePackageInfo(LAUNCHER_3, 673 pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); 674 675 mUninstalledPackages = new HashSet<>(); 676 677 mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files"); 678 679 deleteAllSavedFiles(); 680 681 // Set up users. 682 when(mMockUserManager.getUserInfo(anyInt())).thenAnswer(new AnswerWithSystemCheck<>( 683 inv -> mUserInfos.get((Integer) inv.getArguments()[0]))); 684 685 mUserInfos.put(USER_0, USER_INFO_0); 686 mUserInfos.put(USER_10, USER_INFO_10); 687 mUserInfos.put(USER_11, USER_INFO_11); 688 mUserInfos.put(USER_P0, USER_INFO_P0); 689 690 // Set up isUserRunning and isUserUnlocked. 691 when(mMockUserManager.isUserRunning(anyInt())).thenAnswer(new AnswerWithSystemCheck<>( 692 inv -> mRunningUsers.get((Integer) inv.getArguments()[0]))); 693 694 when(mMockUserManager.isUserUnlocked(anyInt())).thenAnswer(new AnswerWithSystemCheck<>( 695 inv -> { 696 final int userId = (Integer) inv.getArguments()[0]; 697 return mRunningUsers.get(userId) && mUnlockedUsers.get(userId); 698 })); 699 700 // User 0 is always running 701 mRunningUsers.put(USER_0, true); 702 mRunningUsers.put(USER_10, false); 703 mRunningUsers.put(USER_11, false); 704 mRunningUsers.put(USER_P0, false); 705 706 // Unlock all users by default. 707 mUnlockedUsers.put(USER_0, true); 708 mUnlockedUsers.put(USER_10, true); 709 mUnlockedUsers.put(USER_11, true); 710 mUnlockedUsers.put(USER_P0, true); 711 712 // Set up mMockActivityManagerInternal. 713 // By default, startActivityAsPackage() will simply forward to startActivityAsUser(). 714 doAnswer(new AnswerWithSystemCheck<>(inv -> { 715 mServiceContext.startActivityAsUser( 716 (Intent) inv.getArguments()[2], 717 (Bundle) inv.getArguments()[3], 718 UserHandle.of((Integer) inv.getArguments()[1])); 719 return ActivityManager.START_SUCCESS; 720 })).when(mMockActivityManagerInternal).startActivityAsPackage(anyString(), anyInt(), 721 any(Intent.class), any(Bundle.class)); 722 723 // Set up resources 724 setUpAppResources(); 725 726 // Start the service. 727 initService(); 728 setCaller(CALLING_PACKAGE_1); 729 } 730 731 /** 732 * Returns a boolean but also checks if the current UID is SYSTEM_UID. 733 */ 734 protected class AnswerWithSystemCheck<T> implements Answer<T> { 735 private final Function<InvocationOnMock, T> mChecker; 736 737 public AnswerWithSystemCheck(Function<InvocationOnMock, T> checker) { 738 mChecker = checker; 739 } 740 741 @Override 742 public T answer(InvocationOnMock invocation) throws Throwable { 743 assertEquals("Must be called on SYSTEM UID.", 744 Process.SYSTEM_UID, mInjectedCallingUid); 745 return mChecker.apply(invocation); 746 } 747 } 748 749 protected void setUpAppResources() throws Exception { 750 setUpAppResources(/* offset = */ 0); 751 } 752 753 protected void setUpAppResources(int ressIdOffset) throws Exception { 754 // ressIdOffset is used to adjust resource IDs to emulate the case where an updated app 755 // has resource IDs changed. 756 757 doAnswer(pmInvocation -> { 758 assertEquals(Process.SYSTEM_UID, mInjectedCallingUid); 759 760 final String packageName = (String) pmInvocation.getArguments()[0]; 761 final int userId = (Integer) pmInvocation.getArguments()[1]; 762 763 final Resources res = mock(Resources.class); 764 765 doAnswer(resInvocation -> { 766 final int argResId = (Integer) resInvocation.getArguments()[0]; 767 768 return "string-" + packageName + "-user:" + userId + "-res:" + argResId 769 + "/" + mInjectedLocale; 770 }).when(res).getString(anyInt()); 771 772 doAnswer(resInvocation -> { 773 final int resId = (Integer) resInvocation.getArguments()[0]; 774 775 // Always use the "string" resource type. The type doesn't matter during the test. 776 return packageName + ":string/r" + resId; 777 }).when(res).getResourceName(anyInt()); 778 779 doAnswer(resInvocation -> { 780 final String argResName = (String) resInvocation.getArguments()[0]; 781 final String argType = (String) resInvocation.getArguments()[1]; 782 final String argPackageName = (String) resInvocation.getArguments()[2]; 783 784 // See the above code. getResourceName() will just use "r" + res ID as the entry 785 // name. 786 String entryName = argResName; 787 if (entryName.contains("/")) { 788 entryName = ShortcutInfo.getResourceEntryName(entryName); 789 } 790 return Integer.parseInt(entryName.substring(1)) + ressIdOffset; 791 }).when(res).getIdentifier(anyString(), anyString(), anyString()); 792 return res; 793 }).when(mMockPackageManager).getResourcesForApplicationAsUser(anyString(), anyInt()); 794 } 795 796 protected static UserInfo withProfileGroupId(UserInfo in, int groupId) { 797 in.profileGroupId = groupId; 798 return in; 799 } 800 801 @Override 802 protected void tearDown() throws Exception { 803 if (DUMP_IN_TEARDOWN) dumpsysOnLogcat("Teardown"); 804 805 shutdownServices(); 806 807 super.tearDown(); 808 } 809 810 protected Context getTestContext() { 811 return getInstrumentation().getContext(); 812 } 813 814 protected ShortcutManager getManager() { 815 return mManager; 816 } 817 818 protected void deleteAllSavedFiles() { 819 // Empty the data directory. 820 if (mInjectedFilePathRoot.exists()) { 821 Assert.assertTrue("failed to delete dir", 822 FileUtils.deleteContents(mInjectedFilePathRoot)); 823 } 824 mInjectedFilePathRoot.mkdirs(); 825 } 826 827 /** (Re-) init the manager and the service. */ 828 protected void initService() { 829 shutdownServices(); 830 831 LocalServices.removeServiceForTest(ShortcutServiceInternal.class); 832 833 // Instantiate targets. 834 mService = new ShortcutServiceTestable(mServiceContext, mLooper); 835 mManager = new ShortcutManagerTestable(mClientContext, mService); 836 837 mInternal = LocalServices.getService(ShortcutServiceInternal.class); 838 839 mLauncherAppImpl = new LauncherAppImplTestable(mServiceContext); 840 mLauncherApps = null; 841 mLauncherAppsMap.clear(); 842 843 // Send boot sequence events. 844 mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY); 845 846 mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); 847 } 848 849 protected void shutdownServices() { 850 if (mService != null) { 851 // Flush all the unsaved data from the previous instance. 852 mService.saveDirtyInfo(); 853 854 // Make sure everything is consistent. 855 mService.verifyStates(); 856 } 857 LocalServices.removeServiceForTest(ShortcutServiceInternal.class); 858 859 mService = null; 860 mManager = null; 861 mInternal = null; 862 mLauncherAppImpl = null; 863 mLauncherApps = null; 864 mLauncherAppsMap.clear(); 865 } 866 867 protected void runOnHandler(Runnable r) { 868 final long token = mServiceContext.injectClearCallingIdentity(); 869 try { 870 r.run(); 871 } finally { 872 mServiceContext.injectRestoreCallingIdentity(token); 873 } 874 } 875 876 protected void addPackage(String packageName, int uid, int version) { 877 addPackage(packageName, uid, version, packageName); 878 } 879 880 protected Signature[] genSignatures(String... signatures) { 881 final Signature[] sigs = new Signature[signatures.length]; 882 for (int i = 0; i < signatures.length; i++){ 883 sigs[i] = new Signature(signatures[i].getBytes()); 884 } 885 return sigs; 886 } 887 888 protected PackageInfo genPackage(String packageName, int uid, int version, String... signatures) { 889 final PackageInfo pi = new PackageInfo(); 890 pi.packageName = packageName; 891 pi.applicationInfo = new ApplicationInfo(); 892 pi.applicationInfo.uid = uid; 893 pi.applicationInfo.flags = ApplicationInfo.FLAG_INSTALLED 894 | ApplicationInfo.FLAG_ALLOW_BACKUP; 895 pi.versionCode = version; 896 pi.applicationInfo.versionCode = version; 897 pi.signatures = genSignatures(signatures); 898 899 return pi; 900 } 901 902 protected void addPackage(String packageName, int uid, int version, String... signatures) { 903 mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures)); 904 } 905 906 protected void updatePackageInfo(String packageName, Consumer<PackageInfo> c) { 907 c.accept(mInjectedPackages.get(packageName)); 908 } 909 910 protected void updatePackageVersion(String packageName, int increment) { 911 updatePackageInfo(packageName, pi -> { 912 pi.versionCode += increment; 913 pi.applicationInfo.versionCode += increment; 914 }); 915 } 916 917 protected void updatePackageLastUpdateTime(String packageName, long increment) { 918 updatePackageInfo(packageName, pi -> { 919 pi.lastUpdateTime += increment; 920 }); 921 } 922 923 protected void uninstallPackage(int userId, String packageName) { 924 if (ENABLE_DUMP) { 925 Log.v(TAG, "Unnstall package " + packageName + " / " + userId); 926 } 927 mUninstalledPackages.add(PackageWithUser.of(userId, packageName)); 928 } 929 930 protected void installPackage(int userId, String packageName) { 931 if (ENABLE_DUMP) { 932 Log.v(TAG, "Install package " + packageName + " / " + userId); 933 } 934 mUninstalledPackages.remove(PackageWithUser.of(userId, packageName)); 935 } 936 937 PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId, 938 boolean getSignatures) { 939 final PackageInfo pi = mInjectedPackages.get(packageName); 940 if (pi == null) return null; 941 942 final PackageInfo ret = new PackageInfo(); 943 ret.packageName = pi.packageName; 944 ret.versionCode = pi.versionCode; 945 ret.lastUpdateTime = pi.lastUpdateTime; 946 947 ret.applicationInfo = new ApplicationInfo(pi.applicationInfo); 948 ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid); 949 ret.applicationInfo.packageName = pi.packageName; 950 951 if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) { 952 ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; 953 } 954 955 if (getSignatures) { 956 ret.signatures = pi.signatures; 957 } 958 959 return ret; 960 } 961 962 protected void addApplicationInfo(PackageInfo pi, List<ApplicationInfo> list) { 963 if (pi != null && pi.applicationInfo != null) { 964 list.add(pi.applicationInfo); 965 } 966 } 967 968 protected List<ApplicationInfo> getInstalledApplications(int userId) { 969 final ArrayList<ApplicationInfo> ret = new ArrayList<>(); 970 971 addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret); 972 addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret); 973 addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret); 974 addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret); 975 addApplicationInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret); 976 addApplicationInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret); 977 addApplicationInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret); 978 addApplicationInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret); 979 980 return ret; 981 } 982 983 private void addPackageInfo(PackageInfo pi, List<PackageInfo> list) { 984 if (pi != null) { 985 list.add(pi); 986 } 987 } 988 989 private List<PackageInfo> getInstalledPackagesWithUninstalled(int userId) { 990 final ArrayList<PackageInfo> ret = new ArrayList<>(); 991 992 addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret); 993 addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret); 994 addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret); 995 addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret); 996 addPackageInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret); 997 addPackageInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret); 998 addPackageInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret); 999 addPackageInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret); 1000 1001 return ret; 1002 } 1003 1004 protected void addManifestShortcutResource(ComponentName activity, int resId) { 1005 final String packageName = activity.getPackageName(); 1006 LinkedHashMap<ComponentName, Integer> map = mActivityMetadataResId.get(packageName); 1007 if (map == null) { 1008 map = new LinkedHashMap<>(); 1009 mActivityMetadataResId.put(packageName, map); 1010 } 1011 map.put(activity, resId); 1012 } 1013 1014 protected PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) { 1015 final PackageInfo ret = getInjectedPackageInfo(packageName, userId, 1016 /* getSignatures=*/ false); 1017 1018 final HashMap<ComponentName, Integer> activities = mActivityMetadataResId.get(packageName); 1019 if (activities != null) { 1020 final ArrayList<ActivityInfo> list = new ArrayList<>(); 1021 1022 for (ComponentName cn : activities.keySet()) { 1023 ActivityInfo ai = new ActivityInfo(); 1024 ai.packageName = cn.getPackageName(); 1025 ai.name = cn.getClassName(); 1026 ai.metaData = new Bundle(); 1027 ai.metaData.putInt(ShortcutParser.METADATA_KEY, activities.get(cn)); 1028 ai.applicationInfo = ret.applicationInfo; 1029 list.add(ai); 1030 } 1031 ret.activities = list.toArray(new ActivityInfo[list.size()]); 1032 } 1033 return ret; 1034 } 1035 1036 protected XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 1037 if (!ShortcutParser.METADATA_KEY.equals(key) || activityInfo.metaData == null) { 1038 return null; 1039 } 1040 final int resId = activityInfo.metaData.getInt(key); 1041 return getTestContext().getResources().getXml(resId); 1042 } 1043 1044 /** Replace the current calling package */ 1045 protected void setCaller(String packageName, int userId) { 1046 mInjectedClientPackage = packageName; 1047 mInjectedCallingUid = 1048 Preconditions.checkNotNull(getInjectedPackageInfo(packageName, userId, false), 1049 "Unknown package").applicationInfo.uid; 1050 1051 // Set up LauncherApps for this caller. 1052 final Pair<Integer, String> key = Pair.create(userId, packageName); 1053 if (!mLauncherAppsMap.containsKey(key)) { 1054 mLauncherAppsMap.put(key, new LauncherAppsTestable(mClientContext, mLauncherAppImpl)); 1055 } 1056 mLauncherApps = mLauncherAppsMap.get(key); 1057 } 1058 1059 protected void setCaller(String packageName) { 1060 setCaller(packageName, UserHandle.USER_SYSTEM); 1061 } 1062 1063 protected String getCallingPackage() { 1064 return mInjectedClientPackage; 1065 } 1066 1067 protected void setDefaultLauncherChecker(BiPredicate<String, Integer> p) { 1068 mDefaultLauncherChecker = p; 1069 } 1070 1071 protected void runWithCaller(String packageName, int userId, Runnable r) { 1072 final String previousPackage = mInjectedClientPackage; 1073 final int previousUserId = UserHandle.getUserId(mInjectedCallingUid); 1074 1075 setCaller(packageName, userId); 1076 1077 r.run(); 1078 1079 setCaller(previousPackage, previousUserId); 1080 } 1081 1082 protected void runWithSystemUid(Runnable r) { 1083 final int origUid = mInjectedCallingUid; 1084 mInjectedCallingUid = Process.SYSTEM_UID; 1085 r.run(); 1086 mInjectedCallingUid = origUid; 1087 } 1088 1089 protected void lookupAndFillInResourceNames(ShortcutInfo si) { 1090 runWithSystemUid(() -> si.lookupAndFillInResourceNames( 1091 mService.injectGetResourcesForApplicationAsUser(si.getPackage(), si.getUserId()))); 1092 } 1093 1094 protected int getCallingUserId() { 1095 return UserHandle.getUserId(mInjectedCallingUid); 1096 } 1097 1098 protected UserHandle getCallingUser() { 1099 return UserHandle.of(getCallingUserId()); 1100 } 1101 1102 /** For debugging */ 1103 protected void dumpsysOnLogcat() { 1104 dumpsysOnLogcat(""); 1105 } 1106 1107 protected void dumpsysOnLogcat(String message) { 1108 dumpsysOnLogcat(message, false); 1109 } 1110 1111 protected void dumpsysOnLogcat(String message, boolean force) { 1112 if (force || !ENABLE_DUMP) return; 1113 1114 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 1115 final PrintWriter pw = new PrintWriter(out); 1116 mService.dumpInner(pw, null); 1117 pw.close(); 1118 1119 Log.v(TAG, "Dumping ShortcutService: " + message); 1120 for (String line : out.toString().split("\n")) { 1121 Log.v(TAG, line); 1122 } 1123 } 1124 1125 /** 1126 * For debugging, dump arbitrary file on logcat. 1127 */ 1128 protected void dumpFileOnLogcat(String path) { 1129 dumpFileOnLogcat(path, ""); 1130 } 1131 1132 protected void dumpFileOnLogcat(String path, String message) { 1133 if (!ENABLE_DUMP) return; 1134 1135 Log.v(TAG, "Dumping file: " + path + " " + message); 1136 final StringBuilder sb = new StringBuilder(); 1137 try (BufferedReader br = new BufferedReader(new FileReader(path))) { 1138 String line; 1139 while ((line = br.readLine()) != null) { 1140 Log.v(TAG, line); 1141 } 1142 } catch (Exception e) { 1143 Log.e(TAG, "Couldn't read file", e); 1144 fail("Exception " + e); 1145 } 1146 } 1147 1148 /** 1149 * For debugging, dump the main state file on logcat. 1150 */ 1151 protected void dumpBaseStateFile() { 1152 mService.saveDirtyInfo(); 1153 dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath() 1154 + "/system/" + ShortcutService.FILENAME_BASE_STATE); 1155 } 1156 1157 /** 1158 * For debugging, dump per-user state file on logcat. 1159 */ 1160 protected void dumpUserFile(int userId) { 1161 dumpUserFile(userId, ""); 1162 } 1163 1164 protected void dumpUserFile(int userId, String message) { 1165 mService.saveDirtyInfo(); 1166 dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath() 1167 + "/user-" + userId 1168 + "/" + ShortcutService.FILENAME_USER_PACKAGES, message); 1169 } 1170 1171 /** 1172 * Make a shortcut with an ID. 1173 */ 1174 protected ShortcutInfo makeShortcut(String id) { 1175 return makeShortcut( 1176 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, 1177 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1178 } 1179 1180 protected ShortcutInfo makeShortcutWithTitle(String id, String title) { 1181 return makeShortcut( 1182 id, title, /* activity =*/ null, /* icon =*/ null, 1183 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1184 } 1185 1186 /** 1187 * Make a shortcut with an ID and timestamp. 1188 */ 1189 protected ShortcutInfo makeShortcutWithTimestamp(String id, long timestamp) { 1190 final ShortcutInfo s = makeShortcut( 1191 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, 1192 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1193 s.setTimestamp(timestamp); 1194 return s; 1195 } 1196 1197 /** 1198 * Make a shortcut with an ID, a timestamp and an activity component 1199 */ 1200 protected ShortcutInfo makeShortcutWithTimestampWithActivity(String id, long timestamp, 1201 ComponentName activity) { 1202 final ShortcutInfo s = makeShortcut( 1203 id, "Title-" + id, activity, /* icon =*/ null, 1204 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1205 s.setTimestamp(timestamp); 1206 return s; 1207 } 1208 1209 /** 1210 * Make a shortcut with an ID and icon. 1211 */ 1212 protected ShortcutInfo makeShortcutWithIcon(String id, Icon icon) { 1213 return makeShortcut( 1214 id, "Title-" + id, /* activity =*/ null, icon, 1215 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1216 } 1217 1218 protected ShortcutInfo makePackageShortcut(String packageName, String id) { 1219 String origCaller = getCallingPackage(); 1220 1221 setCaller(packageName); 1222 ShortcutInfo s = makeShortcut( 1223 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, 1224 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1225 setCaller(origCaller); // restore the caller 1226 1227 return s; 1228 } 1229 1230 /** 1231 * Make multiple shortcuts with IDs. 1232 */ 1233 protected List<ShortcutInfo> makeShortcuts(String... ids) { 1234 final ArrayList<ShortcutInfo> ret = new ArrayList(); 1235 for (String id : ids) { 1236 ret.add(makeShortcut(id)); 1237 } 1238 return ret; 1239 } 1240 1241 protected ShortcutInfo.Builder makeShortcutBuilder() { 1242 return new ShortcutInfo.Builder(mClientContext); 1243 } 1244 1245 protected ShortcutInfo makeShortcutWithActivity(String id, ComponentName activity) { 1246 return makeShortcut( 1247 id, "Title-" + id, activity, /* icon =*/ null, 1248 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1249 } 1250 1251 protected ShortcutInfo makeShortcutWithIntent(String id, Intent intent) { 1252 return makeShortcut( 1253 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, 1254 intent, /* rank =*/ 0); 1255 } 1256 1257 protected ShortcutInfo makeShortcutWithActivityAndTitle(String id, ComponentName activity, 1258 String title) { 1259 return makeShortcut( 1260 id, title, activity, /* icon =*/ null, 1261 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); 1262 } 1263 1264 protected ShortcutInfo makeShortcutWithActivityAndRank(String id, ComponentName activity, 1265 int rank) { 1266 return makeShortcut( 1267 id, "Title-" + id, activity, /* icon =*/ null, 1268 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank); 1269 } 1270 1271 /** 1272 * Make a shortcut with details. 1273 */ 1274 protected ShortcutInfo makeShortcut(String id, String title, ComponentName activity, 1275 Icon icon, Intent intent, int rank) { 1276 final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) 1277 .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy")) 1278 .setShortLabel(title) 1279 .setRank(rank) 1280 .setIntent(intent); 1281 if (icon != null) { 1282 b.setIcon(icon); 1283 } 1284 if (activity != null) { 1285 b.setActivity(activity); 1286 } 1287 final ShortcutInfo s = b.build(); 1288 1289 s.setTimestamp(mInjectedCurrentTimeMillis); // HACK 1290 1291 return s; 1292 } 1293 1294 /** 1295 * Make a shortcut with details. 1296 */ 1297 protected ShortcutInfo makeShortcutWithExtras(String id, Intent intent, 1298 PersistableBundle extras) { 1299 final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) 1300 .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy")) 1301 .setShortLabel("title-" + id) 1302 .setExtras(extras) 1303 .setIntent(intent); 1304 final ShortcutInfo s = b.build(); 1305 1306 s.setTimestamp(mInjectedCurrentTimeMillis); // HACK 1307 1308 return s; 1309 } 1310 1311 /** 1312 * Make an intent. 1313 */ 1314 protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) { 1315 final Intent intent = new Intent(action); 1316 intent.setComponent(makeComponent(clazz)); 1317 intent.replaceExtras(makeBundle(bundleKeysAndValues)); 1318 return intent; 1319 } 1320 1321 /** 1322 * Make an component name, with the client context. 1323 */ 1324 @NonNull 1325 protected ComponentName makeComponent(Class<?> clazz) { 1326 return new ComponentName(mClientContext, clazz); 1327 } 1328 1329 @NonNull 1330 protected ShortcutInfo findById(List<ShortcutInfo> list, String id) { 1331 for (ShortcutInfo s : list) { 1332 if (s.getId().equals(id)) { 1333 return s; 1334 } 1335 } 1336 fail("Shortcut with id " + id + " not found"); 1337 return null; 1338 } 1339 1340 protected void assertSystem() { 1341 assertEquals("Caller must be system", Process.SYSTEM_UID, mInjectedCallingUid); 1342 } 1343 1344 protected void assertResetTimes(long expectedLastResetTime, long expectedNextResetTime) { 1345 assertEquals(expectedLastResetTime, mService.getLastResetTimeLocked()); 1346 assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked()); 1347 } 1348 1349 public static List<ShortcutInfo> assertAllNotHaveIcon( 1350 List<ShortcutInfo> actualShortcuts) { 1351 for (ShortcutInfo s : actualShortcuts) { 1352 assertNull("ID " + s.getId(), s.getIcon()); 1353 } 1354 return actualShortcuts; 1355 } 1356 1357 @NonNull 1358 protected List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts, 1359 int shortcutFlags) { 1360 for (ShortcutInfo s : actualShortcuts) { 1361 assertTrue("ID " + s.getId() + " doesn't have flags " + shortcutFlags, 1362 s.hasFlags(shortcutFlags)); 1363 } 1364 return actualShortcuts; 1365 } 1366 1367 protected ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) { 1368 return mService.getPackageShortcutForTest(packageName, shortcutId, userId); 1369 } 1370 1371 protected void assertShortcutExists(String packageName, String shortcutId, int userId) { 1372 assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null); 1373 } 1374 1375 protected void assertShortcutNotExists(String packageName, String shortcutId, int userId) { 1376 assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null); 1377 } 1378 1379 protected Intent launchShortcutAndGetIntent( 1380 @NonNull String packageName, @NonNull String shortcutId, int userId) { 1381 reset(mServiceContext); 1382 mLauncherApps.startShortcut(packageName, shortcutId, null, null, 1383 UserHandle.of(userId)); 1384 1385 final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 1386 verify(mServiceContext).startActivityAsUser( 1387 intentCaptor.capture(), 1388 any(Bundle.class), 1389 eq(UserHandle.of(userId))); 1390 return intentCaptor.getValue(); 1391 } 1392 1393 protected Intent launchShortcutAndGetIntent_withShortcutInfo( 1394 @NonNull String packageName, @NonNull String shortcutId, int userId) { 1395 reset(mServiceContext); 1396 1397 mLauncherApps.startShortcut( 1398 getShortcutInfoAsLauncher(packageName, shortcutId, userId), null, null); 1399 1400 final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 1401 verify(mServiceContext).startActivityAsUser( 1402 intentCaptor.capture(), 1403 any(Bundle.class), 1404 eq(UserHandle.of(userId))); 1405 return intentCaptor.getValue(); 1406 } 1407 1408 protected void assertShortcutLaunchable(@NonNull String packageName, @NonNull String shortcutId, 1409 int userId) { 1410 assertNotNull(launchShortcutAndGetIntent(packageName, shortcutId, userId)); 1411 assertNotNull(launchShortcutAndGetIntent_withShortcutInfo(packageName, shortcutId, userId)); 1412 } 1413 1414 protected void assertShortcutNotLaunchable(@NonNull String packageName, 1415 @NonNull String shortcutId, int userId, Class<?> expectedException) { 1416 reset(mServiceContext); 1417 Exception thrown = null; 1418 try { 1419 mLauncherApps.startShortcut(packageName, shortcutId, null, null, 1420 UserHandle.of(userId)); 1421 } catch (Exception e) { 1422 thrown = e; 1423 } 1424 // This shouldn't have been called. 1425 verify(mServiceContext, times(0)).startActivityAsUser( 1426 any(Intent.class), 1427 any(Bundle.class), 1428 any(UserHandle.class)); 1429 assertNotNull("Exception was not thrown", thrown); 1430 assertEquals("Exception type different", expectedException, thrown.getClass()); 1431 } 1432 1433 protected void assertBitmapDirectories(int userId, String... expectedDirectories) { 1434 final Set<String> expected = hashSet(set(expectedDirectories)); 1435 1436 final Set<String> actual = new HashSet<>(); 1437 1438 final File[] files = mService.getUserBitmapFilePath(userId).listFiles(); 1439 if (files != null) { 1440 for (File child : files) { 1441 if (child.isDirectory()) { 1442 actual.add(child.getName()); 1443 } 1444 } 1445 } 1446 1447 assertEquals(expected, actual); 1448 } 1449 1450 protected void assertBitmapFiles(int userId, String packageName, String... expectedFiles) { 1451 final Set<String> expected = hashSet(set(expectedFiles)); 1452 1453 final Set<String> actual = new HashSet<>(); 1454 1455 final File[] files = new File(mService.getUserBitmapFilePath(userId), packageName) 1456 .listFiles(); 1457 if (files != null) { 1458 for (File child : files) { 1459 if (child.isFile()) { 1460 actual.add(child.getName()); 1461 } 1462 } 1463 } 1464 1465 assertEquals(expected, actual); 1466 } 1467 1468 protected String getBitmapFilename(int userId, String packageName, String shortcutId) { 1469 final ShortcutInfo si = mService.getPackageShortcutForTest(packageName, shortcutId, userId); 1470 if (si == null) { 1471 return null; 1472 } 1473 return new File(si.getBitmapPath()).getName(); 1474 } 1475 1476 /** 1477 * @return all shortcuts stored internally for the caller. This reflects the *internal* view 1478 * of shortcuts, which may be different from what {@link #getCallerVisibleShortcuts} would 1479 * return, because getCallerVisibleShortcuts() will get shortcuts from the proper "front door" 1480 * which performs some extra checks, like {@link ShortcutPackage#onRestored}. 1481 */ 1482 protected List<ShortcutInfo> getCallerShortcuts() { 1483 final ShortcutPackage p = mService.getPackageShortcutForTest( 1484 getCallingPackage(), getCallingUserId()); 1485 return p == null ? null : p.getAllShortcutsForTest(); 1486 } 1487 1488 /** 1489 * @return all shortcuts owned by caller that are actually visible via ShortcutManager. 1490 * See also {@link #getCallerShortcuts}. 1491 */ 1492 protected List<ShortcutInfo> getCallerVisibleShortcuts() { 1493 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 1494 ret.addAll(mManager.getDynamicShortcuts()); 1495 ret.addAll(mManager.getPinnedShortcuts()); 1496 ret.addAll(mManager.getManifestShortcuts()); 1497 return ret; 1498 } 1499 1500 protected ShortcutInfo getCallerShortcut(String shortcutId) { 1501 return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId()); 1502 } 1503 1504 protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) { 1505 final List<ShortcutInfo>[] ret = new List[1]; 1506 runWithCaller(launcher, userId, () -> { 1507 final ShortcutQuery q = new ShortcutQuery(); 1508 q.setQueryFlags(queryFlags); 1509 ret[0] = mLauncherApps.getShortcuts(q, UserHandle.of(userId)); 1510 }); 1511 return ret[0]; 1512 } 1513 1514 protected List<ShortcutInfo> getLauncherPinnedShortcuts(String launcher, int userId) { 1515 return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED); 1516 } 1517 1518 protected ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId, 1519 int userId) { 1520 final List<ShortcutInfo> infoList = 1521 mLauncherApps.getShortcutInfo(packageName, list(shortcutId), 1522 UserHandle.of(userId)); 1523 assertEquals("No shortcutInfo found (or too many of them)", 1, infoList.size()); 1524 return infoList.get(0); 1525 } 1526 1527 protected Intent genPackageAddIntent(String packageName, int userId) { 1528 installPackage(userId, packageName); 1529 1530 Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED); 1531 i.setData(Uri.parse("package:" + packageName)); 1532 i.putExtra(Intent.EXTRA_USER_HANDLE, userId); 1533 return i; 1534 } 1535 1536 protected Intent genPackageDeleteIntent(String pakcageName, int userId) { 1537 uninstallPackage(userId, pakcageName); 1538 1539 Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED); 1540 i.setData(Uri.parse("package:" + pakcageName)); 1541 i.putExtra(Intent.EXTRA_USER_HANDLE, userId); 1542 return i; 1543 } 1544 1545 protected Intent genPackageUpdateIntent(String pakcageName, int userId) { 1546 installPackage(userId, pakcageName); 1547 1548 Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED); 1549 i.setData(Uri.parse("package:" + pakcageName)); 1550 i.putExtra(Intent.EXTRA_USER_HANDLE, userId); 1551 i.putExtra(Intent.EXTRA_REPLACING, true); 1552 return i; 1553 } 1554 1555 protected Intent genPackageChangedIntent(String pakcageName, int userId) { 1556 Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED); 1557 i.setData(Uri.parse("package:" + pakcageName)); 1558 i.putExtra(Intent.EXTRA_USER_HANDLE, userId); 1559 return i; 1560 } 1561 1562 protected Intent genPackageDataClear(String packageName, int userId) { 1563 Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED); 1564 i.setData(Uri.parse("package:" + packageName)); 1565 i.putExtra(Intent.EXTRA_USER_HANDLE, userId); 1566 return i; 1567 } 1568 1569 protected void assertExistsAndShadow(ShortcutPackageItem spi) { 1570 assertNotNull(spi); 1571 assertTrue(spi.getPackageInfo().isShadow()); 1572 } 1573 1574 protected File makeFile(File baseDirectory, String... paths) { 1575 File ret = baseDirectory; 1576 1577 for (String path : paths) { 1578 ret = new File(ret, path); 1579 } 1580 1581 return ret; 1582 } 1583 1584 protected boolean bitmapDirectoryExists(String packageName, int userId) { 1585 final File path = new File(mService.getUserBitmapFilePath(userId), packageName); 1586 return path.isDirectory(); 1587 } 1588 protected static ShortcutQuery buildQuery(long changedSince, 1589 String packageName, ComponentName componentName, 1590 /* @ShortcutQuery.QueryFlags */ int flags) { 1591 return buildQuery(changedSince, packageName, null, componentName, flags); 1592 } 1593 1594 protected static ShortcutQuery buildQuery(long changedSince, 1595 String packageName, List<String> shortcutIds, ComponentName componentName, 1596 /* @ShortcutQuery.QueryFlags */ int flags) { 1597 final ShortcutQuery q = new ShortcutQuery(); 1598 q.setChangedSince(changedSince); 1599 q.setPackage(packageName); 1600 q.setShortcutIds(shortcutIds); 1601 q.setActivity(componentName); 1602 q.setQueryFlags(flags); 1603 return q; 1604 } 1605 1606 protected static ShortcutQuery buildAllQuery(String packageName) { 1607 final ShortcutQuery q = new ShortcutQuery(); 1608 q.setPackage(packageName); 1609 q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS); 1610 return q; 1611 } 1612 1613 protected static ShortcutQuery buildPinnedQuery(String packageName) { 1614 final ShortcutQuery q = new ShortcutQuery(); 1615 q.setPackage(packageName); 1616 q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED); 1617 return q; 1618 } 1619 1620 protected static ShortcutQuery buildQueryWithFlags(int queryFlags) { 1621 final ShortcutQuery q = new ShortcutQuery(); 1622 q.setQueryFlags(queryFlags); 1623 return q; 1624 } 1625 1626 protected void backupAndRestore() { 1627 int prevUid = mInjectedCallingUid; 1628 1629 mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it. 1630 1631 dumpsysOnLogcat("Before backup"); 1632 1633 final byte[] payload = mService.getBackupPayload(USER_0); 1634 if (ENABLE_DUMP) { 1635 final String xml = new String(payload); 1636 Log.v(TAG, "Backup payload:"); 1637 for (String line : xml.split("\n")) { 1638 Log.v(TAG, line); 1639 } 1640 } 1641 1642 // Before doing anything else, uninstall all packages. 1643 for (int userId : list(USER_0, USER_P0)) { 1644 for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3, 1645 LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) { 1646 uninstallPackage(userId, pkg); 1647 } 1648 } 1649 1650 shutdownServices(); 1651 1652 deleteAllSavedFiles(); 1653 1654 initService(); 1655 mService.applyRestore(payload, USER_0); 1656 1657 // handleUnlockUser will perform the gone package check, but it shouldn't remove 1658 // shadow information. 1659 mService.handleUnlockUser(USER_0); 1660 1661 dumpsysOnLogcat("After restore"); 1662 1663 mInjectedCallingUid = prevUid; 1664 } 1665 1666 protected void prepareCrossProfileDataSet() { 1667 runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { 1668 assertTrue(mManager.setDynamicShortcuts(list( 1669 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), 1670 makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); 1671 }); 1672 runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { 1673 assertTrue(mManager.setDynamicShortcuts(list( 1674 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), 1675 makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); 1676 }); 1677 runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { 1678 assertTrue(mManager.setDynamicShortcuts(list( 1679 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), 1680 makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); 1681 }); 1682 runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { 1683 assertTrue(mManager.setDynamicShortcuts(list())); 1684 }); 1685 runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { 1686 assertTrue(mManager.setDynamicShortcuts(list( 1687 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), 1688 makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); 1689 }); 1690 runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { 1691 assertTrue(mManager.setDynamicShortcuts(list( 1692 makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"), 1693 makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6")))); 1694 }); 1695 1696 runWithCaller(LAUNCHER_1, USER_0, () -> { 1697 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0); 1698 mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0); 1699 mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0); 1700 1701 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0); 1702 }); 1703 runWithCaller(LAUNCHER_2, USER_0, () -> { 1704 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0); 1705 mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0); 1706 mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0); 1707 1708 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0); 1709 }); 1710 1711 // Note LAUNCHER_3 has allowBackup=false. 1712 runWithCaller(LAUNCHER_3, USER_0, () -> { 1713 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0); 1714 mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0); 1715 mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0); 1716 1717 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0); 1718 }); 1719 runWithCaller(LAUNCHER_4, USER_0, () -> { 1720 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0); 1721 mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0); 1722 mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0); 1723 mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0); 1724 }); 1725 1726 // Launcher on a managed profile is referring ot user 0! 1727 runWithCaller(LAUNCHER_1, USER_P0, () -> { 1728 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0); 1729 mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0); 1730 mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"), 1731 HANDLE_USER_0); 1732 1733 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0); 1734 }); 1735 runWithCaller(LAUNCHER_1, USER_10, () -> { 1736 mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10); 1737 mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10); 1738 mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"), 1739 HANDLE_USER_10); 1740 }); 1741 1742 // Then remove some dynamic shortcuts. 1743 runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { 1744 assertTrue(mManager.setDynamicShortcuts(list( 1745 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); 1746 }); 1747 runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { 1748 assertTrue(mManager.setDynamicShortcuts(list( 1749 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); 1750 }); 1751 runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { 1752 assertTrue(mManager.setDynamicShortcuts(list( 1753 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); 1754 }); 1755 runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { 1756 assertTrue(mManager.setDynamicShortcuts(list())); 1757 }); 1758 runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { 1759 assertTrue(mManager.setDynamicShortcuts(list( 1760 makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); 1761 }); 1762 runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { 1763 assertTrue(mManager.setDynamicShortcuts(list( 1764 makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3")))); 1765 }); 1766 } 1767 1768 public static List<ShortcutInfo> assertAllHaveIconResId( 1769 List<ShortcutInfo> actualShortcuts) { 1770 for (ShortcutInfo s : actualShortcuts) { 1771 assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource()); 1772 assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile()); 1773 } 1774 return actualShortcuts; 1775 } 1776 1777 public static List<ShortcutInfo> assertAllHaveIconFile( 1778 List<ShortcutInfo> actualShortcuts) { 1779 for (ShortcutInfo s : actualShortcuts) { 1780 assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource()); 1781 assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile()); 1782 } 1783 return actualShortcuts; 1784 } 1785 1786 public static List<ShortcutInfo> assertAllHaveIcon( 1787 List<ShortcutInfo> actualShortcuts) { 1788 for (ShortcutInfo s : actualShortcuts) { 1789 assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); 1790 } 1791 return actualShortcuts; 1792 } 1793 1794 public static List<ShortcutInfo> assertAllStringsResolved( 1795 List<ShortcutInfo> actualShortcuts) { 1796 for (ShortcutInfo s : actualShortcuts) { 1797 assertTrue("ID " + s.getId(), s.hasStringResourcesResolved()); 1798 } 1799 return actualShortcuts; 1800 } 1801} 1802