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;