Launcher shortcut callback should deliver manifest shortcuts too

- Also include "activity" in the key fields, as this is now an
important field.

- Also optimize ShortcutInfo parceling for the "key field only" case.

Bug 29394043
Bug 29451629

Change-Id: I61b2bc2f61ad6ebdcbaf6d02f1bd88777c45a7f0
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6cb50fc..a76bf24 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -171,8 +171,9 @@
          * 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).  Only "key"
-         *    information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
+         * @param shortcuts all shortcuts from the package (dynamic, manifest and/or pinned).
+         *    Only "key" information will be provided, as defined in
+         *    {@link ShortcutInfo#hasKeyFieldsOnly()}.
          * @param user The UserHandle of the profile that generated the change.
          */
         public void onShortcutsChanged(@NonNull String packageName,
@@ -199,6 +200,10 @@
          */
         public static final int FLAG_GET_MANIFEST = 1 << 3;
 
+        /** @hide */
+        public static final int FLAG_GET_ALL_KINDS =
+                FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
+
         /**
          * Requests "key" fields only.  See {@link ShortcutInfo#hasKeyFieldsOnly()} for which
          * fields are available.
@@ -485,7 +490,7 @@
         final ShortcutQuery q = new ShortcutQuery();
         q.setPackage(packageName);
         q.setShortcutIds(ids);
-        q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
         return getShortcuts(q, user);
     }
 
@@ -526,7 +531,7 @@
         final ShortcutQuery q = new ShortcutQuery();
         q.setPackage(packageName);
         q.setShortcutIds(Arrays.asList(shortcutId));
-        q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
         final List<ShortcutInfo> shortcuts = getShortcuts(q, user);
 
         return shortcuts.size() > 0 ? shortcuts.get(0).getIconResourceId() : 0;
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 064b909..8492fd8 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -294,6 +294,7 @@
         mUserId = source.mUserId;
         mId = source.mId;
         mPackageName = source.mPackageName;
+        mActivity = source.mActivity;
         mFlags = source.mFlags;
         mLastChangedTimestamp = source.mLastChangedTimestamp;
 
@@ -301,7 +302,6 @@
         mIconResId = source.mIconResId;
 
         if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
-            mActivity = source.mActivity;
 
             if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
                 mIcon = source.mIcon;
@@ -1252,6 +1252,7 @@
      * <ul>
      *     <li>{@link #getId()}
      *     <li>{@link #getPackage()}
+     *     <li>{@link #getActivity()}
      *     <li>{@link #getLastChangedTimestamp()}
      *     <li>{@link #isDynamic()}
      *     <li>{@link #isPinned()}
@@ -1403,6 +1404,14 @@
         mId = source.readString();
         mPackageName = source.readString();
         mActivity = source.readParcelable(cl);
+        mFlags = source.readInt();
+        mIconResId = source.readInt();
+        mLastChangedTimestamp = source.readLong();
+
+        if (source.readInt() == 0) {
+            return; // key information only.
+        }
+
         mIcon = source.readParcelable(cl);
         mTitle = source.readCharSequence();
         mTitleResId = source.readInt();
@@ -1414,9 +1423,6 @@
         mIntentPersistableExtras = source.readParcelable(cl);
         mRank = source.readInt();
         mExtras = source.readParcelable(cl);
-        mLastChangedTimestamp = source.readLong();
-        mFlags = source.readInt();
-        mIconResId = source.readInt();
         mBitmapPath = source.readString();
 
         mIconResName = source.readString();
@@ -1441,6 +1447,16 @@
         dest.writeString(mId);
         dest.writeString(mPackageName);
         dest.writeParcelable(mActivity, flags);
+        dest.writeInt(mFlags);
+        dest.writeInt(mIconResId);
+        dest.writeLong(mLastChangedTimestamp);
+
+        if (hasKeyFieldsOnly()) {
+            dest.writeInt(0);
+            return;
+        }
+        dest.writeInt(1);
+
         dest.writeParcelable(mIcon, flags);
         dest.writeCharSequence(mTitle);
         dest.writeInt(mTitleResId);
@@ -1453,9 +1469,6 @@
         dest.writeParcelable(mIntentPersistableExtras, flags);
         dest.writeInt(mRank);
         dest.writeParcelable(mExtras, flags);
-        dest.writeLong(mLastChangedTimestamp);
-        dest.writeInt(mFlags);
-        dest.writeInt(mIconResId);
         dest.writeString(mBitmapPath);
 
         dest.writeString(mIconResName);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 43a0b91..6e006f1 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -775,8 +775,7 @@
                                     /* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
                                     /* component= */ null,
                                     ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
-                                    | ShortcutQuery.FLAG_GET_PINNED
-                                    | ShortcutQuery.FLAG_GET_DYNAMIC
+                                    | ShortcutQuery.FLAG_GET_ALL_KINDS
                                     , userId);
                     try {
                         listener.onShortcutChanged(user, packageName,
diff --git a/services/tests/servicestests/res/xml/shortcut_3.xml b/services/tests/servicestests/res/xml/shortcut_3.xml
new file mode 100644
index 0000000..432ca49
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_3.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:icon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        >
+        <intent
+            android:action="action1"
+            android:data="http://a.b.c/"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms2"
+        android:enabled="true"
+        android:icon="@drawable/icon2"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        >
+        <intent
+            android:action="action2"
+            android:data="http://a.b.c/2"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms3"
+        android:enabled="true"
+        android:icon="@drawable/icon3"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        >
+        <intent android:action="action3" />
+    </shortcut>
+</shortcuts>
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 69152d4..b8b67e9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -532,7 +532,7 @@
 
     static {
         QUERY_ALL.setQueryFlags(
-                ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+                ShortcutQuery.FLAG_GET_ALL_KINDS);
     }
 
     @Override
@@ -1434,7 +1434,7 @@
     protected static ShortcutQuery buildAllQuery(String packageName) {
         final ShortcutQuery q = new ShortcutQuery();
         q.setPackage(packageName);
-        q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
         return q;
     }
 
@@ -1445,6 +1445,12 @@
         return q;
     }
 
+    protected static ShortcutQuery buildQueryWithFlags(int queryFlags) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setQueryFlags(queryFlags);
+        return q;
+    }
+
     protected void backupAndRestore() {
         int prevUid = mInjectedCallingUid;
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 56232c0..0894323 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1102,6 +1102,117 @@
         // TODO More tests: pinned but dynamic.
     }
 
+    public void testGetShortcuts_shortcutKinds() throws Exception {
+        // Create 3 manifest and 3 dynamic shortcuts
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_3);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        // Pin 2 and 3
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "ms3", "s2", "s3"),
+                    HANDLE_USER_0);
+        });
+
+        // Remove ms3 and s3
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"))));
+        });
+
+        // Check their status.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1", "ms2", "ms3", "s1", "s2", "s3")
+
+                    .selectByIds("ms1", "ms2")
+                    .areAllManifest()
+                    .areAllImmutable()
+                    .areAllNotDynamic()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms3")
+                    .areAllNotManifest()
+                    .areAllImmutable()
+                    .areAllDisabled()
+                    .areAllNotDynamic()
+
+                    .revertToOriginalList()
+                    .selectByIds("s1", "s2")
+                    .areAllNotManifest()
+                    .areAllMutable()
+                    .areAllDynamic()
+
+                    .revertToOriginalList()
+                    .selectByIds("s3")
+                    .areAllNotManifest()
+                    .areAllMutable()
+                    .areAllEnabled()
+                    .areAllNotDynamic()
+
+                    .revertToOriginalList()
+                    .selectByIds("s1", "ms1")
+                    .areAllNotPinned()
+
+                    .revertToOriginalList()
+                    .selectByIds("s2", "s3", "ms2", "ms3")
+                    .areAllPinned()
+            ;
+        });
+
+        // Finally, actual tests.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertWith(mLauncherApps.getShortcuts(
+                    buildQueryWithFlags(ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0))
+                    .haveIds("s1", "s2");
+            assertWith(mLauncherApps.getShortcuts(
+                    buildQueryWithFlags(ShortcutQuery.FLAG_GET_MANIFEST), HANDLE_USER_0))
+                    .haveIds("ms1", "ms2");
+            assertWith(mLauncherApps.getShortcuts(
+                    buildQueryWithFlags(ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0))
+                    .haveIds("s2", "s3", "ms2", "ms3");
+
+            assertWith(mLauncherApps.getShortcuts(
+                    buildQueryWithFlags(
+                            ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED
+                    ), HANDLE_USER_0))
+                    .haveIds("s1", "s2", "s3", "ms2", "ms3");
+
+            assertWith(mLauncherApps.getShortcuts(
+                    buildQueryWithFlags(
+                            ShortcutQuery.FLAG_GET_MANIFEST | ShortcutQuery.FLAG_GET_PINNED
+                    ), HANDLE_USER_0))
+                    .haveIds("ms1", "s2", "s3", "ms2", "ms3");
+
+            assertWith(mLauncherApps.getShortcuts(
+                    buildQueryWithFlags(
+                            ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_MANIFEST
+                    ), HANDLE_USER_0))
+                    .haveIds("ms1", "ms2", "s1", "s2");
+
+            assertWith(mLauncherApps.getShortcuts(
+                    buildQueryWithFlags(
+                            ShortcutQuery.FLAG_GET_ALL_KINDS
+                    ), HANDLE_USER_0))
+                    .haveIds("ms1", "ms2", "ms3", "s1", "s2", "s3");
+        });
+    }
+
     public void testGetShortcuts_resolveStrings() throws Exception {
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
@@ -2223,6 +2334,12 @@
     }
 
     public void testLauncherCallback() throws Throwable {
+        // Disable throttling for this test.
+        mService.updateConfigurationLocked(
+                ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
+                        + ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
+        );
+
         LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
 
         // Set listeners
@@ -2243,8 +2360,10 @@
                 shortcuts.capture(),
                 eq(HANDLE_USER_0)
         );
-        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
-                "s1", "s2", "s3");
+        assertWith(shortcuts.getValue())
+                .haveIds("s1", "s2", "s3")
+                .areAllWithKeyFieldsOnly()
+                .areAllDynamic();
 
         // From different package.
         reset(c0);
@@ -2259,8 +2378,10 @@
                 shortcuts.capture(),
                 eq(HANDLE_USER_0)
         );
-        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
-                "s1", "s2", "s3");
+        assertWith(shortcuts.getValue())
+                .haveIds("s1", "s2", "s3")
+                .areAllWithKeyFieldsOnly()
+                .areAllDynamic();
 
         // Different user, callback shouldn't be called.
         reset(c0);
@@ -2289,8 +2410,10 @@
                 shortcuts.capture(),
                 eq(HANDLE_USER_0)
         );
-        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
-                "s1", "s2", "s3", "s4");
+        assertWith(shortcuts.getValue())
+                .haveIds("s1", "s2", "s3", "s4")
+                .areAllWithKeyFieldsOnly()
+                .areAllDynamic();
 
         // Test for remove
         reset(c0);
@@ -2305,8 +2428,10 @@
                 shortcuts.capture(),
                 eq(HANDLE_USER_0)
         );
-        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
-                "s2", "s3", "s4");
+        assertWith(shortcuts.getValue())
+                .haveIds("s2", "s3", "s4")
+                .areAllWithKeyFieldsOnly()
+                .areAllDynamic();
 
         // Test for update
         reset(c0);
@@ -2322,8 +2447,10 @@
                 shortcuts.capture(),
                 eq(HANDLE_USER_0)
         );
-        assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
-                "s2", "s3", "s4");
+        assertWith(shortcuts.getValue())
+                .haveIds("s2", "s3", "s4")
+                .areAllWithKeyFieldsOnly()
+                .areAllDynamic();
 
         // Test for deleteAll
         reset(c0);
@@ -2338,7 +2465,100 @@
                 shortcuts.capture(),
                 eq(HANDLE_USER_0)
         );
-        assertEquals(0, shortcuts.getValue().size());
+        assertWith(shortcuts.getValue())
+                .isEmpty();
+
+        // Update package1 with manifest shortcuts
+        reset(c0);
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertWith(shortcuts.getValue())
+                .areAllManifest()
+                .areAllWithKeyFieldsOnly()
+                .haveIds("ms1", "ms2");
+
+        // Make sure pinned shortcuts are passed too.
+        // 1. Add dynamic shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"))));
+        });
+
+        // 2. Pin some.
+        runWithCaller(LAUNCHER_1, UserHandle.USER_SYSTEM, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "s2"), HANDLE_USER_0);
+        });
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1", "ms2", "s1", "s2")
+                    .areAllEnabled()
+
+                    .selectByIds("ms1", "ms2")
+                    .areAllManifest()
+
+                    .revertToOriginalList()
+                    .selectByIds("s1", "s2")
+                    .areAllDynamic()
+                    ;
+        });
+
+        // 3 Update the app with no manifest shortcuts.  (Pinned one will survive.)
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_0);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        reset(c0); // Check the callback for the next API call.
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            mManager.removeDynamicShortcuts(list("s2"));
+
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms2", "s1", "s2")
+
+                    .selectByIds("ms2")
+                    .areAllNotManifest()
+                    .areAllPinned()
+                    .areAllImmutable()
+                    .areAllDisabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned()
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("s2")
+                    .areAllNotDynamic()
+                    .areAllPinned()
+                    .areAllEnabled()
+                    ;
+        });
+
+        waitOnMainThread();
+        shortcuts = ArgumentCaptor.forClass(List.class);
+        verify(c0).onShortcutsChanged(
+                eq(CALLING_PACKAGE_1),
+                shortcuts.capture(),
+                eq(HANDLE_USER_0)
+        );
+        assertWith(shortcuts.getValue())
+                .haveIds("ms2", "s1", "s2")
+                .areAllWithKeyFieldsOnly();
 
         // Remove CALLING_PACKAGE_2
         reset(c0);
@@ -2353,7 +2573,8 @@
                 shortcuts.capture(),
                 eq(HANDLE_USER_0)
         );
-        assertEquals(0, shortcuts.getValue().size());
+        assertWith(shortcuts.getValue())
+                .isEmpty();
     }
 
     public void testLauncherCallback_crossProfile() throws Throwable {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 428b872..fdd8f14 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -329,7 +329,7 @@
 
         assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(null, si.getActivity());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals(null, si.getTitle());
         assertEquals(null, si.getText());
@@ -445,7 +445,7 @@
 
         assertEquals(mClientContext.getPackageName(), si.getPackage());
         assertEquals("id", si.getId());
-        assertEquals(null, si.getActivity());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
         assertEquals(null, si.getIcon());
         assertEquals(0, si.getTitleResId());
         assertEquals(null, si.getTitleResName());
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 712bc1e..d9dbd5a 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -806,11 +806,24 @@
             return this;
         }
 
+        public ShortcutListAsserter areAllWithKeyFieldsOnly() {
+            forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.hasKeyFieldsOnly()));
+            return this;
+        }
+
+        public ShortcutListAsserter areAllNotWithKeyFieldsOnly() {
+            forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.hasKeyFieldsOnly()));
+            return this;
+        }
+
         public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
+            boolean found = false;
             for (int i = 0; i < mList.size(); i++) {
                 final ShortcutInfo si = mList.get(i);
+                found = true;
                 sa.accept(si);
             }
+            assertTrue("No shortcuts found.", found);
             return this;
         }