diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/content/pm/ILauncherApps.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/content/pm/LauncherApps.java | 48 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageManagerInternal.java | 7 | ||||
| -rw-r--r-- | core/java/android/content/pm/ShortcutServiceInternal.java | 2 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/LauncherAppsService.java | 46 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 17 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/ShortcutService.java | 298 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java | 26 |
11 files changed, 363 insertions, 86 deletions
diff --git a/api/current.txt b/api/current.txt index 9e0af763ef56..c04debfe209e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9454,6 +9454,7 @@ package android.content.pm { method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); + method public boolean hasShortcutHostPermission(); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle); method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); diff --git a/api/system-current.txt b/api/system-current.txt index 00cf3573c71e..d242e2ffe9dc 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -9788,6 +9788,7 @@ package android.content.pm { method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); + method public boolean hasShortcutHostPermission(); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle); method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); diff --git a/api/test-current.txt b/api/test-current.txt index 9aed5e940d52..78bdf16bee90 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -9463,6 +9463,7 @@ package android.content.pm { method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); + method public boolean hasShortcutHostPermission(); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle); method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index b1d3f207f82f..7b578728b104 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -58,4 +58,6 @@ interface ILauncherApps { int getShortcutIconResId(String callingPackage, in ShortcutInfo shortcut, in UserHandle user); ParcelFileDescriptor getShortcutIconFd(String callingPackage, in ShortcutInfo shortcut, in UserHandle user); + + boolean hasShortcutHostPermission(String callingPackage); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index a6a732ea9513..8d43c44ea4d5 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -16,11 +16,9 @@ package android.content.pm; -import android.Manifest.permission; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.RequiresPermission; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -30,7 +28,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; @@ -159,6 +156,9 @@ public class LauncherApps { * Indicates that one or more shortcuts (which may be dynamic and/or pinned) * have been added, updated or removed. * + * <p>Only the applications that are allowed to access the shortcut information, + * as defined in {@link #hasShortcutHostPermission()}, will receive it. + * * @param packageName The name of the package that has the shortcuts. * @param shortcuts all shortcuts from the package (dynamic and/or pinned). * @param user The UserHandle of the profile that generated the change. @@ -395,16 +395,34 @@ public class LauncherApps { } /** + * Returns whether the caller can access the shortcut information. + * + * <p>Only the default launcher can access the shortcut information. + * + * <p>Note when this method returns {@code false}, that may be a temporary situation because + * the user is trying a new launcher application. The user may decide to change the default + * launcher to the calling application again, so even if a launcher application loses + * this permission, it does <b>not</b> have to purge pinned shortcut information. + */ + public boolean hasShortcutHostPermission() { + try { + return mService.hasShortcutHostPermission(mContext.getPackageName()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns the IDs of {@link ShortcutInfo}s that match {@code query}. * - * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * <p>Callers must be allowed to access the shortcut information, as defined in {@link + * #hasShortcutHostPermission()}. * * @param query result includes shortcuts matching this query. * @param user The UserHandle of the profile. * * @return the IDs of {@link ShortcutInfo}s that match the query. */ - @RequiresPermission(permission.BIND_APPWIDGET) @Nullable public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query, @NonNull UserHandle user) { @@ -420,7 +438,8 @@ public class LauncherApps { /** * Returns {@link ShortcutInfo}s with the given IDs from a package. * - * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * <p>Callers must be allowed to access the shortcut information, as defined in {@link + * #hasShortcutHostPermission()}. * * @param packageName The target package. * @param ids IDs of the shortcuts to retrieve. @@ -428,7 +447,6 @@ public class LauncherApps { * * @return list of {@link ShortcutInfo} associated with the package. */ - @RequiresPermission(permission.BIND_APPWIDGET) @Nullable public List<ShortcutInfo> getShortcutInfo(@NonNull String packageName, @NonNull List<String> ids, @NonNull UserHandle user) { @@ -447,13 +465,13 @@ public class LauncherApps { * <p>This API is <b>NOT</b> cumulative; this will replace all pinned shortcuts for the package. * However, different launchers may have different set of pinned shortcuts. * - * <p>Callers must have the {@link permission#BIND_APPWIDGET} permission. + * <p>Callers must be allowed to access the shortcut information, as defined in {@link + * #hasShortcutHostPermission()}. * * @param packageName The target package name. * @param shortcutIds The IDs of the shortcut to be pinned. * @param user The UserHandle of the profile. */ - @RequiresPermission(permission.BIND_APPWIDGET) public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds, @NonNull UserHandle user) { try { @@ -467,12 +485,12 @@ public class LauncherApps { * Return the icon resource ID, if {@code shortcut} has one * (i.e. when {@link ShortcutInfo#hasIconResource()} returns {@code true}). * - * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * <p>Callers must be allowed to access the shortcut information, as defined in {@link + * #hasShortcutHostPermission()}. * * @param shortcut The target shortcut. * @param user The UserHandle of the profile. */ - @RequiresPermission(permission.BIND_APPWIDGET) public int getShortcutIconResId(@NonNull ShortcutInfo shortcut, @NonNull UserHandle user) { try { return mService.getShortcutIconResId(mContext.getPackageName(), shortcut, user); @@ -485,12 +503,12 @@ public class LauncherApps { * Return the icon as {@link ParcelFileDescriptor}, when it's stored as a file * (i.e. when {@link ShortcutInfo#hasIconFile()} returns {@code true}). * - * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * <p>Callers must be allowed to access the shortcut information, as defined in {@link + * #hasShortcutHostPermission()}. * * @param shortcut The target shortcut. * @param user The UserHandle of the profile. */ - @RequiresPermission(permission.BIND_APPWIDGET) public ParcelFileDescriptor getShortcutIconFd( @NonNull ShortcutInfo shortcut, @NonNull UserHandle user) { try { @@ -503,7 +521,8 @@ public class LauncherApps { /** * Launches a shortcut. * - * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * <p>Callers must be allowed to access the shortcut information, as defined in {@link + * #hasShortcutHostPermission()}. * * @param packageName The target shortcut package name. * @param shortcutId The target shortcut ID. @@ -513,7 +532,6 @@ public class LauncherApps { * @return {@code false} when the shortcut is no longer valid (e.g. the creator application * has been uninstalled). {@code true} when the shortcut is still valid. */ - @RequiresPermission(permission.BIND_APPWIDGET) public boolean startShortcut(@NonNull String packageName, @NonNull String shortcutId, @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions, @NonNull UserHandle user) { diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 89f2fc4334a4..13ebb823bd78 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.ComponentName; import android.content.pm.PackageManager.NameNotFoundException; import java.util.List; @@ -140,4 +141,10 @@ public abstract class PackageManagerInternal { * found on the system. */ public abstract ApplicationInfo getApplicationInfo(String packageName, int userId); + + /** + * Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}. + */ + public abstract ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, + int userId); } diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 918c763545fa..7c764aa7d0ff 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -63,4 +63,6 @@ public abstract class ShortcutServiceInternal { public abstract ParcelFileDescriptor getShortcutIconFd(@NonNull String callingPackage, @NonNull ShortcutInfo shortcut, int userId); + + public abstract boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index b7cd3185a228..6cc0544afb21 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -99,6 +99,15 @@ public class LauncherAppsService extends SystemService { mShortcutServiceInternal.addListener(mPackageMonitor); } + @VisibleForTesting + int injectBinderCallingUid() { + return getCallingUid(); + } + + private int getCallingUserId() { + return UserHandle.getUserId(injectBinderCallingUid()); + } + /* * @see android.content.pm.ILauncherApps#addOnAppsChangedListener( * android.content.pm.IOnAppsChangedListener) @@ -296,17 +305,21 @@ public class LauncherAppsService extends SystemService { } } - private void enforceShortcutPermission(UserHandle user) { + private void ensureShortcutPermission(@NonNull String callingPackage, UserHandle user) { + verifyCallingPackage(callingPackage); ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user); - // STOPSHIP Implement it + + if (!mShortcutServiceInternal.hasShortcutHostPermission(callingPackage, + user.getIdentifier())) { + throw new SecurityException("Caller can't access shortcut information"); + } } @Override public ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, ComponentName componentName, int flags, UserHandle user) throws RemoteException { - enforceShortcutPermission(user); - verifyCallingPackage(callingPackage); + ensureShortcutPermission(callingPackage, user); return new ParceledListSlice<>( mShortcutServiceInternal.getShortcuts(callingPackage, changedSince, packageName, @@ -316,8 +329,7 @@ public class LauncherAppsService extends SystemService { @Override public ParceledListSlice getShortcutInfo(String callingPackage, String packageName, List<String> ids, UserHandle user) throws RemoteException { - enforceShortcutPermission(user); - verifyCallingPackage(callingPackage); + ensureShortcutPermission(callingPackage, user); return new ParceledListSlice<>( mShortcutServiceInternal.getShortcutInfo(callingPackage, packageName, @@ -327,8 +339,7 @@ public class LauncherAppsService extends SystemService { @Override public void pinShortcuts(String callingPackage, String packageName, List<String> ids, UserHandle user) throws RemoteException { - enforceShortcutPermission(user); - verifyCallingPackage(callingPackage); + ensureShortcutPermission(callingPackage, user); mShortcutServiceInternal.pinShortcuts(callingPackage, packageName, ids, user.getIdentifier()); @@ -337,8 +348,7 @@ public class LauncherAppsService extends SystemService { @Override public int getShortcutIconResId(String callingPackage, ShortcutInfo shortcut, UserHandle user) { - enforceShortcutPermission(user); - verifyCallingPackage(callingPackage); + ensureShortcutPermission(callingPackage, user); return mShortcutServiceInternal.getShortcutIconResId(callingPackage, shortcut, user.getIdentifier()); @@ -347,19 +357,24 @@ public class LauncherAppsService extends SystemService { @Override public ParcelFileDescriptor getShortcutIconFd(String callingPackage, ShortcutInfo shortcut, UserHandle user) { - enforceShortcutPermission(user); - verifyCallingPackage(callingPackage); + ensureShortcutPermission(callingPackage, user); return mShortcutServiceInternal.getShortcutIconFd(callingPackage, shortcut, user.getIdentifier()); } @Override + public boolean hasShortcutHostPermission(String callingPackage) throws RemoteException { + verifyCallingPackage(callingPackage); + return mShortcutServiceInternal.hasShortcutHostPermission(callingPackage, + getCallingUserId()); + } + + @Override public boolean startShortcut(String callingPackage, String packageName, String shortcutId, Rect sourceBounds, Bundle startActivityOptions, UserHandle user) throws RemoteException { - enforceShortcutPermission(user); - verifyCallingPackage(callingPackage); + ensureShortcutPermission(callingPackage, user); final Intent intent = mShortcutServiceInternal.createShortcutIntent(callingPackage, packageName, shortcutId, user.getIdentifier()); @@ -656,6 +671,9 @@ public class LauncherAppsService extends SystemService { 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. + try { listener.onShortcutChanged(user, packageName, new ParceledListSlice<>(shortcuts)); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9a1ecd70c743..b624087d250b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -16456,14 +16456,17 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); @Override public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) { + return getHomeActivitiesAsUser(allHomeCandidates, UserHandle.getCallingUserId()); + } + + ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, + int userId) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); - - final int callingUserId = UserHandle.getCallingUserId(); List<ResolveInfo> list = queryIntentActivitiesInternal(intent, null, - PackageManager.GET_META_DATA, callingUserId); + PackageManager.GET_META_DATA, userId); ResolveInfo preferred = findPreferredActivity(intent, null, 0, list, 0, - true, false, false, callingUserId); + true, false, false, userId); allHomeCandidates.clear(); if (list != null) { @@ -19252,6 +19255,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); public ApplicationInfo getApplicationInfo(String packageName, int userId) { return PackageManagerService.this.getApplicationInfo(packageName, 0 /*flags*/, userId); } + + @Override + public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, + int userId) { + return PackageManagerService.this.getHomeActivitiesAsUser(allHomeCandidates, userId); + } } @Override diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 0b0f7abbce4e..f3c63ee60b3c 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -28,7 +28,9 @@ import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; @@ -71,6 +73,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import libcore.io.IoUtils; +import libcore.util.Objects; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -88,7 +91,6 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.function.Predicate; /** @@ -112,7 +114,7 @@ import java.util.function.Predicate; public class ShortcutService extends IShortcutService.Stub { static final String TAG = "ShortcutService"; - static final boolean DEBUG = false; // STOPSHIP if true + static final boolean DEBUG = true; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true @VisibleForTesting @@ -156,6 +158,7 @@ public class ShortcutService extends IShortcutService.Stub { static final String TAG_INTENT_EXTRAS = "intent-extras"; static final String TAG_EXTRAS = "extras"; static final String TAG_SHORTCUT = "shortcut"; + static final String TAG_LAUNCHER = "launcher"; static final String ATTR_VALUE = "value"; static final String ATTR_NAME = "name"; @@ -251,10 +254,13 @@ public class ShortcutService extends IShortcutService.Stub { private CompressFormat mIconPersistFormat; private int mIconPersistQuality; + private final PackageManagerInternal mPackageManagerInternal; + public ShortcutService(Context context) { mContext = Preconditions.checkNotNull(context); LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); mHandler = new Handler(BackgroundThread.get().getLooper()); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); } /** @@ -460,6 +466,11 @@ public class ShortcutService extends IShortcutService.Stub { writeTagValue(out, tag, Long.toString(value)); } + static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException { + if (name == null) return; + writeTagValue(out, tag, name.flattenToString()); + } + static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle) throws IOException, XmlPullParserException { if (bundle == null) return; @@ -658,7 +669,7 @@ public class ShortcutService extends IShortcutService.Stub { } // TODO Actually make it async. - private void scheduleSaveUser(@UserIdInt int userId) { + void scheduleSaveUser(@UserIdInt int userId) { synchronized (mLock) { saveUserLocked(userId); } @@ -1289,6 +1300,87 @@ public class ShortcutService extends IShortcutService.Stub { Slog.i(TAG, "ShortcutManager: throttling counter reset"); } + // We override this method in unit tests to do a simpler check. + boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { + return hasShortcutHostPermissionInner(callingPackage, userId); + } + + // This method is extracted so we can directly call this method from unit tests, + // even when hasShortcutPermission() is overridden. + @VisibleForTesting + boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) { + synchronized (mLock) { + long start = 0; + if (DEBUG) { + start = System.currentTimeMillis(); + } + + final UserShortcuts user = getUserShortcutsLocked(userId); + + final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); + + // Default launcher from package manager. + final ComponentName defaultLauncher = injectPackageManagerInternal() + .getHomeActivitiesAsUser(allHomeCandidates, userId); + + ComponentName detected; + if (defaultLauncher != null) { + detected = defaultLauncher; + if (DEBUG) { + Slog.v(TAG, "Default launcher from PM: " + detected); + } + } else { + detected = user.getLauncherComponent(); + + // TODO: Make sure it's still enabled. + if (DEBUG) { + Slog.v(TAG, "Cached launcher: " + detected); + } + } + + if (detected == null) { + // If we reach here, that means it's the first check since the user was created, + // and there's already multiple launchers and there's no default set. + // Find the system one with the highest priority. + // (We need to check the priority too because of FallbackHome in Settings.) + // If there's no system launcher yet, then no one can access shortcuts, until + // the user explicitly + final int size = allHomeCandidates.size(); + + int lastPriority = Integer.MIN_VALUE; + for (int i = 0; i < size; i++) { + final ResolveInfo ri = allHomeCandidates.get(i); + if (!ri.activityInfo.applicationInfo.isSystemApp()) { + continue; + } + if (DEBUG) { + Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d", + ri.activityInfo.getComponentName(), ri.priority)); + } + if (ri.priority < lastPriority) { + continue; + } + detected = ri.activityInfo.getComponentName(); + lastPriority = ri.priority; + } + } + if (DEBUG) { + long end = System.currentTimeMillis(); + Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start)); + } + if (detected != null) { + if (DEBUG) { + Slog.v(TAG, "Detected launcher: " + detected); + } + user.setLauncherComponent(this, detected); + return detected.getPackageName().equals(callingPackage); + } else { + // Default launcher not found. + return false; + } + } + } + /** * Entry point from {@link LauncherApps}. */ @@ -1434,6 +1526,11 @@ public class ShortcutService extends IShortcutService.Stub { } } } + + @Override + public boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { + return ShortcutService.this.hasShortcutHostPermission(callingPackage, userId); + } } // === Dump === @@ -1512,37 +1609,74 @@ public class ShortcutService extends IShortcutService.Stub { (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver); } + static class CommandException extends Exception { + public CommandException(String message) { + super(message); + } + } + /** * Handle "adb shell cmd". */ private class MyShellCommand extends ShellCommand { + + private int mUserId = UserHandle.USER_SYSTEM; + + private void parseOptions(boolean takeUser) + throws CommandException { + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + if (takeUser) { + mUserId = UserHandle.parseUserArg(getNextArgRequired()); + break; + } + // fallthrough + default: + throw new CommandException("Unknown option: " + opt); + } + } + } + @Override public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(cmd); } final PrintWriter pw = getOutPrintWriter(); - int ret = 1; - switch (cmd) { - case "reset-package-throttling": - ret = handleResetPackageThrottling(); - break; - case "reset-throttling": - ret = handleResetThrottling(); - break; - case "override-config": - ret = handleOverrideConfig(); - break; - case "reset-config": - ret = handleResetConfig(); - break; - default: - return handleDefaultCommands(cmd); - } - if (ret == 0) { - pw.println("Success"); + try { + switch (cmd) { + case "reset-package-throttling": + handleResetPackageThrottling(); + break; + case "reset-throttling": + handleResetThrottling(); + break; + case "override-config": + handleOverrideConfig(); + break; + case "reset-config": + handleResetConfig(); + break; + case "clear-default-launcher": + handleClearDefaultLauncher(); + break; + case "get-default-launcher": + handleGetDefaultLauncher(); + break; + case "refresh-default-launcher": + handleRefreshDefaultLauncher(); + break; + default: + return handleDefaultCommands(cmd); + } + } catch (CommandException e) { + pw.println("Error: " + e.getMessage()); + return 1; } - return ret; + pw.println("Success"); + return 0; } @Override @@ -1562,6 +1696,15 @@ public class ShortcutService extends IShortcutService.Stub { pw.println("cmd shortcut reset-config"); pw.println(" Reset the configuration set with \"update-config\""); pw.println(); + pw.println("cmd shortcut clear-default-launcher [--user USER_ID]"); + pw.println(" Clear the cached default launcher"); + pw.println(); + pw.println("cmd shortcut get-default-launcher [--user USER_ID]"); + pw.println(" Show the cached default launcher"); + pw.println(); + pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]"); + pw.println(" Refresh the cached default launcher"); + pw.println(); } private int handleResetThrottling() { @@ -1569,49 +1712,67 @@ public class ShortcutService extends IShortcutService.Stub { return 0; } - private int handleResetPackageThrottling() { - final PrintWriter pw = getOutPrintWriter(); + private void handleResetPackageThrottling() throws CommandException { + parseOptions(/* takeUser =*/ true); - int userId = UserHandle.USER_SYSTEM; - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "--user": - userId = UserHandle.parseUserArg(getNextArgRequired()); - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } - } final String packageName = getNextArgRequired(); synchronized (mLock) { - getPackageShortcutsLocked(packageName, userId).resetRateLimitingForCommandLine(); - saveUserLocked(userId); + getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine(); + saveUserLocked(mUserId); } - - return 0; } - private int handleOverrideConfig() { - final PrintWriter pw = getOutPrintWriter(); + private void handleOverrideConfig() throws CommandException { final String config = getNextArgRequired(); synchronized (mLock) { if (!updateConfigurationLocked(config)) { - pw.println("override-config failed. See logcat for details."); - return 1; + throw new CommandException("override-config failed. See logcat for details."); } } - return 0; } - private int handleResetConfig() { + private void handleResetConfig() { synchronized (mLock) { loadConfigurationLocked(); } - return 0; + } + + private void clearLauncher() { + synchronized (mLock) { + getUserShortcutsLocked(mUserId).setLauncherComponent( + ShortcutService.this, null); + } + } + + private void showLauncher() { + synchronized (mLock) { + // This ensures to set the cached launcher. Package name doesn't matter. + hasShortcutHostPermissionInner("-", mUserId); + + getOutPrintWriter().println("Launcher: " + + getUserShortcutsLocked(mUserId).getLauncherComponent()); + } + } + + private void handleClearDefaultLauncher() throws CommandException { + parseOptions(/* takeUser =*/ true); + + clearLauncher(); + } + + private void handleGetDefaultLauncher() throws CommandException { + parseOptions(/* takeUser =*/ true); + + showLauncher(); + } + + private void handleRefreshDefaultLauncher() throws CommandException { + parseOptions(/* takeUser =*/ true); + + clearLauncher(); + showLauncher(); } } @@ -1640,6 +1801,10 @@ public class ShortcutService extends IShortcutService.Stub { return ActivityManager.isLowRamDeviceStatic(); } + PackageManagerInternal injectPackageManagerInternal() { + return mPackageManagerInternal; + } + File getUserBitmapFilePath(@UserIdInt int userId) { return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); } @@ -1687,6 +1852,9 @@ public class ShortcutService extends IShortcutService.Stub { } } +/** + * Per-user information. + */ class UserShortcuts { private static final String TAG = ShortcutService.TAG; @@ -1695,6 +1863,8 @@ class UserShortcuts { private final ArrayMap<String, PackageShortcuts> mPackages = new ArrayMap<>(); + private ComponentName mLauncherComponent; + public UserShortcuts(int userId) { mUserId = userId; } @@ -1706,11 +1876,11 @@ class UserShortcuts { public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { out.startTag(null, ShortcutService.TAG_USER); - for (int i = 0; i < mPackages.size(); i++) { - final String packageName = mPackages.keyAt(i); - final PackageShortcuts packageShortcuts = mPackages.valueAt(i); + ShortcutService.writeTagValue(out, ShortcutService.TAG_LAUNCHER, + mLauncherComponent); - packageShortcuts.saveToXml(out); + for (int i = 0; i < mPackages.size(); i++) { + mPackages.valueAt(i).saveToXml(out); } out.endTag(null, ShortcutService.TAG_USER); @@ -1730,6 +1900,10 @@ class UserShortcuts { final int depth = parser.getDepth(); final String tag = parser.getName(); switch (tag) { + case ShortcutService.TAG_LAUNCHER: + ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute( + parser, ShortcutService.ATTR_VALUE); + continue; case ShortcutService.TAG_PACKAGE: final PackageShortcuts shortcuts = PackageShortcuts.loadFromXml(parser, userId); @@ -1742,12 +1916,30 @@ class UserShortcuts { return ret; } + public ComponentName getLauncherComponent() { + return mLauncherComponent; + } + + public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) { + if (Objects.equal(mLauncherComponent, launcherComponent)) { + return; + } + mLauncherComponent = launcherComponent; + s.scheduleSaveUser(mUserId); + } + public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { - pw.print(" "); + pw.print(prefix); pw.print("User: "); pw.print(mUserId); pw.println(); + pw.print(prefix); + pw.print(" "); + pw.print("Default launcher: "); + pw.print(mLauncherComponent); + pw.println(); + for (int i = 0; i < mPackages.size(); i++) { mPackages.valueAt(i).dump(s, pw, prefix + " "); } 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 f2c42dbb012b..caadbf9d9c7d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -16,6 +16,7 @@ package com.android.server.pm; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -27,6 +28,7 @@ import android.content.pm.ILauncherApps; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; 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; @@ -191,6 +193,17 @@ public class ShortcutManagerTest extends AndroidTestCase { boolean injectIsLowRamDevice() { return mInjectdIsLowRamDevice; } + + @Override + PackageManagerInternal injectPackageManagerInternal() { + return mMockPackageManagerInternal; + } + + @Override + boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { + // Sort of hack; do a simpler check. + return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage); + } } /** ShortcutManager with injection override methods. */ @@ -258,6 +271,7 @@ public class ShortcutManagerTest extends AndroidTestCase { private Map<String, Integer> mInjectedPackageUidMap; private PackageManager mMockPackageManager; + private PackageManagerInternal mMockPackageManagerInternal; private UserManager mMockUserManager; private static final String CALLING_PACKAGE_1 = "com.android.test.1"; @@ -298,6 +312,7 @@ public class ShortcutManagerTest extends AndroidTestCase { mClientContext = new ClientContext(); mMockPackageManager = mock(PackageManager.class); + mMockPackageManagerInternal = mock(PackageManagerInternal.class); mMockUserManager = mock(UserManager.class); // Prepare injection values. @@ -1889,6 +1904,9 @@ public class ShortcutManagerTest extends AndroidTestCase { assertEquals(2, mManager.getRemainingCallCount()); }); + mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncherComponent( + mService, new ComponentName("pkg1", "class")); + // Restore. initService(); @@ -1918,6 +1936,9 @@ public class ShortcutManagerTest extends AndroidTestCase { assertEquals("title2-2", getCallerShortcut("s2").getTitle()); }); + assertEquals("pkg1", mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM) + .getLauncherComponent().getPackageName()); + // Start another user mService.onStartUserLocked(USER_10); @@ -1932,6 +1953,7 @@ public class ShortcutManagerTest extends AndroidTestCase { assertEquals("title10-1-1", getCallerShortcut("s1").getTitle()); assertEquals("title10-1-2", getCallerShortcut("s2").getTitle()); }); + assertNull(mService.getShortcutsForTest().get(USER_10).getLauncherComponent()); // Try stopping the user mService.onCleanupUserInner(USER_10); @@ -1941,4 +1963,8 @@ public class ShortcutManagerTest extends AndroidTestCase { // TODO Check all other fields } + + // TODO Detailed test for hasShortcutPermissionInner(). + + // TODO Add tests for the command line functions too. } |