Push chooser targets to the shortcut manager.

More info at go/direct-share-push

Test: Some basic testing currently in, more to be added.

Change-Id: I069f9779988c3eca2c4d8b83ec62501983854355
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c11131a..71bfa64 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -442,8 +442,8 @@
 
         @Override
         public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
-                String packageName, List shortcutIds, ComponentName componentName, int flags,
-                UserHandle targetUser) {
+                String packageName, List shortcutIds, ComponentName componentName, Intent intent,
+                int flags, UserHandle targetUser) {
             ensureShortcutPermission(callingPackage);
             if (!canAccessProfile(callingPackage, targetUser, "Cannot get shortcuts")
                     || !isUserEnabled(targetUser)) {
@@ -454,11 +454,17 @@
                         "To query by shortcut ID, package name must also be set");
             }
 
+            if ((flags & ShortcutQuery.FLAG_MATCH_CHOOSER) == 0
+                    && intent != null) {
+                throw new IllegalArgumentException("Supplied an intent in the query, but did "
+                        + "not request chooser targets");
+            }
+
             // TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below.
             return new ParceledListSlice<>((List<ShortcutInfo>)
                     mShortcutServiceInternal.getShortcuts(getCallingUserId(),
                             callingPackage, changedSince, packageName, shortcutIds,
-                            componentName, flags, targetUser.getIdentifier()));
+                            componentName, intent, flags, targetUser.getIdentifier()));
         }
 
         @Override
@@ -906,6 +912,7 @@
                                         cookie.packageName,
                                         /* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
                                         /* component= */ null,
+                                        /* intent= */ null,
                                         ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
                                         | ShortcutQuery.FLAG_GET_ALL_KINDS
                                         , userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 570259b..ac98ab9 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,6 +20,7 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Resources;
@@ -31,6 +32,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.ShortcutService.ShortcutOperation;
@@ -68,6 +70,9 @@
     private static final String TAG_EXTRAS = "extras";
     private static final String TAG_SHORTCUT = "shortcut";
     private static final String TAG_CATEGORIES = "categories";
+    private static final String TAG_CHOOSER_EXTRAS = "chooser-extras";
+    private static final String TAG_CHOOSER_INTENT_FILTERS = "chooser-intent-filters";
+    private static final String TAG_CHOOSER_COMPONENT_NAMES = "chooser-component-names";
 
     private static final String ATTR_NAME = "name";
     private static final String ATTR_CALL_COUNT = "call-count";
@@ -91,6 +96,7 @@
     private static final String ATTR_ICON_RES_ID = "icon-res";
     private static final String ATTR_ICON_RES_NAME = "icon-resname";
     private static final String ATTR_BITMAP_PATH = "bitmap-path";
+    private static final String ATTR_COMPONENT_NAMES = "component-names";
 
     private static final String NAME_CATEGORIES = "categories";
 
@@ -200,7 +206,7 @@
         if (shortcut != null) {
             mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
             shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
-                    | ShortcutInfo.FLAG_MANIFEST);
+                    | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CHOOSER);
         }
         return shortcut;
     }
@@ -226,7 +232,7 @@
         Preconditions.checkArgument(newShortcut.isEnabled(),
                 "add/setDynamicShortcuts() cannot publish disabled shortcuts");
 
-        newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
+        addCorrectDynamicFlags(newShortcut);
 
         final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
 
@@ -250,6 +256,17 @@
         addShortcutInner(newShortcut);
     }
 
+    // TODO: Sample code & JavaDoc for ShortcutManager needs updating to reflect the fact that
+    //       Chooser shortcuts are not always dynamic.
+    public void addCorrectDynamicFlags(@NonNull ShortcutInfo shortcut) {
+        if (shortcut.getIntent() != null) {
+            shortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
+        }
+        if (!ArrayUtils.isEmpty(shortcut.getChooserIntentFilters())) {
+            shortcut.addFlags(ShortcutInfo.FLAG_CHOOSER);
+        }
+    }
+
     /**
      * Remove all shortcuts that aren't pinned nor dynamic.
      */
@@ -282,11 +299,11 @@
         boolean changed = false;
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
-            if (si.isDynamic()) {
+            if (si.isDynamic() || si.isChooser()) {
                 changed = true;
 
                 si.setTimestamp(now);
-                si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+                si.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_CHOOSER);
                 si.setRank(0); // It may still be pinned, so clear the rank.
             }
         }
@@ -355,7 +372,8 @@
         if (oldShortcut.isPinned()) {
 
             oldShortcut.setRank(0);
-            oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
+            oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST
+                    | ShortcutInfo.FLAG_CHOOSER);
             if (disable) {
                 oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
             }
@@ -1116,8 +1134,8 @@
                     // Don't adjust ranks for manifest shortcuts.
                     continue;
                 }
-                // At this point, it must be dynamic.
-                if (!si.isDynamic()) {
+                // At this point, it must be dynamic or a chooser.
+                if (!si.isDynamicOrChooser()) {
                     s.wtf("Non-dynamic shortcut found.");
                     continue;
                 }
@@ -1294,7 +1312,7 @@
             ShortcutService.writeAttr(out, ATTR_FLAGS,
                     si.getFlags() &
                             ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
-                            | ShortcutInfo.FLAG_DYNAMIC));
+                            | ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_CHOOSER));
         } else {
             // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
             // as dynamic.
@@ -1317,15 +1335,36 @@
         }
         final Intent[] intentsNoExtras = si.getIntentsNoExtras();
         final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
-        final int numIntents = intentsNoExtras.length;
-        for (int i = 0; i < numIntents; i++) {
-            out.startTag(null, TAG_INTENT);
-            ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
-            ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
-            out.endTag(null, TAG_INTENT);
+        if (intentsNoExtras != null) {
+            final int numIntents = intentsNoExtras.length;
+            for (int i = 0; i < numIntents; i++) {
+                out.startTag(null, TAG_INTENT);
+                ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+                ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+                out.endTag(null, TAG_INTENT);
+            }
+        }
+        ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+
+        ShortcutService.writeTagExtra(out, TAG_CHOOSER_EXTRAS, si.getChooserExtras());
+
+        final IntentFilter[] intentFilters = si.getChooserIntentFilters();
+        if (intentFilters != null) {
+            for (int i = 0; i < intentFilters.length; i++) {
+                out.startTag(null, TAG_CHOOSER_INTENT_FILTERS);
+                intentFilters[i].writeToXml(out);
+                out.endTag(null, TAG_CHOOSER_INTENT_FILTERS);
+            }
         }
 
-        ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+        final ComponentName[] componentNames = si.getChooserComponentNames();
+        if (componentNames != null) {
+            for (int i = 0; i < componentNames.length; i++) {
+                out.startTag(null, TAG_CHOOSER_COMPONENT_NAMES);
+                ShortcutService.writeAttr(out, ATTR_COMPONENT_NAMES, componentNames[i]);
+                out.endTag(null, TAG_CHOOSER_COMPONENT_NAMES);
+            }
+        }
 
         out.endTag(null, TAG_SHORTCUT);
     }
@@ -1398,6 +1437,9 @@
         String iconResName;
         String bitmapPath;
         ArraySet<String> categories = null;
+        PersistableBundle chooserExtras;
+        List<IntentFilter> chooserIntentFilters = new ArrayList<>();
+        List<ComponentName> chooserComponentNames = new ArrayList<>();
 
         id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
         activityComponent = ShortcutService.parseComponentNameAttribute(parser,
@@ -1458,6 +1500,18 @@
                         }
                     }
                     continue;
+                case TAG_CHOOSER_EXTRAS:
+                    chooserExtras = PersistableBundle.restoreFromXml(parser);
+                    continue;
+                case TAG_CHOOSER_COMPONENT_NAMES:
+                    chooserComponentNames.add(ShortcutService.parseComponentNameAttribute(parser,
+                            ATTR_ACTIVITY));
+                    continue;
+                case TAG_CHOOSER_INTENT_FILTERS:
+                    IntentFilter toAdd = new IntentFilter();
+                    toAdd.readFromXml(parser);
+                    chooserIntentFilters.add(toAdd);
+                    continue;
             }
             throw ShortcutService.throwForInvalidTag(depth, tag);
         }
@@ -1551,10 +1605,10 @@
         // Verify each shortcut's status.
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
-            if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned())) {
+            if (!(si.isDeclaredInManifest() || si.isDynamicOrChooser() || si.isPinned())) {
                 failed = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
-                        + " is not manifest, dynamic or pinned.");
+                        + " is not manifest, dynamic, chooser or pinned.");
             }
             if (si.isDeclaredInManifest() && si.isDynamic()) {
                 failed = true;
@@ -1596,6 +1650,11 @@
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has a dummy target activity");
             }
+            if (si.getIntent() == null && !si.isChooser()) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " has a null intent, but is not a chooser");
+            }
         }
 
         if (failed) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 057e781..74eb340 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -27,6 +27,7 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -64,6 +65,7 @@
 import android.os.Handler;
 import android.os.LocaleList;
 import android.os.Looper;
+import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -1750,6 +1752,7 @@
             ps.clearAllImplicitRanks();
             assignImplicitRanks(newShortcuts);
 
+            // TODO: Consider removing Chooser fields. If so, the FLAG_CHOOSER should be removed
             for (int i = 0; i < size; i++) {
                 final ShortcutInfo source = newShortcuts.get(i);
                 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
@@ -1789,6 +1792,13 @@
                 if (replacingIcon || source.hasStringResources()) {
                     fixUpShortcutResourceNamesAndValues(target);
                 }
+
+                // While updating, we keep the dynamic flag as it previously was, but refresh the
+                // chooser flag.
+                // TODO: If we support clearing Chooser fields, we should also remove the flag.
+                if (target.getChooserIntentFilters() != null) {
+                    target.addFlags(ShortcutInfo.FLAG_CHOOSER);
+                }
             }
 
             // Lastly, adjust the ranks.
@@ -1852,6 +1862,7 @@
         return true;
     }
 
+    // TODO: Ensure non-launchable shortcuts can not be pinned
     @Override
     public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
             IntentSender resultIntent, int userId) {
@@ -2007,7 +2018,7 @@
 
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isDynamic);
+                    ShortcutInfo::isDynamicOrChooser);
         }
     }
 
@@ -2200,6 +2211,14 @@
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
+            // For the chooser, we just check is the system is calling.
+            // STOPSHIP: We need to implement a new permission here rather than this terrible check.
+            //           The packageName check is to try to distinguish between when an actual
+            //           launcher is making the call, and when it's the system.
+            if (isCallerSystem() && packageName.equals("android")) {
+                return true;
+            }
+
             final ShortcutUser user = getUserShortcutsLocked(userId);
 
             // Always trust the cached component.
@@ -2372,7 +2391,7 @@
         public List<ShortcutInfo> getShortcuts(int launcherUserId,
                 @NonNull String callingPackage, long changedSince,
                 @Nullable String packageName, @Nullable List<String> shortcutIds,
-                @Nullable ComponentName componentName,
+                @Nullable ComponentName componentName, @Nullable Intent intent,
                 int queryFlags, int userId) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
@@ -2394,13 +2413,13 @@
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
                             callingPackage, packageName, shortcutIds, changedSince,
-                            componentName, queryFlags, userId, ret, cloneFlag);
+                            componentName, intent, queryFlags, userId, ret, cloneFlag);
                 } else {
                     final List<String> shortcutIdsF = shortcutIds;
                     getUserShortcutsLocked(userId).forAllPackages(p -> {
                         getShortcutsInnerLocked(launcherUserId,
                                 callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
-                                componentName, queryFlags, userId, ret, cloneFlag);
+                                componentName, intent, queryFlags, userId, ret, cloneFlag);
                     });
                 }
             }
@@ -2409,7 +2428,7 @@
 
         private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
                 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
-                @Nullable ComponentName componentName, int queryFlags,
+                @Nullable ComponentName componentName, Intent intent, int queryFlags,
                 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
             final ArraySet<String> ids = shortcutIds == null ? null
                     : new ArraySet<>(shortcutIds);
@@ -2434,6 +2453,15 @@
                                 return false;
                             }
                         }
+                        if (intent != null
+                                && !si.hasMatchingFilter(mContext.getContentResolver(), intent)) {
+                            return false;
+                        }
+
+                        if (((queryFlags & ShortcutQuery.FLAG_MATCH_CHOOSER) != 0)
+                                && si.isChooser()) {
+                            return true;
+                        }
                         if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
                                 && si.isDynamic()) {
                             return true;