/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.cloneShortcutList; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.IUidObserver; import android.app.usage.UsageStatsManagerInternal; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.pm.ShortcutServiceInternal; import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.test.InstrumentationTestCase; import android.test.mock.MockContext; import android.util.Log; import android.util.Pair; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.LauncherAppsService.LauncherAppsImpl; import com.android.server.pm.ShortcutUser.PackageWithUser; import org.junit.Assert; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static final String TAG = "ShortcutManagerTest"; protected static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true /** * Whether to enable dump or not. Should be only true when debugging to avoid bugs where * dump affecting the behavior. */ protected static final boolean ENABLE_DUMP = false // DO NOT SUBMIT WITH true || DUMP_IN_TEARDOWN || ShortcutService.DEBUG; protected static final String[] EMPTY_STRINGS = new String[0]; // Just for readability. protected static final String MAIN_ACTIVITY_CLASS = "MainActivity"; // public for mockito public class BaseContext extends MockContext { @Override public Object getSystemService(String name) { switch (name) { case Context.USER_SERVICE: return mMockUserManager; } throw new UnsupportedOperationException(); } @Override public String getSystemServiceName(Class<?> serviceClass) { return getTestContext().getSystemServiceName(serviceClass); } @Override public PackageManager getPackageManager() { return mMockPackageManager; } @Override public Resources getResources() { return getTestContext().getResources(); } @Override public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) { // ignore. return null; } @Override public void unregisterReceiver(BroadcastReceiver receiver) { // ignore. } } /** Context used in the client side */ public class ClientContext extends BaseContext { @Override public String getPackageName() { return mInjectedClientPackage; } @Override public int getUserId() { return getCallingUserId(); } } /** Context used in the service side */ public class ServiceContext extends BaseContext { long injectClearCallingIdentity() { final int prevCallingUid = mInjectedCallingUid; mInjectedCallingUid = Process.SYSTEM_UID; return prevCallingUid; } void injectRestoreCallingIdentity(long token) { mInjectedCallingUid = (int) token; } @Override public int getUserId() { return UserHandle.USER_SYSTEM; } public PackageInfo injectGetActivitiesWithMetadata( String packageName, @UserIdInt int userId) { return BaseShortcutManagerTest.this.injectGetActivitiesWithMetadata(packageName, userId); } public XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { return BaseShortcutManagerTest.this.injectXmlMetaData(activityInfo, key); } } /** ShortcutService with injection override methods. */ protected final class ShortcutServiceTestable extends ShortcutService { final ServiceContext mContext; IUidObserver mUidObserver; public ShortcutServiceTestable(ServiceContext context, Looper looper) { super(context, looper, /* onyForPackageManagerApis */ false); mContext = context; } @Override public String injectGetLocaleTagsForUser(@UserIdInt int userId) { return mInjectedLocale.toLanguageTag(); } @Override boolean injectShouldPerformVerification() { return true; // Always verify during unit tests. } @Override String injectShortcutManagerConstants() { return ConfigConstants.KEY_RESET_INTERVAL_SEC + "=" + (INTERVAL / 1000) + "," + ConfigConstants.KEY_MAX_SHORTCUTS + "=" + MAX_SHORTCUTS + "," + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=" + MAX_UPDATES_PER_INTERVAL + "," + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=" + MAX_ICON_DIMENSION + "," + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=" + MAX_ICON_DIMENSION_LOWRAM + "," + ConfigConstants.KEY_ICON_FORMAT + "=PNG," + ConfigConstants.KEY_ICON_QUALITY + "=100"; } @Override long injectClearCallingIdentity() { return mContext.injectClearCallingIdentity(); } @Override void injectRestoreCallingIdentity(long token) { mContext.injectRestoreCallingIdentity(token); } @Override int injectDipToPixel(int dip) { return dip; } @Override long injectCurrentTimeMillis() { return mInjectedCurrentTimeMillis; } @Override long injectElapsedRealtime() { // TODO This should be kept separately from mInjectedCurrentTimeMillis, since // this should increase even if we rewind mInjectedCurrentTimeMillis in some tests. return mInjectedCurrentTimeMillis - START_TIME; } @Override int injectBinderCallingUid() { return mInjectedCallingUid; } @Override int injectGetPackageUid(String packageName, int userId) { return getInjectedPackageInfo(packageName, userId, false).applicationInfo.uid; } @Override File injectSystemDataPath() { return new File(mInjectedFilePathRoot, "system"); } @Override File injectUserDataPath(@UserIdInt int userId) { return new File(mInjectedFilePathRoot, "user-" + userId); } @Override void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { // Can't check } @Override boolean injectIsLowRamDevice() { return mInjectedIsLowRamDevice; } @Override void injectRegisterUidObserver(IUidObserver observer, int which) { mUidObserver = observer; } @Override boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { return mDefaultLauncherChecker.test(callingPackage, userId); } @Override PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, boolean getSignatures) { return getInjectedPackageInfo(packageName, userId, getSignatures); } @Override ApplicationInfo injectApplicationInfoWithUninstalled( String packageName, @UserIdInt int userId) { PackageInfo pi = injectPackageInfoWithUninstalled( packageName, userId, /* getSignatures= */ false); return pi != null ? pi.applicationInfo : null; } @Override List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId) { return BaseShortcutManagerTest.this.getInstalledPackagesWithUninstalled(userId); } @Override ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled(ComponentName activity, @UserIdInt int userId) { final PackageInfo pi = mContext.injectGetActivitiesWithMetadata( activity.getPackageName(), userId); if (pi == null || pi.activities == null) { return null; } for (ActivityInfo ai : pi.activities) { if (!mEnabledActivityChecker.test(ai.getComponentName(), userId)) { continue; } if (activity.equals(ai.getComponentName())) { return ai; } } return null; } @Override boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { if (!mEnabledActivityChecker.test(activity, userId)) { return false; } return mMainActivityChecker.test(activity, userId); } @Override List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) { final PackageInfo pi = mContext.injectGetActivitiesWithMetadata( packageName, userId); if (pi == null || pi.activities == null) { return null; } final ArrayList<ResolveInfo> ret = new ArrayList<>(pi.activities.length); for (int i = 0; i < pi.activities.length; i++) { if (!mEnabledActivityChecker.test(pi.activities[i].getComponentName(), userId)) { continue; } final ResolveInfo ri = new ResolveInfo(); ri.activityInfo = pi.activities[i]; ret.add(ri); } return ret; } @Override ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) { return mMainActivityFetcher.apply(packageName, userId); } @Override boolean injectIsActivityEnabledAndExported(ComponentName activity, @UserIdInt int userId) { return mEnabledActivityChecker.test(activity, userId); } @Override XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { return mContext.injectXmlMetaData(activityInfo, key); } @Override void injectPostToHandler(Runnable r) { runOnHandler(r); } @Override void injectRunOnNewThread(Runnable r) { runOnHandler(r); } @Override void injectEnforceCallingPermission(String permission, String message) { if (!mCallerPermissions.contains(permission)) { throw new SecurityException("Missing permission: " + permission); } } @Override boolean injectIsSafeModeEnabled() { return mSafeMode; } @Override String injectBuildFingerprint() { return mInjectedBuildFingerprint; } @Override void wtf(String message, Throwable th) { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); } } /** ShortcutManager with injection override methods. */ protected class ShortcutManagerTestable extends ShortcutManager { public ShortcutManagerTestable(Context context, ShortcutServiceTestable service) { super(context, service); } @Override protected int injectMyUserId() { return UserHandle.getUserId(mInjectedCallingUid); } @Override public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { // Note to simulate the binder RPC, we need to clone the incoming arguments. // Otherwise bad things will happen because they're mutable. return super.setDynamicShortcuts(cloneShortcutList(shortcutInfoList)); } @Override public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { // Note to simulate the binder RPC, we need to clone the incoming arguments. return super.addDynamicShortcuts(cloneShortcutList(shortcutInfoList)); } @Override public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) { // Note to simulate the binder RPC, we need to clone the incoming arguments. return super.updateShortcuts(cloneShortcutList(shortcutInfoList)); } } protected class LauncherAppImplTestable extends LauncherAppsImpl { final ServiceContext mContext; public LauncherAppImplTestable(ServiceContext context) { super(context); mContext = context; } @Override public void verifyCallingPackage(String callingPackage) { // SKIP } @Override void postToPackageMonitorHandler(Runnable r) { runOnHandler(r); } @Override int injectBinderCallingUid() { return mInjectedCallingUid; } @Override long injectClearCallingIdentity() { final int prevCallingUid = mInjectedCallingUid; mInjectedCallingUid = Process.SYSTEM_UID; return prevCallingUid; } @Override void injectRestoreCallingIdentity(long token) { mInjectedCallingUid = (int) token; } } protected class LauncherAppsTestable extends LauncherApps { public LauncherAppsTestable(Context context, ILauncherApps service) { super(context, service); } } public static class ShortcutActivity extends Activity { } public static class ShortcutActivity2 extends Activity { } public static class ShortcutActivity3 extends Activity { } protected Looper mLooper; protected Handler mHandler; protected ServiceContext mServiceContext; protected ClientContext mClientContext; protected ShortcutServiceTestable mService; protected ShortcutManagerTestable mManager; protected ShortcutServiceInternal mInternal; protected LauncherAppImplTestable mLauncherAppImpl; // LauncherApps has per-instace state, so we need a differnt instance for each launcher. protected final Map<Pair<Integer, String>, LauncherAppsTestable> mLauncherAppsMap = new HashMap<>(); protected LauncherAppsTestable mLauncherApps; // Current one protected File mInjectedFilePathRoot; protected boolean mSafeMode; protected long mInjectedCurrentTimeMillis; protected boolean mInjectedIsLowRamDevice; protected Locale mInjectedLocale = Locale.ENGLISH; protected int mInjectedCallingUid; protected String mInjectedClientPackage; protected Map<String, PackageInfo> mInjectedPackages; protected Set<PackageWithUser> mUninstalledPackages; protected Set<String> mSystemPackages; protected PackageManager mMockPackageManager; protected PackageManagerInternal mMockPackageManagerInternal; protected UserManager mMockUserManager; protected UsageStatsManagerInternal mMockUsageStatsManagerInternal; protected ActivityManagerInternal mMockActivityManagerInternal; protected static final String CALLING_PACKAGE_1 = "com.android.test.1"; protected static final int CALLING_UID_1 = 10001; protected static final String CALLING_PACKAGE_2 = "com.android.test.2"; protected static final int CALLING_UID_2 = 10002; protected static final String CALLING_PACKAGE_3 = "com.android.test.3"; protected static final int CALLING_UID_3 = 10003; protected static final String CALLING_PACKAGE_4 = "com.android.test.4"; protected static final int CALLING_UID_4 = 10004; protected static final String LAUNCHER_1 = "com.android.launcher.1"; protected static final int LAUNCHER_UID_1 = 10011; protected static final String LAUNCHER_2 = "com.android.launcher.2"; protected static final int LAUNCHER_UID_2 = 10012; protected static final String LAUNCHER_3 = "com.android.launcher.3"; protected static final int LAUNCHER_UID_3 = 10013; protected static final String LAUNCHER_4 = "com.android.launcher.4"; protected static final int LAUNCHER_UID_4 = 10014; protected static final int USER_0 = UserHandle.USER_SYSTEM; protected static final int USER_10 = 10; protected static final int USER_11 = 11; protected static final int USER_P0 = 20; // profile of user 0 protected static final UserHandle HANDLE_USER_0 = UserHandle.of(USER_0); protected static final UserHandle HANDLE_USER_10 = UserHandle.of(USER_10); protected static final UserHandle HANDLE_USER_11 = UserHandle.of(USER_11); protected static final UserHandle HANDLE_USER_P0 = UserHandle.of(USER_P0); protected static final UserInfo USER_INFO_0 = withProfileGroupId( new UserInfo(USER_0, "user0", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED), 10); protected static final UserInfo USER_INFO_10 = new UserInfo(USER_10, "user10", UserInfo.FLAG_INITIALIZED); protected static final UserInfo USER_INFO_11 = new UserInfo(USER_11, "user11", UserInfo.FLAG_INITIALIZED); protected static final UserInfo USER_INFO_P0 = withProfileGroupId( new UserInfo(USER_P0, "userP0", UserInfo.FLAG_MANAGED_PROFILE), 10); protected BiPredicate<String, Integer> mDefaultLauncherChecker = (callingPackage, userId) -> LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage) || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage); protected BiPredicate<ComponentName, Integer> mMainActivityChecker = (activity, userId) -> true; protected BiFunction<String, Integer, ComponentName> mMainActivityFetcher = (packageName, userId) -> new ComponentName(packageName, MAIN_ACTIVITY_CLASS); protected BiPredicate<ComponentName, Integer> mEnabledActivityChecker = (activity, userId) -> true; // all activities are enabled. protected static final long START_TIME = 1440000000101L; protected static final long INTERVAL = 10000; protected static final int MAX_SHORTCUTS = 10; protected static final int MAX_UPDATES_PER_INTERVAL = 3; protected static final int MAX_ICON_DIMENSION = 128; protected static final int MAX_ICON_DIMENSION_LOWRAM = 32; protected static final ShortcutQuery QUERY_ALL = new ShortcutQuery(); protected final ArrayList<String> mCallerPermissions = new ArrayList<>(); protected final HashMap<String, LinkedHashMap<ComponentName, Integer>> mActivityMetadataResId = new HashMap<>(); protected final Map<Integer, UserInfo> mUserInfos = new HashMap<>(); protected final Map<Integer, Boolean> mRunningUsers = new HashMap<>(); protected final Map<Integer, Boolean> mUnlockedUsers = new HashMap<>(); protected static final String PACKAGE_SYSTEM_LAUNCHER = "com.android.systemlauncher"; protected static final String PACKAGE_SYSTEM_LAUNCHER_NAME = "systemlauncher_name"; protected static final int PACKAGE_SYSTEM_LAUNCHER_PRIORITY = 0; protected static final String PACKAGE_FALLBACK_LAUNCHER = "com.android.settings"; protected static final String PACKAGE_FALLBACK_LAUNCHER_NAME = "fallback"; protected static final int PACKAGE_FALLBACK_LAUNCHER_PRIORITY = -999; protected String mInjectedBuildFingerprint = "build1"; static { QUERY_ALL.setQueryFlags( ShortcutQuery.FLAG_GET_ALL_KINDS); } @Override protected void setUp() throws Exception { super.setUp(); mLooper = Looper.getMainLooper(); mHandler = new Handler(mLooper); mServiceContext = spy(new ServiceContext()); mClientContext = new ClientContext(); mMockPackageManager = mock(PackageManager.class); mMockPackageManagerInternal = mock(PackageManagerInternal.class); mMockUserManager = mock(UserManager.class); mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class); mMockActivityManagerInternal = mock(ActivityManagerInternal.class); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); LocalServices.addService(UsageStatsManagerInternal.class, mMockUsageStatsManagerInternal); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mMockActivityManagerInternal); // Prepare injection values. mInjectedCurrentTimeMillis = START_TIME; mInjectedPackages = new HashMap<>(); addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1); addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2); addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3); addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10); addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4); addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5); addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6); addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10); // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target. updatePackageInfo(CALLING_PACKAGE_3, pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); updatePackageInfo(LAUNCHER_3, pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); mUninstalledPackages = new HashSet<>(); mSystemPackages = new HashSet<>(); mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files"); deleteAllSavedFiles(); // Set up users. when(mMockUserManager.getUserInfo(anyInt())).thenAnswer(new AnswerWithSystemCheck<>( inv -> mUserInfos.get((Integer) inv.getArguments()[0]))); mUserInfos.put(USER_0, USER_INFO_0); mUserInfos.put(USER_10, USER_INFO_10); mUserInfos.put(USER_11, USER_INFO_11); mUserInfos.put(USER_P0, USER_INFO_P0); // Set up isUserRunning and isUserUnlocked. when(mMockUserManager.isUserRunning(anyInt())).thenAnswer(new AnswerWithSystemCheck<>( inv -> b(mRunningUsers.get((Integer) inv.getArguments()[0])))); when(mMockUserManager.isUserUnlocked(anyInt())) .thenAnswer(new AnswerWithSystemCheck<>(inv -> { final int userId = (Integer) inv.getArguments()[0]; return b(mRunningUsers.get(userId)) && b(mUnlockedUsers.get(userId)); })); // isUserUnlockingOrUnlocked() return the same value as isUserUnlocked(). when(mMockUserManager.isUserUnlockingOrUnlocked(anyInt())) .thenAnswer(new AnswerWithSystemCheck<>(inv -> { final int userId = (Integer) inv.getArguments()[0]; return b(mRunningUsers.get(userId)) && b(mUnlockedUsers.get(userId)); })); when(mMockActivityManagerInternal.getUidProcessState(anyInt())).thenReturn( ActivityManager.PROCESS_STATE_CACHED_EMPTY); // User 0 and P0 are always running mRunningUsers.put(USER_0, true); mRunningUsers.put(USER_10, false); mRunningUsers.put(USER_11, false); mRunningUsers.put(USER_P0, true); // Unlock all users by default. mUnlockedUsers.put(USER_0, true); mUnlockedUsers.put(USER_10, true); mUnlockedUsers.put(USER_11, true); mUnlockedUsers.put(USER_P0, true); // Set up resources setUpAppResources(); // Start the service. initService(); setCaller(CALLING_PACKAGE_1); } private static boolean b(Boolean value) { return (value != null && value); } /** * Returns a boolean but also checks if the current UID is SYSTEM_UID. */ protected class AnswerWithSystemCheck<T> implements Answer<T> { private final Function<InvocationOnMock, T> mChecker; public AnswerWithSystemCheck(Function<InvocationOnMock, T> checker) { mChecker = checker; } @Override public T answer(InvocationOnMock invocation) throws Throwable { assertEquals("Must be called on SYSTEM UID.", Process.SYSTEM_UID, mInjectedCallingUid); return mChecker.apply(invocation); } } protected void setUpAppResources() throws Exception { setUpAppResources(/* offset = */ 0); } protected void setUpAppResources(int ressIdOffset) throws Exception { // ressIdOffset is used to adjust resource IDs to emulate the case where an updated app // has resource IDs changed. doAnswer(pmInvocation -> { assertEquals(Process.SYSTEM_UID, mInjectedCallingUid); final String packageName = (String) pmInvocation.getArguments()[0]; final int userId = (Integer) pmInvocation.getArguments()[1]; final Resources res = mock(Resources.class); doAnswer(resInvocation -> { final int argResId = (Integer) resInvocation.getArguments()[0]; return "string-" + packageName + "-user:" + userId + "-res:" + argResId + "/" + mInjectedLocale; }).when(res).getString(anyInt()); doAnswer(resInvocation -> { final int resId = (Integer) resInvocation.getArguments()[0]; // Always use the "string" resource type. The type doesn't matter during the test. return packageName + ":string/r" + resId; }).when(res).getResourceName(anyInt()); doAnswer(resInvocation -> { final String argResName = (String) resInvocation.getArguments()[0]; final String argType = (String) resInvocation.getArguments()[1]; final String argPackageName = (String) resInvocation.getArguments()[2]; // See the above code. getResourceName() will just use "r" + res ID as the entry // name. String entryName = argResName; if (entryName.contains("/")) { entryName = ShortcutInfo.getResourceEntryName(entryName); } return Integer.parseInt(entryName.substring(1)) + ressIdOffset; }).when(res).getIdentifier(anyString(), anyString(), anyString()); return res; }).when(mMockPackageManager).getResourcesForApplicationAsUser(anyString(), anyInt()); } protected static UserInfo withProfileGroupId(UserInfo in, int groupId) { in.profileGroupId = groupId; return in; } @Override protected void tearDown() throws Exception { if (DUMP_IN_TEARDOWN) dumpsysOnLogcat("Teardown"); shutdownServices(); super.tearDown(); } protected Context getTestContext() { return getInstrumentation().getContext(); } protected ShortcutManager getManager() { return mManager; } protected void deleteAllSavedFiles() { // Empty the data directory. if (mInjectedFilePathRoot.exists()) { Assert.assertTrue("failed to delete dir", FileUtils.deleteContents(mInjectedFilePathRoot)); } mInjectedFilePathRoot.mkdirs(); } /** (Re-) init the manager and the service. */ protected void initService() { shutdownServices(); LocalServices.removeServiceForTest(ShortcutServiceInternal.class); // Instantiate targets. mService = new ShortcutServiceTestable(mServiceContext, mLooper); mManager = new ShortcutManagerTestable(mClientContext, mService); mInternal = LocalServices.getService(ShortcutServiceInternal.class); mLauncherAppImpl = new LauncherAppImplTestable(mServiceContext); mLauncherApps = null; mLauncherAppsMap.clear(); // Send boot sequence events. mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY); mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); } protected void shutdownServices() { if (mService != null) { // Flush all the unsaved data from the previous instance. mService.saveDirtyInfo(); // Make sure everything is consistent. mService.verifyStates(); } LocalServices.removeServiceForTest(ShortcutServiceInternal.class); mService = null; mManager = null; mInternal = null; mLauncherAppImpl = null; mLauncherApps = null; mLauncherAppsMap.clear(); } protected void runOnHandler(Runnable r) { final long token = mServiceContext.injectClearCallingIdentity(); try { r.run(); } finally { mServiceContext.injectRestoreCallingIdentity(token); } } protected void addPackage(String packageName, int uid, int version) { addPackage(packageName, uid, version, packageName); } protected Signature[] genSignatures(String... signatures) { final Signature[] sigs = new Signature[signatures.length]; for (int i = 0; i < signatures.length; i++){ sigs[i] = new Signature(signatures[i].getBytes()); } return sigs; } protected PackageInfo genPackage(String packageName, int uid, int version, String... signatures) { final PackageInfo pi = new PackageInfo(); pi.packageName = packageName; pi.applicationInfo = new ApplicationInfo(); pi.applicationInfo.uid = uid; pi.applicationInfo.flags = ApplicationInfo.FLAG_INSTALLED | ApplicationInfo.FLAG_ALLOW_BACKUP; pi.versionCode = version; pi.applicationInfo.versionCode = version; pi.signatures = genSignatures(signatures); return pi; } protected void addPackage(String packageName, int uid, int version, String... signatures) { mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures)); } protected void updatePackageInfo(String packageName, Consumer<PackageInfo> c) { c.accept(mInjectedPackages.get(packageName)); } protected void updatePackageVersion(String packageName, int increment) { updatePackageInfo(packageName, pi -> { pi.versionCode += increment; pi.applicationInfo.versionCode += increment; }); } protected void updatePackageLastUpdateTime(String packageName, long increment) { updatePackageInfo(packageName, pi -> { pi.lastUpdateTime += increment; }); } protected void setPackageLastUpdateTime(String packageName, long value) { updatePackageInfo(packageName, pi -> { pi.lastUpdateTime = value; }); } protected void uninstallPackage(int userId, String packageName) { if (ENABLE_DUMP) { Log.v(TAG, "Unnstall package " + packageName + " / " + userId); } mUninstalledPackages.add(PackageWithUser.of(userId, packageName)); } protected void installPackage(int userId, String packageName) { if (ENABLE_DUMP) { Log.v(TAG, "Install package " + packageName + " / " + userId); } mUninstalledPackages.remove(PackageWithUser.of(userId, packageName)); } PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId, boolean getSignatures) { final PackageInfo pi = mInjectedPackages.get(packageName); if (pi == null) return null; final PackageInfo ret = new PackageInfo(); ret.packageName = pi.packageName; ret.versionCode = pi.versionCode; ret.lastUpdateTime = pi.lastUpdateTime; ret.applicationInfo = new ApplicationInfo(pi.applicationInfo); ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid); ret.applicationInfo.packageName = pi.packageName; if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) { ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } if (mSystemPackages.contains(packageName)) { ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } if (getSignatures) { ret.signatures = pi.signatures; } return ret; } protected void addApplicationInfo(PackageInfo pi, List<ApplicationInfo> list) { if (pi != null && pi.applicationInfo != null) { list.add(pi.applicationInfo); } } protected List<ApplicationInfo> getInstalledApplications(int userId) { final ArrayList<ApplicationInfo> ret = new ArrayList<>(); addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret); addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret); addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret); addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret); addApplicationInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret); addApplicationInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret); addApplicationInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret); addApplicationInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret); return ret; } private void addPackageInfo(PackageInfo pi, List<PackageInfo> list) { if (pi != null) { list.add(pi); } } private List<PackageInfo> getInstalledPackagesWithUninstalled(int userId) { final ArrayList<PackageInfo> ret = new ArrayList<>(); addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret); addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret); addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret); addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret); addPackageInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret); addPackageInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret); addPackageInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret); addPackageInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret); return ret; } protected void addManifestShortcutResource(ComponentName activity, int resId) { final String packageName = activity.getPackageName(); LinkedHashMap<ComponentName, Integer> map = mActivityMetadataResId.get(packageName); if (map == null) { map = new LinkedHashMap<>(); mActivityMetadataResId.put(packageName, map); } map.put(activity, resId); } protected PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) { final PackageInfo ret = getInjectedPackageInfo(packageName, userId, /* getSignatures=*/ false); final HashMap<ComponentName, Integer> activities = mActivityMetadataResId.get(packageName); if (activities != null) { final ArrayList<ActivityInfo> list = new ArrayList<>(); for (ComponentName cn : activities.keySet()) { ActivityInfo ai = new ActivityInfo(); ai.packageName = cn.getPackageName(); ai.name = cn.getClassName(); ai.metaData = new Bundle(); ai.metaData.putInt(ShortcutParser.METADATA_KEY, activities.get(cn)); ai.applicationInfo = ret.applicationInfo; list.add(ai); } ret.activities = list.toArray(new ActivityInfo[list.size()]); } return ret; } protected XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { if (!ShortcutParser.METADATA_KEY.equals(key) || activityInfo.metaData == null) { return null; } final int resId = activityInfo.metaData.getInt(key); return getTestContext().getResources().getXml(resId); } /** Replace the current calling package */ protected void setCaller(String packageName, int userId) { mInjectedClientPackage = packageName; mInjectedCallingUid = Preconditions.checkNotNull(getInjectedPackageInfo(packageName, userId, false), "Unknown package").applicationInfo.uid; // Set up LauncherApps for this caller. final Pair<Integer, String> key = Pair.create(userId, packageName); if (!mLauncherAppsMap.containsKey(key)) { mLauncherAppsMap.put(key, new LauncherAppsTestable(mClientContext, mLauncherAppImpl)); } mLauncherApps = mLauncherAppsMap.get(key); } protected void setCaller(String packageName) { setCaller(packageName, UserHandle.USER_SYSTEM); } protected String getCallingPackage() { return mInjectedClientPackage; } protected void setDefaultLauncherChecker(BiPredicate<String, Integer> p) { mDefaultLauncherChecker = p; } protected void runWithCaller(String packageName, int userId, Runnable r) { final String previousPackage = mInjectedClientPackage; final int previousUserId = UserHandle.getUserId(mInjectedCallingUid); setCaller(packageName, userId); r.run(); setCaller(previousPackage, previousUserId); } protected void runWithSystemUid(Runnable r) { final int origUid = mInjectedCallingUid; mInjectedCallingUid = Process.SYSTEM_UID; r.run(); mInjectedCallingUid = origUid; } protected void lookupAndFillInResourceNames(ShortcutInfo si) { runWithSystemUid(() -> si.lookupAndFillInResourceNames( mService.injectGetResourcesForApplicationAsUser(si.getPackage(), si.getUserId()))); } protected int getCallingUserId() { return UserHandle.getUserId(mInjectedCallingUid); } protected UserHandle getCallingUser() { return UserHandle.of(getCallingUserId()); } /** For debugging */ protected void dumpsysOnLogcat() { dumpsysOnLogcat(""); } protected void dumpsysOnLogcat(String message) { dumpsysOnLogcat(message, false); } protected void dumpsysOnLogcat(String message, boolean force) { if (force || !ENABLE_DUMP) return; Log.v(TAG, "Dumping ShortcutService: " + message); for (String line : dumpsys(null).split("\n")) { Log.v(TAG, line); } } protected String dumpCheckin() { return dumpsys(new String[]{"--checkin"}); } private String dumpsys(String[] args) { final ArrayList<String> origPermissions = new ArrayList<>(mCallerPermissions); mCallerPermissions.add(android.Manifest.permission.DUMP); try { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintWriter pw = new PrintWriter(out); mService.dump(/* fd */ null, pw, args); pw.close(); return out.toString(); } finally { mCallerPermissions.clear(); mCallerPermissions.addAll(origPermissions); } } /** * For debugging, dump arbitrary file on logcat. */ protected void dumpFileOnLogcat(String path) { dumpFileOnLogcat(path, ""); } protected void dumpFileOnLogcat(String path, String message) { if (!ENABLE_DUMP) return; Log.v(TAG, "Dumping file: " + path + " " + message); final StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new FileReader(path))) { String line; while ((line = br.readLine()) != null) { Log.v(TAG, line); } } catch (Exception e) { Log.e(TAG, "Couldn't read file", e); fail("Exception " + e); } } /** * For debugging, dump the main state file on logcat. */ protected void dumpBaseStateFile() { mService.saveDirtyInfo(); dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath() + "/system/" + ShortcutService.FILENAME_BASE_STATE); } /** * For debugging, dump per-user state file on logcat. */ protected void dumpUserFile(int userId) { dumpUserFile(userId, ""); } protected void dumpUserFile(int userId, String message) { mService.saveDirtyInfo(); dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath() + "/user-" + userId + "/" + ShortcutService.FILENAME_USER_PACKAGES, message); } /** * Make a shortcut with an ID. */ protected ShortcutInfo makeShortcut(String id) { return makeShortcut( id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } protected ShortcutInfo makeShortcutWithTitle(String id, String title) { return makeShortcut( id, title, /* activity =*/ null, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } /** * Make a shortcut with an ID and timestamp. */ protected ShortcutInfo makeShortcutWithTimestamp(String id, long timestamp) { final ShortcutInfo s = makeShortcut( id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); s.setTimestamp(timestamp); return s; } /** * Make a shortcut with an ID, a timestamp and an activity component */ protected ShortcutInfo makeShortcutWithTimestampWithActivity(String id, long timestamp, ComponentName activity) { final ShortcutInfo s = makeShortcut( id, "Title-" + id, activity, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); s.setTimestamp(timestamp); return s; } /** * Make a shortcut with an ID and icon. */ protected ShortcutInfo makeShortcutWithIcon(String id, Icon icon) { return makeShortcut( id, "Title-" + id, /* activity =*/ null, icon, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } protected ShortcutInfo makePackageShortcut(String packageName, String id) { String origCaller = getCallingPackage(); setCaller(packageName); ShortcutInfo s = makeShortcut( id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); setCaller(origCaller); // restore the caller return s; } /** * Make multiple shortcuts with IDs. */ protected List<ShortcutInfo> makeShortcuts(String... ids) { final ArrayList<ShortcutInfo> ret = new ArrayList(); for (String id : ids) { ret.add(makeShortcut(id)); } return ret; } protected ShortcutInfo.Builder makeShortcutBuilder() { return new ShortcutInfo.Builder(mClientContext); } protected ShortcutInfo makeShortcutWithActivity(String id, ComponentName activity) { return makeShortcut( id, "Title-" + id, activity, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } protected ShortcutInfo makeShortcutWithIntent(String id, Intent intent) { return makeShortcut( id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, intent, /* rank =*/ 0); } protected ShortcutInfo makeShortcutWithActivityAndTitle(String id, ComponentName activity, String title) { return makeShortcut( id, title, activity, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } protected ShortcutInfo makeShortcutWithActivityAndRank(String id, ComponentName activity, int rank) { return makeShortcut( id, "Title-" + id, activity, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank); } /** * Make a shortcut with details. */ protected ShortcutInfo makeShortcut(String id, String title, ComponentName activity, Icon icon, Intent intent, int rank) { final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy")) .setShortLabel(title) .setRank(rank) .setIntent(intent); if (icon != null) { b.setIcon(icon); } if (activity != null) { b.setActivity(activity); } final ShortcutInfo s = b.build(); s.setTimestamp(mInjectedCurrentTimeMillis); // HACK return s; } protected ShortcutInfo makeShortcutWithIntents(String id, Intent... intents) { return makeShortcut( id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, intents, /* rank =*/ 0); } /** * Make a shortcut with details. */ protected ShortcutInfo makeShortcut(String id, String title, ComponentName activity, Icon icon, Intent[] intents, int rank) { final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy")) .setShortLabel(title) .setRank(rank) .setIntents(intents); if (icon != null) { b.setIcon(icon); } if (activity != null) { b.setActivity(activity); } final ShortcutInfo s = b.build(); s.setTimestamp(mInjectedCurrentTimeMillis); // HACK return s; } /** * Make a shortcut with details. */ protected ShortcutInfo makeShortcutWithExtras(String id, Intent intent, PersistableBundle extras) { final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy")) .setShortLabel("title-" + id) .setExtras(extras) .setIntent(intent); final ShortcutInfo s = b.build(); s.setTimestamp(mInjectedCurrentTimeMillis); // HACK return s; } /** * Make an intent. */ protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) { final Intent intent = new Intent(action); intent.setComponent(makeComponent(clazz)); intent.replaceExtras(makeBundle(bundleKeysAndValues)); return intent; } /** * Make an component name, with the client context. */ @NonNull protected ComponentName makeComponent(Class<?> clazz) { return new ComponentName(mClientContext, clazz); } @NonNull protected ShortcutInfo findById(List<ShortcutInfo> list, String id) { for (ShortcutInfo s : list) { if (s.getId().equals(id)) { return s; } } fail("Shortcut with id " + id + " not found"); return null; } protected void assertSystem() { assertEquals("Caller must be system", Process.SYSTEM_UID, mInjectedCallingUid); } protected void assertResetTimes(long expectedLastResetTime, long expectedNextResetTime) { assertEquals(expectedLastResetTime, mService.getLastResetTimeLocked()); assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked()); } public static List<ShortcutInfo> assertAllNotHaveIcon( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNull("ID " + s.getId(), s.getIcon()); } return actualShortcuts; } @NonNull protected List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts, int shortcutFlags) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId() + " doesn't have flags " + shortcutFlags, s.hasFlags(shortcutFlags)); } return actualShortcuts; } protected ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) { return mService.getPackageShortcutForTest(packageName, shortcutId, userId); } protected void assertShortcutExists(String packageName, String shortcutId, int userId) { assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null); } protected void assertShortcutNotExists(String packageName, String shortcutId, int userId) { assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null); } protected Intent[] launchShortcutAndGetIntentsInner(Runnable shortcutStarter, @NonNull String packageName, @NonNull String shortcutId, int userId) { reset(mMockActivityManagerInternal); shortcutStarter.run(); final ArgumentCaptor<Intent[]> intentsCaptor = ArgumentCaptor.forClass(Intent[].class); verify(mMockActivityManagerInternal).startActivitiesAsPackage( eq(packageName), eq(userId), intentsCaptor.capture(), any(Bundle.class)); return intentsCaptor.getValue(); } protected Intent[] launchShortcutAndGetIntents( @NonNull String packageName, @NonNull String shortcutId, int userId) { return launchShortcutAndGetIntentsInner( () -> { mLauncherApps.startShortcut(packageName, shortcutId, null, null, UserHandle.of(userId)); }, packageName, shortcutId, userId ); } protected Intent launchShortcutAndGetIntent( @NonNull String packageName, @NonNull String shortcutId, int userId) { final Intent[] intents = launchShortcutAndGetIntents(packageName, shortcutId, userId); assertEquals(1, intents.length); return intents[0]; } protected Intent[] launchShortcutAndGetIntents_withShortcutInfo( @NonNull String packageName, @NonNull String shortcutId, int userId) { return launchShortcutAndGetIntentsInner( () -> { mLauncherApps.startShortcut( getShortcutInfoAsLauncher(packageName, shortcutId, userId), null, null); }, packageName, shortcutId, userId ); } protected Intent launchShortcutAndGetIntent_withShortcutInfo( @NonNull String packageName, @NonNull String shortcutId, int userId) { final Intent[] intents = launchShortcutAndGetIntents_withShortcutInfo( packageName, shortcutId, userId); assertEquals(1, intents.length); return intents[0]; } protected void assertShortcutLaunchable(@NonNull String packageName, @NonNull String shortcutId, int userId) { assertNotNull(launchShortcutAndGetIntent(packageName, shortcutId, userId)); assertNotNull(launchShortcutAndGetIntent_withShortcutInfo(packageName, shortcutId, userId)); } protected void assertShortcutNotLaunched(@NonNull String packageName, @NonNull String shortcutId, int userId) { reset(mMockActivityManagerInternal); try { mLauncherApps.startShortcut(packageName, shortcutId, null, null, UserHandle.of(userId)); fail("ActivityNotFoundException was not thrown"); } catch (ActivityNotFoundException expected) { } // This shouldn't have been called. verify(mMockActivityManagerInternal, times(0)).startActivitiesAsPackage( anyString(), anyInt(), any(Intent[].class), any(Bundle.class)); } protected void assertStartShortcutThrowsException(@NonNull String packageName, @NonNull String shortcutId, int userId, Class<?> expectedException) { Exception thrown = null; try { mLauncherApps.startShortcut(packageName, shortcutId, null, null, UserHandle.of(userId)); } catch (Exception e) { thrown = e; } assertNotNull("Exception was not thrown", thrown); assertEquals("Exception type different", expectedException, thrown.getClass()); } protected void assertBitmapDirectories(int userId, String... expectedDirectories) { final Set<String> expected = hashSet(set(expectedDirectories)); final Set<String> actual = new HashSet<>(); final File[] files = mService.getUserBitmapFilePath(userId).listFiles(); if (files != null) { for (File child : files) { if (child.isDirectory()) { actual.add(child.getName()); } } } assertEquals(expected, actual); } protected void assertBitmapFiles(int userId, String packageName, String... expectedFiles) { final Set<String> expected = hashSet(set(expectedFiles)); final Set<String> actual = new HashSet<>(); final File[] files = new File(mService.getUserBitmapFilePath(userId), packageName) .listFiles(); if (files != null) { for (File child : files) { if (child.isFile()) { actual.add(child.getName()); } } } assertEquals(expected, actual); } protected String getBitmapFilename(int userId, String packageName, String shortcutId) { final ShortcutInfo si = mService.getPackageShortcutForTest(packageName, shortcutId, userId); if (si == null) { return null; } return new File(si.getBitmapPath()).getName(); } /** * @return all shortcuts stored internally for the caller. This reflects the *internal* view * of shortcuts, which may be different from what {@link #getCallerVisibleShortcuts} would * return, because getCallerVisibleShortcuts() will get shortcuts from the proper "front door" * which performs some extra checks, like {@link ShortcutPackage#onRestored}. */ protected List<ShortcutInfo> getCallerShortcuts() { final ShortcutPackage p = mService.getPackageShortcutForTest( getCallingPackage(), getCallingUserId()); return p == null ? null : p.getAllShortcutsForTest(); } /** * @return all shortcuts owned by caller that are actually visible via ShortcutManager. * See also {@link #getCallerShortcuts}. */ protected List<ShortcutInfo> getCallerVisibleShortcuts() { final ArrayList<ShortcutInfo> ret = new ArrayList<>(); ret.addAll(mManager.getDynamicShortcuts()); ret.addAll(mManager.getPinnedShortcuts()); ret.addAll(mManager.getManifestShortcuts()); return ret; } protected ShortcutInfo getCallerShortcut(String shortcutId) { return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId()); } protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) { final List<ShortcutInfo>[] ret = new List[1]; runWithCaller(launcher, userId, () -> { final ShortcutQuery q = new ShortcutQuery(); q.setQueryFlags(queryFlags); ret[0] = mLauncherApps.getShortcuts(q, UserHandle.of(userId)); }); return ret[0]; } protected List<ShortcutInfo> getLauncherPinnedShortcuts(String launcher, int userId) { return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED); } protected ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId, int userId) { final List<ShortcutInfo> infoList = mLauncherApps.getShortcutInfo(packageName, list(shortcutId), UserHandle.of(userId)); assertEquals("No shortcutInfo found (or too many of them)", 1, infoList.size()); return infoList.get(0); } protected Intent genPackageAddIntent(String packageName, int userId) { installPackage(userId, packageName); Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED); i.setData(Uri.parse("package:" + packageName)); i.putExtra(Intent.EXTRA_USER_HANDLE, userId); return i; } protected Intent genPackageDeleteIntent(String pakcageName, int userId) { uninstallPackage(userId, pakcageName); Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED); i.setData(Uri.parse("package:" + pakcageName)); i.putExtra(Intent.EXTRA_USER_HANDLE, userId); return i; } protected Intent genPackageUpdateIntent(String pakcageName, int userId) { installPackage(userId, pakcageName); Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED); i.setData(Uri.parse("package:" + pakcageName)); i.putExtra(Intent.EXTRA_USER_HANDLE, userId); i.putExtra(Intent.EXTRA_REPLACING, true); return i; } protected Intent genPackageChangedIntent(String pakcageName, int userId) { Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED); i.setData(Uri.parse("package:" + pakcageName)); i.putExtra(Intent.EXTRA_USER_HANDLE, userId); return i; } protected Intent genPackageDataClear(String packageName, int userId) { Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED); i.setData(Uri.parse("package:" + packageName)); i.putExtra(Intent.EXTRA_USER_HANDLE, userId); return i; } protected void assertExistsAndShadow(ShortcutPackageItem spi) { assertNotNull(spi); assertTrue(spi.getPackageInfo().isShadow()); } protected File makeFile(File baseDirectory, String... paths) { File ret = baseDirectory; for (String path : paths) { ret = new File(ret, path); } return ret; } protected boolean bitmapDirectoryExists(String packageName, int userId) { final File path = new File(mService.getUserBitmapFilePath(userId), packageName); return path.isDirectory(); } protected static ShortcutQuery buildQuery(long changedSince, String packageName, ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) { return buildQuery(changedSince, packageName, null, componentName, flags); } protected static ShortcutQuery buildQuery(long changedSince, String packageName, List<String> shortcutIds, ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) { final ShortcutQuery q = new ShortcutQuery(); q.setChangedSince(changedSince); q.setPackage(packageName); q.setShortcutIds(shortcutIds); q.setActivity(componentName); q.setQueryFlags(flags); return q; } protected static ShortcutQuery buildAllQuery(String packageName) { final ShortcutQuery q = new ShortcutQuery(); q.setPackage(packageName); q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS); return q; } protected static ShortcutQuery buildPinnedQuery(String packageName) { final ShortcutQuery q = new ShortcutQuery(); q.setPackage(packageName); q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED); return q; } protected static ShortcutQuery buildQueryWithFlags(int queryFlags) { final ShortcutQuery q = new ShortcutQuery(); q.setQueryFlags(queryFlags); return q; } protected void backupAndRestore() { int prevUid = mInjectedCallingUid; mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it. dumpsysOnLogcat("Before backup"); final byte[] payload = mService.getBackupPayload(USER_0); if (ENABLE_DUMP) { final String xml = new String(payload); Log.v(TAG, "Backup payload:"); for (String line : xml.split("\n")) { Log.v(TAG, line); } } // Before doing anything else, uninstall all packages. for (int userId : list(USER_0, USER_P0)) { for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3, LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) { uninstallPackage(userId, pkg); } } shutdownServices(); deleteAllSavedFiles(); initService(); mService.applyRestore(payload, USER_0); // handleUnlockUser will perform the gone package check, but it shouldn't remove // shadow information. mService.handleUnlockUser(USER_0); dumpsysOnLogcat("After restore"); mInjectedCallingUid = prevUid; } protected void prepareCrossProfileDataSet() { mRunningUsers.put(USER_10, true); // this test needs user 10. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); }); runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); }); runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); }); runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list())); }); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); }); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"), makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6")))); }); runWithCaller(LAUNCHER_1, USER_0, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0); }); runWithCaller(LAUNCHER_2, USER_0, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0); }); // Note LAUNCHER_3 has allowBackup=false. runWithCaller(LAUNCHER_3, USER_0, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0); }); runWithCaller(LAUNCHER_4, USER_0, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0); }); // Launcher on a managed profile is referring ot user 0! runWithCaller(LAUNCHER_1, USER_P0, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"), HANDLE_USER_0); mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0); }); runWithCaller(LAUNCHER_1, USER_10, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10); mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10); mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"), HANDLE_USER_10); }); // Then remove some dynamic shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); }); runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); }); runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); }); runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list())); }); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); }); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3")))); }); } public static List<ShortcutInfo> assertAllHaveIconResId( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource()); assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllHaveIconFile( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource()); assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllHaveIcon( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); } return actualShortcuts; } public static List<ShortcutInfo> assertAllStringsResolved( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertTrue("ID " + s.getId(), s.hasStringResourcesResolved()); } return actualShortcuts; } public String readTestAsset(String assetPath) throws IOException { final StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader( new InputStreamReader( getTestContext().getResources().getAssets().open(assetPath)))) { String line; while ((line = br.readLine()) != null) { sb.append(line); sb.append(System.lineSeparator()); } } return sb.toString(); } protected void prepareGetHomeActivitiesAsUser(ComponentName preferred, List<ResolveInfo> candidates, int userId) { doAnswer(inv -> { ((List) inv.getArguments()[0]).addAll(candidates); return preferred; }).when(mMockPackageManagerInternal).getHomeActivitiesAsUser(any(List.class), eq(userId)); } protected static ComponentName cn(String packageName, String name) { return new ComponentName(packageName, name); } protected static ResolveInfo ri(String packageName, String name, boolean isSystem, int priority) { final ResolveInfo ri = new ResolveInfo(); ri.activityInfo = new ActivityInfo(); ri.activityInfo.applicationInfo = new ApplicationInfo(); ri.activityInfo.packageName = packageName; ri.activityInfo.name = name; if (isSystem) { ri.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } ri.priority = priority; return ri; } protected static ResolveInfo getSystemLauncher() { return ri(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME, true, PACKAGE_SYSTEM_LAUNCHER_PRIORITY); } protected static ResolveInfo getFallbackLauncher() { return ri(PACKAGE_FALLBACK_LAUNCHER, PACKAGE_FALLBACK_LAUNCHER_NAME, true, PACKAGE_FALLBACK_LAUNCHER_PRIORITY); } }