From 4dbe0ded4ae9faaef580be80184fca0749e27198 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Mon, 14 Mar 2016 17:31:49 -0700 Subject: [PATCH] Fix launcher side callback - Fix crash in work profile case - Calculate shortcuts for each caller - Add tests Bug 27548047 Change-Id: I1a5b6a96956abac75d01eb5cc1cec9a9e8e379a3 --- .../android/content/pm/ILauncherApps.aidl | 2 +- .../java/android/content/pm/LauncherApps.java | 3 +- .../content/pm/ShortcutServiceInternal.java | 3 +- .../server/pm/LauncherAppsService.java | 144 +++++++---- .../android/server/pm/ShortcutService.java | 39 ++- .../server/pm/ShortcutManagerTest.java | 244 ++++++++++++++++-- 6 files changed, 340 insertions(+), 95 deletions(-) diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 7b578728b104b..6b3d4f1326ead 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -34,7 +34,7 @@ import java.util.List; * {@hide} */ interface ILauncherApps { - void addOnAppsChangedListener(in IOnAppsChangedListener listener); + void addOnAppsChangedListener(String callingPackage, in IOnAppsChangedListener listener); void removeOnAppsChangedListener(in IOnAppsChangedListener listener); ParceledListSlice getLauncherActivities(String packageName, in UserHandle user); ResolveInfo resolveActivity(in Intent intent, in UserHandle user); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 8d43c44ea4d5c..8724d5ece5cae 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -565,7 +565,8 @@ public class LauncherApps { addCallbackLocked(callback, handler); if (addedFirstCallback) { try { - mService.addOnAppsChangedListener(mAppsChangedListener); + mService.addOnAppsChangedListener(mContext.getPackageName(), + mAppsChangedListener); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 7c764aa7d0ffc..4255582f7976d 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -36,8 +36,7 @@ import java.util.List; */ public abstract class ShortcutServiceInternal { public interface ShortcutChangeListener { - void onShortcutChanged(@NonNull String packageName, - @NonNull List shortcuts, @UserIdInt int userId); + void onShortcutChanged(@NonNull String packageName, @UserIdInt int userId); } public abstract List diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 6cc0544afb215..5b0ceca4cb9d1 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -28,6 +28,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; import android.content.pm.IPackageManager; +import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -74,9 +75,20 @@ public class LauncherAppsService extends SystemService { @Override public void onStart() { + Binder.LOG_RUNTIME_EXCEPTION = true; publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl); } + static class BroadcastCookie { + public final UserHandle user; + public final String packageName; + + BroadcastCookie(UserHandle userHandle, String packageName) { + this.user = userHandle; + this.packageName = packageName; + } + } + @VisibleForTesting static class LauncherAppsImpl extends ILauncherApps.Stub { private static final boolean DEBUG = false; @@ -113,7 +125,8 @@ public class LauncherAppsService extends SystemService { * android.content.pm.IOnAppsChangedListener) */ @Override - public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException { + public void addOnAppsChangedListener(String callingPackage, IOnAppsChangedListener listener) + throws RemoteException { synchronized (mListeners) { if (DEBUG) { Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle()); @@ -125,7 +138,8 @@ public class LauncherAppsService extends SystemService { startWatchingPackageBroadcasts(); } mListeners.unregister(listener); - mListeners.register(listener, Binder.getCallingUserHandle()); + mListeners.register(listener, new BroadcastCookie(UserHandle.of(getCallingUserId()), + callingPackage)); } } @@ -490,41 +504,44 @@ public class LauncherAppsService extends SystemService { } } - - private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener { - - /** Checks if user is a profile of or same as listeningUser. - * and the user is enabled. */ - private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, - String debugMsg) { - if (user.getIdentifier() == listeningUser.getIdentifier()) { - if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg); + /** Checks if user is a profile of or same as listeningUser. + * and the user is enabled. */ + boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, + String debugMsg) { + if (user.getIdentifier() == listeningUser.getIdentifier()) { + if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg); + return true; + } + long ident = Binder.clearCallingIdentity(); + try { + UserInfo userInfo = mUm.getUserInfo(user.getIdentifier()); + UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier()); + if (userInfo == null || listeningUserInfo == null + || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID + || userInfo.profileGroupId != listeningUserInfo.profileGroupId + || !userInfo.isEnabled()) { + if (DEBUG) { + Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":" + + debugMsg); + } + return false; + } else { + if (DEBUG) { + Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":" + + debugMsg); + } return true; } - long ident = Binder.clearCallingIdentity(); - try { - UserInfo userInfo = mUm.getUserInfo(user.getIdentifier()); - UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier()); - if (userInfo == null || listeningUserInfo == null - || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID - || userInfo.profileGroupId != listeningUserInfo.profileGroupId - || !userInfo.isEnabled()) { - if (DEBUG) { - Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":" - + debugMsg); - } - return false; - } else { - if (DEBUG) { - Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":" - + debugMsg); - } - return true; - } - } finally { - Binder.restoreCallingIdentity(ident); - } + } finally { + Binder.restoreCallingIdentity(ident); } + } + + void postToPackageMonitor(Runnable r) { + mPackageMonitor.getRegisteredHandler().post(r); + } + + private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener { // TODO Simplify with lambdas. @@ -534,8 +551,8 @@ public class LauncherAppsService extends SystemService { final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue; + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onPackageAdded")) continue; try { listener.onPackageAdded(user, packageName); } catch (RemoteException re) { @@ -553,8 +570,8 @@ public class LauncherAppsService extends SystemService { final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue; + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onPackageRemoved")) continue; try { listener.onPackageRemoved(user, packageName); } catch (RemoteException re) { @@ -572,8 +589,8 @@ public class LauncherAppsService extends SystemService { final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue; + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onPackageModified")) continue; try { listener.onPackageChanged(user, packageName); } catch (RemoteException re) { @@ -591,8 +608,8 @@ public class LauncherAppsService extends SystemService { final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue; + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onPackagesAvailable")) continue; try { listener.onPackagesAvailable(user, packages, isReplacing()); } catch (RemoteException re) { @@ -610,8 +627,8 @@ public class LauncherAppsService extends SystemService { final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue; + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onPackagesUnavailable")) continue; try { listener.onPackagesUnavailable(user, packages, isReplacing()); } catch (RemoteException re) { @@ -629,8 +646,8 @@ public class LauncherAppsService extends SystemService { final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackagesSuspended")) continue; + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onPackagesSuspended")) continue; try { listener.onPackagesSuspended(user, packages); } catch (RemoteException re) { @@ -648,8 +665,8 @@ public class LauncherAppsService extends SystemService { final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnsuspended")) continue; + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onPackagesUnsuspended")) continue; try { listener.onPackagesUnsuspended(user, packages); } catch (RemoteException re) { @@ -663,20 +680,39 @@ public class LauncherAppsService extends SystemService { @Override public void onShortcutChanged(@NonNull String packageName, - @NonNull List shortcuts, @UserIdInt int userId) { + @UserIdInt int userId) { + postToPackageMonitor(() -> onShortcutChangedInner(packageName, userId)); + } + + private void onShortcutChangedInner(@NonNull String packageName, + @UserIdInt int userId) { final UserHandle user = UserHandle.of(userId); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(user, listeningUser, "onShortcutChanged")) continue; - - // STOPSHIP Skip if the receiver doesn't have the permission. + BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, cookie.user, "onShortcutChanged")) continue; + // Make sure the caller has the permission. + if (!mShortcutServiceInternal.hasShortcutHostPermission(cookie.packageName, + cookie.user.getIdentifier())) { + continue; + } + // Each launcher has a different set of pinned shortcuts, so we need to do a + // query in here. + // (As of now, only one launcher has the permission at a time, so it's bit + // moot, but we may change the permission model eventually.) + final List list = + mShortcutServiceInternal.getShortcuts(cookie.packageName, + /* changedSince= */ 0, packageName, /* component= */ null, + ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY + | ShortcutQuery.FLAG_GET_PINNED + | ShortcutQuery.FLAG_GET_DYNAMIC + , userId); try { listener.onShortcutChanged(user, packageName, - new ParceledListSlice<>(shortcuts)); + new ParceledListSlice<>(list)); } catch (RemoteException re) { Slog.d(TAG, "Callback failed ", re); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 3791eb05ca823..06417494efddd 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -814,7 +814,7 @@ public class ShortcutService extends IShortcutService.Stub { return; } - final long token = Binder.clearCallingIdentity(); + final long token = injectClearCallingIdentity(); try { // Clear icon info on the shortcut. shortcut.setIconResourceId(0); @@ -891,7 +891,7 @@ public class ShortcutService extends IShortcutService.Stub { shortcut.clearIcon(); } } finally { - Binder.restoreCallingIdentity(token); + injectRestoreCallingIdentity(token); } } @@ -992,6 +992,10 @@ public class ShortcutService extends IShortcutService.Stub { } } + void postToHandler(Runnable r) { + mHandler.post(r); + } + /** * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}. */ @@ -1011,17 +1015,16 @@ public class ShortcutService extends IShortcutService.Stub { } private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { - final ArrayList copy; - final List shortcuts = new ArrayList<>(); - synchronized (mLock) { - copy = new ArrayList<>(mListeners); - - getPackageShortcutsLocked(packageName, userId) - .findAll(shortcuts, /* query =*/ null, ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); - } - for (int i = copy.size() - 1; i >= 0; i--) { - copy.get(i).onShortcutChanged(packageName, shortcuts, userId); - } + postToHandler(() -> { + final ArrayList copy; + synchronized (mLock) { + copy = new ArrayList<>(mListeners); + } + // Note onShortcutChanged() needs to be called with the system service permissions. + for (int i = copy.size() - 1; i >= 0; i--) { + copy.get(i).onShortcutChanged(packageName, userId); + } + }); } /** @@ -1799,6 +1802,16 @@ public class ShortcutService extends IShortcutService.Stub { return UserHandle.getUserId(injectBinderCallingUid()); } + // Injection point. + long injectClearCallingIdentity() { + return Binder.clearCallingIdentity(); + } + + // Injection point. + void injectRestoreCallingIdentity(long token) { + Binder.restoreCallingIdentity(token); + } + File injectSystemDataPath() { return Environment.getDataSystemDirectory(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index f978d843e6be6..6bcaf4da05b1d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -15,15 +15,22 @@ */ package com.android.server.pm; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ILauncherApps; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; @@ -40,10 +47,13 @@ import android.graphics.drawable.Icon; import android.os.BaseBundle; import android.os.Bundle; import android.os.FileUtils; +import android.os.Handler; +import android.os.Looper; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import android.test.AndroidTestCase; +import android.test.InstrumentationTestCase; import android.test.mock.MockContext; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; @@ -59,6 +69,7 @@ import com.android.server.pm.ShortcutService.FileOutputStreamWithPath; import libcore.io.IoUtils; import org.junit.Assert; +import org.mockito.ArgumentCaptor; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -92,7 +103,7 @@ import java.util.Set; * */ @SmallTest -public class ShortcutManagerTest extends AndroidTestCase { +public class ShortcutManagerTest extends InstrumentationTestCase { private static final String TAG = "ShortcutManagerTest"; /** @@ -118,7 +129,14 @@ public class ShortcutManagerTest extends AndroidTestCase { @Override public Resources getResources() { - return ShortcutManagerTest.this.getContext().getResources(); + return getTestContext().getResources(); + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + // ignore. + return null; } } @@ -132,13 +150,24 @@ public class ShortcutManagerTest extends AndroidTestCase { /** Context used in the service side */ private final class ServiceContext extends BaseContext { + long injectClearCallingIdentity() { + final int prevCallingUid = mInjectedCallingUid; + mInjectedCallingUid = Process.SYSTEM_UID; + return prevCallingUid; + } + + void injectRestoreCallingIdentity(long token) { + mInjectedCallingUid = (int) token; + } } /** ShortcutService with injection override methods. */ private final class ShortcutServiceTestable extends ShortcutService { - public ShortcutServiceTestable(Context context) { - super(context); + final ServiceContext mContext; + public ShortcutServiceTestable(ServiceContext context) { + super(context); + mContext = context; } @Override @@ -153,6 +182,16 @@ public class ShortcutManagerTest extends AndroidTestCase { + 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; @@ -204,6 +243,13 @@ public class ShortcutManagerTest extends AndroidTestCase { // Sort of hack; do a simpler check. return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage); } + + @Override + void postToHandler(Runnable r) { + final long token = mContext.injectClearCallingIdentity(); + r.run(); + mContext.injectRestoreCallingIdentity(token); + } } /** ShortcutManager with injection override methods. */ @@ -219,12 +265,20 @@ public class ShortcutManagerTest extends AndroidTestCase { } private class LauncherAppImplTestable extends LauncherAppsImpl { - public LauncherAppImplTestable(Context context) { + final ServiceContext mContext; + + public LauncherAppImplTestable(ServiceContext context) { super(context); + mContext = context; } @Override public void ensureInUserProfiles(UserHandle userToCheck, String message) { + if (getCallingUserId() == userToCheck.getIdentifier()) { + return; // okay + } + + assertEquals(Process.SYSTEM_UID, mInjectedCallingUid); // SKIP } @@ -232,6 +286,20 @@ public class ShortcutManagerTest extends AndroidTestCase { public void verifyCallingPackage(String callingPackage) { // SKIP } + + @Override + boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, String debugMsg) { + // This requires CROSS_USER + assertEquals(Process.SYSTEM_UID, mInjectedCallingUid); + return user.getIdentifier() == listeningUser.getIdentifier(); + } + + @Override + void postToPackageMonitor(Runnable r) { + final long token = mContext.injectClearCallingIdentity(); + r.run(); + mContext.injectRestoreCallingIdentity(token); + } } private class LauncherAppsTestable extends LauncherApps { @@ -289,6 +357,7 @@ public class ShortcutManagerTest extends AndroidTestCase { private static final String LAUNCHER_2 = "com.android.launcher.2"; private static final int LAUNCHER_UID_2 = 10012; + private static final int USER_0 = UserHandle.USER_SYSTEM; private static final int USER_10 = 10; private static final int USER_11 = 11; @@ -326,7 +395,7 @@ public class ShortcutManagerTest extends AndroidTestCase { mInjectedPackageUidMap.put(LAUNCHER_1, LAUNCHER_UID_1); mInjectedPackageUidMap.put(LAUNCHER_2, LAUNCHER_UID_2); - mInjectedFilePathRoot = new File(getContext().getCacheDir(), "test-files"); + mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files"); // Empty the data directory. if (mInjectedFilePathRoot.exists()) { @@ -339,6 +408,10 @@ public class ShortcutManagerTest extends AndroidTestCase { setCaller(CALLING_PACKAGE_1); } + private Context getTestContext() { + return getInstrumentation().getContext(); + } + /** (Re-) init the manager and the service. */ private void initService() { LocalServices.removeServiceForTest(ShortcutServiceInternal.class); @@ -442,6 +515,10 @@ public class ShortcutManagerTest extends AndroidTestCase { + "/" + ShortcutService.FILENAME_USER_PACKAGES); } + private void waitOnMainThread() throws Throwable { + runTestOnUiThread(() -> {}); + } + private static Bundle makeBundle(Object... keysAndValues) { Preconditions.checkState((keysAndValues.length % 2) == 0); @@ -879,9 +956,9 @@ public class ShortcutManagerTest extends AndroidTestCase { } public void testSetDynamicShortcuts() { - final Icon icon1 = Icon.createWithResource(mContext, R.drawable.icon1); + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1); final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( - mContext.getResources(), R.drawable.icon2)); + getTestContext().getResources(), R.drawable.icon2)); final ShortcutInfo si1 = makeShortcut( "shortcut1", @@ -1163,16 +1240,16 @@ public class ShortcutManagerTest extends AndroidTestCase { } public void testIcons() { - final Icon res32x32 = Icon.createWithResource(mContext, R.drawable.black_32x32); - final Icon res64x64 = Icon.createWithResource(mContext, R.drawable.black_64x64); - final Icon res512x512 = Icon.createWithResource(mContext, R.drawable.black_512x512); + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); + final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512); final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( - mContext.getResources(), R.drawable.black_32x32)); + getTestContext().getResources(), R.drawable.black_32x32)); final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource( - mContext.getResources(), R.drawable.black_64x64)); + getTestContext().getResources(), R.drawable.black_64x64)); final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource( - mContext.getResources(), R.drawable.black_512x512)); + getTestContext().getResources(), R.drawable.black_512x512)); // Set from package 1 setCaller(CALLING_PACKAGE_1); @@ -1278,7 +1355,7 @@ public class ShortcutManagerTest extends AndroidTestCase { private void checkShrinkBitmap(int expectedWidth, int expectedHeight, int resId, int maxSize) { assertBitmapSize(expectedWidth, expectedHeight, ShortcutService.shrinkBitmap(BitmapFactory.decodeResource( - mContext.getResources(), resId), + getTestContext().getResources(), resId), maxSize)); } @@ -1433,7 +1510,7 @@ public class ShortcutManagerTest extends AndroidTestCase { runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { ShortcutInfo s2 = makeShortcutBuilder() .setId("s2") - .setIcon(Icon.createWithResource(mContext, R.drawable.black_32x32)) + .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32)) .build(); ShortcutInfo s4 = makeShortcutBuilder() @@ -1802,6 +1879,125 @@ public class ShortcutManagerTest extends AndroidTestCase { // TODO Check extra, etc } + public void testLauncherCallback() throws Throwable { + LauncherApps.Callback c0 = mock(LauncherApps.Callback.class); + + + // Set listeners + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper())); + }); + + runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + + waitOnMainThread(); + ArgumentCaptor shortcuts = ArgumentCaptor.forClass(List.class); + verify(c0).onShortcutsChanged( + eq(CALLING_PACKAGE_1), + shortcuts.capture(), + eq(UserHandle.of(USER_0)) + ); + assertShortcutIds(assertAllDynamic(shortcuts.getValue()), + "s1", "s2", "s3"); + + // From different package. + reset(c0); + runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + waitOnMainThread(); + shortcuts = ArgumentCaptor.forClass(List.class); + verify(c0).onShortcutsChanged( + eq(CALLING_PACKAGE_2), + shortcuts.capture(), + eq(UserHandle.of(USER_0)) + ); + assertShortcutIds(assertAllDynamic(shortcuts.getValue()), + "s1", "s2", "s3"); + + // Different user, callback shouldn't be called. + reset(c0); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + waitOnMainThread(); + verify(c0, times(0)).onShortcutsChanged( + anyString(), + any(List.class), + any(UserHandle.class) + ); + + // Test for addDynamicShortcut. + reset(c0); + runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { + assertTrue(mManager.addDynamicShortcut(makeShortcut("s4"))); + }); + + waitOnMainThread(); + shortcuts = ArgumentCaptor.forClass(List.class); + verify(c0).onShortcutsChanged( + eq(CALLING_PACKAGE_1), + shortcuts.capture(), + eq(UserHandle.of(USER_0)) + ); + assertShortcutIds(assertAllDynamic(shortcuts.getValue()), + "s1", "s2", "s3", "s4"); + + // Test for remove + reset(c0); + runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { + mManager.deleteDynamicShortcut("s1"); + }); + + waitOnMainThread(); + shortcuts = ArgumentCaptor.forClass(List.class); + verify(c0).onShortcutsChanged( + eq(CALLING_PACKAGE_1), + shortcuts.capture(), + eq(UserHandle.of(USER_0)) + ); + assertShortcutIds(assertAllDynamic(shortcuts.getValue()), + "s2", "s3", "s4"); + + // Test for update + reset(c0); + runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { + assertTrue(mManager.updateShortcuts(Arrays.asList( + makeShortcut("s1"), makeShortcut("s2")))); + }); + + waitOnMainThread(); + shortcuts = ArgumentCaptor.forClass(List.class); + verify(c0).onShortcutsChanged( + eq(CALLING_PACKAGE_1), + shortcuts.capture(), + eq(UserHandle.of(USER_0)) + ); + assertShortcutIds(assertAllDynamic(shortcuts.getValue()), + "s2", "s3", "s4"); + + // Test for deleteAll + reset(c0); + runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { + mManager.deleteAllDynamicShortcuts(); + }); + + waitOnMainThread(); + shortcuts = ArgumentCaptor.forClass(List.class); + verify(c0).onShortcutsChanged( + eq(CALLING_PACKAGE_1), + shortcuts.capture(), + eq(UserHandle.of(USER_0)) + ); + assertEquals(0, shortcuts.getValue().size()); + } + // === Test for persisting === public void testSaveAndLoadUser_empty() { @@ -1823,9 +2019,9 @@ public class ShortcutManagerTest extends AndroidTestCase { public void testSaveAndLoadUser() { // First, create some shortcuts and save. runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { - final Icon icon1 = Icon.createWithResource(mContext, R.drawable.black_64x16); + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16); final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( - mContext.getResources(), R.drawable.icon2)); + getTestContext().getResources(), R.drawable.icon2)); final ShortcutInfo si1 = makeShortcut( "s1", @@ -1850,9 +2046,9 @@ public class ShortcutManagerTest extends AndroidTestCase { assertEquals(2, mManager.getRemainingCallCount()); }); runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> { - final Icon icon1 = Icon.createWithResource(mContext, R.drawable.black_16x64); + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_16x64); final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( - mContext.getResources(), R.drawable.icon2)); + getTestContext().getResources(), R.drawable.icon2)); final ShortcutInfo si1 = makeShortcut( "s1", @@ -1877,9 +2073,9 @@ public class ShortcutManagerTest extends AndroidTestCase { assertEquals(2, mManager.getRemainingCallCount()); }); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { - final Icon icon1 = Icon.createWithResource(mContext, R.drawable.black_64x64); + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( - mContext.getResources(), R.drawable.icon2)); + getTestContext().getResources(), R.drawable.icon2)); final ShortcutInfo si1 = makeShortcut( "s1",