/* * 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.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; import android.app.IUidObserver; import android.app.usage.UsageStatsManagerInternal; 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.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.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.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.BiPredicate; import java.util.function.Consumer; public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static final String TAG = "ShortcutManagerTest"; /** * 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 protected static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true protected static final String[] EMPTY_STRINGS = new String[0]; // Just for readability. // 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; } } /** 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 void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options, UserHandle userId) { } @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); mContext = context; } @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 PackageManagerInternal injectPackageManagerInternal() { return mMockPackageManagerInternal; } @Override boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { return mDefaultLauncherChecker.test(callingPackage, userId); } @Override PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, boolean getSignatures) { return getInjectedPackageInfo(packageName, userId, getSignatures); } @Override ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) { PackageInfo pi = injectPackageInfo(packageName, userId, /* getSignatures= */ false); return pi != null ? pi.applicationInfo : null; } @Override List injectInstalledPackages(@UserIdInt int userId) { return getInstalledPackages(userId); } @Override PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) { return mContext.injectGetActivitiesWithMetadata(packageName, userId); } @Override XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { return mContext.injectXmlMetaData(activityInfo, key); } @Override void injectPostToHandler(Runnable r) { final long token = mContext.injectClearCallingIdentity(); r.run(); mContext.injectRestoreCallingIdentity(token); } @Override void injectEnforceCallingPermission(String permission, String message) { if (!mCallerPermissions.contains(permission)) { throw new SecurityException("Missing permission: " + permission); } } @Override void wtf(String message, Exception e) { // During tests, WTF is fatal. fail(message + " exception: " + e); } } /** 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 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 shortcutInfoList) { // Note to simulate the binder RPC, we need to clone the incoming arguments. return super.addDynamicShortcuts(cloneShortcutList(shortcutInfoList)); } @Override public boolean updateShortcuts(List 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) { final long token = mContext.injectClearCallingIdentity(); r.run(); mContext.injectRestoreCallingIdentity(token); } @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 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, LauncherAppsTestable> mLauncherAppsMap = new HashMap<>(); protected LauncherAppsTestable mLauncherApps; // Current one protected File mInjectedFilePathRoot; protected long mInjectedCurrentTimeMillis; protected boolean mInjectedIsLowRamDevice; protected Locale mInjectedLocale = Locale.ENGLISH; protected int mInjectedCallingUid; protected String mInjectedClientPackage; protected Map mInjectedPackages; protected Set mUninstalledPackages; protected PackageManager mMockPackageManager; protected PackageManagerInternal mMockPackageManagerInternal; protected UserManager mMockUserManager; protected UsageStatsManagerInternal mMockUsageStatsManagerInternal; 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 mDefaultLauncherChecker = (callingPackage, userId) -> LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage) || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage); 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 mCallerPermissions = new ArrayList<>(); protected final HashMap> mActivityMetadataResId = new HashMap<>(); static { QUERY_ALL.setQueryFlags( ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED); } @Override protected void setUp() throws Exception { super.setUp(); mServiceContext = spy(new ServiceContext()); mClientContext = new ClientContext(); mMockPackageManager = mock(PackageManager.class); mMockPackageManagerInternal = mock(PackageManagerInternal.class); mMockUserManager = mock(UserManager.class); mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); LocalServices.addService(UsageStatsManagerInternal.class, mMockUsageStatsManagerInternal); // 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<>(); mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files"); deleteAllSavedFiles(); // Set up users. doAnswer(inv -> { assertSystem(); return USER_INFO_0; }).when(mMockUserManager).getUserInfo(eq(USER_0)); doAnswer(inv -> { assertSystem(); return USER_INFO_10; }).when(mMockUserManager).getUserInfo(eq(USER_10)); doAnswer(inv -> { assertSystem(); return USER_INFO_11; }).when(mMockUserManager).getUserInfo(eq(USER_11)); doAnswer(inv -> { assertSystem(); return USER_INFO_P0; }).when(mMockUserManager).getUserInfo(eq(USER_P0)); // User 0 is always running. when(mMockUserManager.isUserRunning(eq(USER_0))).thenAnswer(new AnswerIsUserRunning(true)); setUpAppResources(); // Start the service. initService(); setCaller(CALLING_PACKAGE_1); // In order to complicate the situation, we set mLocaleChangeSequenceNumber to 1 by // calling this. Running test with mLocaleChangeSequenceNumber == 0 might make us miss // some edge cases. mInternal.onSystemLocaleChangedNoLock(); } /** * Returns a boolean but also checks if the current UID is SYSTEM_UID. */ protected class AnswerIsUserRunning implements Answer { protected final boolean mAnswer; protected AnswerIsUserRunning(boolean answer) { mAnswer = answer; } @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { assertEquals("isUserRunning() must be called on SYSTEM UID.", Process.SYSTEM_UID, mInjectedCallingUid); return mAnswer; } } 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 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, Looper.getMainLooper()); 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); // Make sure a call to onSystemLocaleChangedNoLock() before PHASE_BOOT_COMPLETED will be // ignored. final long origSequenceNumber = mService.getLocaleChangeSequenceNumber(); mInternal.onSystemLocaleChangedNoLock(); assertEquals(origSequenceNumber, mService.getLocaleChangeSequenceNumber()); 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 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 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 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 (getSignatures) { ret.signatures = pi.signatures; } return ret; } protected void addApplicationInfo(PackageInfo pi, List list) { if (pi != null && pi.applicationInfo != null) { list.add(pi.applicationInfo); } } protected List getInstalledApplications(int userId) { final ArrayList 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 list) { if (pi != null) { list.add(pi); } } private List getInstalledPackages(int userId) { final ArrayList 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 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 activities = mActivityMetadataResId.get(packageName); if (activities != null) { final ArrayList 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)); 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 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 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; final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintWriter pw = new PrintWriter(out); mService.dumpInner(pw, null); pw.close(); Log.v(TAG, "Dumping ShortcutService: " + message); for (String line : out.toString().split("\n")) { Log.v(TAG, line); } } /** * 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); } protected void waitOnMainThread() throws Throwable { runTestOnUiThread(() -> {}); } /** * 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 makeShortcuts(String... ids) { final ArrayList 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 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) .setId(id) .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy")) .setTitle(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; } /** * 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 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 assertAllNotHaveIcon( List actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNull("ID " + s.getId(), s.getIcon()); } return actualShortcuts; } @NonNull protected List assertAllHaveFlags(@NonNull List 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 launchShortcutAndGetIntent( @NonNull String packageName, @NonNull String shortcutId, int userId) { reset(mServiceContext); assertTrue(mLauncherApps.startShortcut(packageName, shortcutId, null, null, UserHandle.of(userId))); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mServiceContext).startActivityAsUser( intentCaptor.capture(), any(Bundle.class), eq(UserHandle.of(userId))); return intentCaptor.getValue(); } protected Intent launchShortcutAndGetIntent_withShortcutInfo( @NonNull String packageName, @NonNull String shortcutId, int userId) { reset(mServiceContext); assertTrue(mLauncherApps.startShortcut( getShortcutInfoAsLauncher(packageName, shortcutId, userId), null, null)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mServiceContext).startActivityAsUser( intentCaptor.capture(), any(Bundle.class), eq(UserHandle.of(userId))); return intentCaptor.getValue(); } protected void assertShortcutLaunchable(@NonNull String packageName, @NonNull String shortcutId, int userId) { assertNotNull(launchShortcutAndGetIntent(packageName, shortcutId, userId)); assertNotNull(launchShortcutAndGetIntent_withShortcutInfo(packageName, shortcutId, userId)); } protected void assertShortcutNotLaunchable(@NonNull String packageName, @NonNull String shortcutId, int userId) { try { final boolean ok = mLauncherApps.startShortcut(packageName, shortcutId, null, null, UserHandle.of(userId)); if (!ok) { return; // didn't launch, okay. } fail(); } catch (SecurityException expected) { // security exception is okay too. } } protected void assertBitmapDirectories(int userId, String... expectedDirectories) { final Set expected = hashSet(set(expectedDirectories)); final Set 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 expected = hashSet(set(expectedFiles)); final Set 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(); } protected List getCallerShortcuts() { final ShortcutPackage p = mService.getPackageShortcutForTest( getCallingPackage(), getCallingUserId()); return p == null ? null : p.getAllShortcutsForTest(); } protected ShortcutInfo getCallerShortcut(String shortcutId) { return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId()); } protected List getLauncherShortcuts(String launcher, int userId, int queryFlags) { final List[] 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 getLauncherPinnedShortcuts(String launcher, int userId) { return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED); } protected ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId, int userId) { final List 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 pakcageName, int userId) { Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED); i.setData(Uri.parse("package:" + pakcageName)); i.putExtra(Intent.EXTRA_USER_HANDLE, userId); return i; } protected Intent genPackageDeleteIntent(String pakcageName, int userId) { 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) { 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 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 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_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED); 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 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() { 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); }); 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")))); }); } }