Loading core/java/android/content/pm/ShortcutInfo.java +10 −3 Original line number Diff line number Diff line Loading @@ -314,9 +314,11 @@ public final class ShortcutInfo implements Parcelable { * * @hide */ public void enforceMandatoryFields() { public void enforceMandatoryFields(boolean forPinned) { Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); if (!forPinned) { Preconditions.checkNotNull(mActivity, "Activity must be provided"); } if (mTitle == null && mTitleResId == 0) { throw new IllegalArgumentException("Short label must be provided"); } Loading Loading @@ -1055,6 +1057,11 @@ public final class ShortcutInfo implements Parcelable { * Launcher apps should show the launcher icon for the returned activity alongside * this shortcut. * * <p>When a shortcut is dynamic or manifest * (i.e. either {@link #isDynamic()} or {@link #isDeclaredInManifest()} returns {@code TRUE}), * then it should always have a non-null target activity. * Otherwise it will return null. * * @see Builder#setActivity */ @Nullable Loading Loading @@ -1361,7 +1368,7 @@ public final class ShortcutInfo implements Parcelable { } /** * @return true if pinned but neither static nor dynamic. * Return {@code TRUE} if a shortcut is pinned but neither manifest nor dynamic. * @hide */ public boolean isFloating() { Loading services/core/java/com/android/server/pm/ShortcutPackage.java +24 −5 Original line number Diff line number Diff line Loading @@ -259,6 +259,11 @@ class ShortcutPackage extends ShortcutPackageItem { for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.isFloating()) { si.setRank(0); si.setActivity(null); } if (si.isAlive()) continue; if (removeList == null) { Loading Loading @@ -288,6 +293,7 @@ class ShortcutPackage extends ShortcutPackageItem { si.setTimestamp(now); si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); si.setRank(0); // It may still be pinned, so clear the rank. si.setActivity(null); } } if (changed) { Loading Loading @@ -355,6 +361,7 @@ class ShortcutPackage extends ShortcutPackageItem { if (oldShortcut.isPinned()) { oldShortcut.setRank(0); oldShortcut.setActivity(null); oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); if (disable) { oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED); Loading Loading @@ -595,6 +602,10 @@ class ShortcutPackage extends ShortcutPackageItem { for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.isFloating()) { continue; // Ignore floating shortcuts, which are not tied to any activities. } final ComponentName activity = si.getActivity(); if (checked.contains(activity)) { Loading Loading @@ -1356,6 +1367,10 @@ class ShortcutPackage extends ShortcutPackageItem { case TAG_SHORTCUT: final ShortcutInfo si = parseShortcut(parser, packageName, shortcutUser.getUserId()); // Floating shortcut used to have target activities, but not anymore. if (si.isFloating()) { // Not really needed by just in case. si.setActivity(null); } // Don't use addShortcut(), we don't need to save the icon. ret.mShortcuts.put(si.getId(), si); Loading Loading @@ -1462,7 +1477,6 @@ class ShortcutPackage extends ShortcutPackageItem { intents.clear(); intents.add(intentLegacy); } return new ShortcutInfo( userId, id, packageName, activityComponent, /* icon =*/ null, title, titleResId, titleResName, text, textResId, textResName, Loading Loading @@ -1553,12 +1567,17 @@ class ShortcutPackage extends ShortcutPackageItem { Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " is both dynamic and manifest at the same time."); } if (si.getActivity() == null) { if (!si.isFloating() && si.getActivity() == null) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " is not floating, but has null activity."); } if (si.isFloating() && si.getActivity() != null) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " has null activity."); + " is floating, but has non-null activity."); } if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) { if (!si.isFloating() && !si.isEnabled()) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " is not floating, but is disabled."); Loading @@ -1581,7 +1600,7 @@ class ShortcutPackage extends ShortcutPackageItem { } if (failed) { throw new IllegalStateException("See logcat for errors"); mShortcutUser.mService.verifyError(); } } Loading services/core/java/com/android/server/pm/ShortcutService.java +13 −3 Original line number Diff line number Diff line Loading @@ -124,6 +124,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; Loading Loading @@ -401,6 +402,9 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) { if (DEBUG) { Binder.LOG_RUNTIME_EXCEPTION = true; } mContext = Preconditions.checkNotNull(context); LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); mHandler = new Handler(looper); Loading Loading @@ -1604,7 +1608,7 @@ public class ShortcutService extends IShortcutService.Stub { } if (!forUpdate) { shortcut.enforceMandatoryFields(); shortcut.enforceMandatoryFields(/* forPinned= */ false); Preconditions.checkArgument( injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), "Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity"); Loading Loading @@ -1752,6 +1756,9 @@ public class ShortcutService extends IShortcutService.Stub { // Note copyNonNullFieldsFrom() does the "updatable with?" check too. target.copyNonNullFieldsFrom(source); if (target.isFloating()) { target.setActivity(null); } target.setTimestamp(injectCurrentTimeMillis()); if (replacingIcon) { Loading Loading @@ -2320,8 +2327,7 @@ public class ShortcutService extends IShortcutService.Stub { return false; } if (componentName != null) { if (si.getActivity() != null && !si.getActivity().equals(componentName)) { if (!Objects.equals(componentName, si.getActivity())) { return false; } } Loading Loading @@ -3771,4 +3777,8 @@ public class ShortcutService extends IShortcutService.Stub { forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); } } void verifyError() { Slog.e(TAG, "See logcat for errors"); } } services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +6 −0 Original line number Diff line number Diff line Loading @@ -376,6 +376,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { @Override boolean injectIsActivityEnabledAndExported(ComponentName activity, @UserIdInt int userId) { assertNotNull(activity); return mEnabledActivityChecker.test(activity, userId); } Loading Loading @@ -416,6 +417,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); } @Override void verifyError() { fail("Verify error"); } } /** ShortcutManager with injection override methods. */ Loading services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +37 −1 Original line number Diff line number Diff line Loading @@ -1292,7 +1292,43 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /* activity =*/ null, /* flags */ 0), getCallingUser()); }); // TODO More tests: pinned but dynamic. // Make sure floating shortcuts don't match with an activity. // At this point, s1 is dynamic and pinned, so it still has a target activity. runWithCaller(LAUNCHER_1, USER_0, () -> { assertWith(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 0, CALLING_PACKAGE_2, /* activity =*/ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()), ShortcutQuery.FLAG_GET_PINNED), getCallingUser())) .haveIds("s3") .areAllPinned() .areAllDynamic() .areAllWithActivity(new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName())); }); // Now remove as a dynamic, making it floating. runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { mManager.removeDynamicShortcuts(list("s3")); assertWith(mManager.getPinnedShortcuts()) .selectFloating() .areAllWithNoActivity(); }); runWithCaller(LAUNCHER_1, USER_0, () -> { // This shouldn't match now. assertWith(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 0, CALLING_PACKAGE_2, /* activity =*/ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()), ShortcutQuery.FLAG_GET_PINNED), getCallingUser())) .isEmpty(); }); } public void testGetShortcuts_shortcutKinds() throws Exception { Loading Loading
core/java/android/content/pm/ShortcutInfo.java +10 −3 Original line number Diff line number Diff line Loading @@ -314,9 +314,11 @@ public final class ShortcutInfo implements Parcelable { * * @hide */ public void enforceMandatoryFields() { public void enforceMandatoryFields(boolean forPinned) { Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); if (!forPinned) { Preconditions.checkNotNull(mActivity, "Activity must be provided"); } if (mTitle == null && mTitleResId == 0) { throw new IllegalArgumentException("Short label must be provided"); } Loading Loading @@ -1055,6 +1057,11 @@ public final class ShortcutInfo implements Parcelable { * Launcher apps should show the launcher icon for the returned activity alongside * this shortcut. * * <p>When a shortcut is dynamic or manifest * (i.e. either {@link #isDynamic()} or {@link #isDeclaredInManifest()} returns {@code TRUE}), * then it should always have a non-null target activity. * Otherwise it will return null. * * @see Builder#setActivity */ @Nullable Loading Loading @@ -1361,7 +1368,7 @@ public final class ShortcutInfo implements Parcelable { } /** * @return true if pinned but neither static nor dynamic. * Return {@code TRUE} if a shortcut is pinned but neither manifest nor dynamic. * @hide */ public boolean isFloating() { Loading
services/core/java/com/android/server/pm/ShortcutPackage.java +24 −5 Original line number Diff line number Diff line Loading @@ -259,6 +259,11 @@ class ShortcutPackage extends ShortcutPackageItem { for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.isFloating()) { si.setRank(0); si.setActivity(null); } if (si.isAlive()) continue; if (removeList == null) { Loading Loading @@ -288,6 +293,7 @@ class ShortcutPackage extends ShortcutPackageItem { si.setTimestamp(now); si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); si.setRank(0); // It may still be pinned, so clear the rank. si.setActivity(null); } } if (changed) { Loading Loading @@ -355,6 +361,7 @@ class ShortcutPackage extends ShortcutPackageItem { if (oldShortcut.isPinned()) { oldShortcut.setRank(0); oldShortcut.setActivity(null); oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); if (disable) { oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED); Loading Loading @@ -595,6 +602,10 @@ class ShortcutPackage extends ShortcutPackageItem { for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.isFloating()) { continue; // Ignore floating shortcuts, which are not tied to any activities. } final ComponentName activity = si.getActivity(); if (checked.contains(activity)) { Loading Loading @@ -1356,6 +1367,10 @@ class ShortcutPackage extends ShortcutPackageItem { case TAG_SHORTCUT: final ShortcutInfo si = parseShortcut(parser, packageName, shortcutUser.getUserId()); // Floating shortcut used to have target activities, but not anymore. if (si.isFloating()) { // Not really needed by just in case. si.setActivity(null); } // Don't use addShortcut(), we don't need to save the icon. ret.mShortcuts.put(si.getId(), si); Loading Loading @@ -1462,7 +1477,6 @@ class ShortcutPackage extends ShortcutPackageItem { intents.clear(); intents.add(intentLegacy); } return new ShortcutInfo( userId, id, packageName, activityComponent, /* icon =*/ null, title, titleResId, titleResName, text, textResId, textResName, Loading Loading @@ -1553,12 +1567,17 @@ class ShortcutPackage extends ShortcutPackageItem { Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " is both dynamic and manifest at the same time."); } if (si.getActivity() == null) { if (!si.isFloating() && si.getActivity() == null) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " is not floating, but has null activity."); } if (si.isFloating() && si.getActivity() != null) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " has null activity."); + " is floating, but has non-null activity."); } if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) { if (!si.isFloating() && !si.isEnabled()) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " is not floating, but is disabled."); Loading @@ -1581,7 +1600,7 @@ class ShortcutPackage extends ShortcutPackageItem { } if (failed) { throw new IllegalStateException("See logcat for errors"); mShortcutUser.mService.verifyError(); } } Loading
services/core/java/com/android/server/pm/ShortcutService.java +13 −3 Original line number Diff line number Diff line Loading @@ -124,6 +124,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; Loading Loading @@ -401,6 +402,9 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) { if (DEBUG) { Binder.LOG_RUNTIME_EXCEPTION = true; } mContext = Preconditions.checkNotNull(context); LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); mHandler = new Handler(looper); Loading Loading @@ -1604,7 +1608,7 @@ public class ShortcutService extends IShortcutService.Stub { } if (!forUpdate) { shortcut.enforceMandatoryFields(); shortcut.enforceMandatoryFields(/* forPinned= */ false); Preconditions.checkArgument( injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), "Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity"); Loading Loading @@ -1752,6 +1756,9 @@ public class ShortcutService extends IShortcutService.Stub { // Note copyNonNullFieldsFrom() does the "updatable with?" check too. target.copyNonNullFieldsFrom(source); if (target.isFloating()) { target.setActivity(null); } target.setTimestamp(injectCurrentTimeMillis()); if (replacingIcon) { Loading Loading @@ -2320,8 +2327,7 @@ public class ShortcutService extends IShortcutService.Stub { return false; } if (componentName != null) { if (si.getActivity() != null && !si.getActivity().equals(componentName)) { if (!Objects.equals(componentName, si.getActivity())) { return false; } } Loading Loading @@ -3771,4 +3777,8 @@ public class ShortcutService extends IShortcutService.Stub { forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); } } void verifyError() { Slog.e(TAG, "See logcat for errors"); } }
services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +6 −0 Original line number Diff line number Diff line Loading @@ -376,6 +376,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { @Override boolean injectIsActivityEnabledAndExported(ComponentName activity, @UserIdInt int userId) { assertNotNull(activity); return mEnabledActivityChecker.test(activity, userId); } Loading Loading @@ -416,6 +417,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); } @Override void verifyError() { fail("Verify error"); } } /** ShortcutManager with injection override methods. */ Loading
services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +37 −1 Original line number Diff line number Diff line Loading @@ -1292,7 +1292,43 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /* activity =*/ null, /* flags */ 0), getCallingUser()); }); // TODO More tests: pinned but dynamic. // Make sure floating shortcuts don't match with an activity. // At this point, s1 is dynamic and pinned, so it still has a target activity. runWithCaller(LAUNCHER_1, USER_0, () -> { assertWith(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 0, CALLING_PACKAGE_2, /* activity =*/ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()), ShortcutQuery.FLAG_GET_PINNED), getCallingUser())) .haveIds("s3") .areAllPinned() .areAllDynamic() .areAllWithActivity(new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName())); }); // Now remove as a dynamic, making it floating. runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { mManager.removeDynamicShortcuts(list("s3")); assertWith(mManager.getPinnedShortcuts()) .selectFloating() .areAllWithNoActivity(); }); runWithCaller(LAUNCHER_1, USER_0, () -> { // This shouldn't match now. assertWith(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 0, CALLING_PACKAGE_2, /* activity =*/ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()), ShortcutQuery.FLAG_GET_PINNED), getCallingUser())) .isEmpty(); }); } public void testGetShortcuts_shortcutKinds() throws Exception { Loading