Merge "Push chooser targets to the shortcut manager."

This commit is contained in:
TreeHugger Robot
2017-02-27 20:24:52 +00:00
committed by Android (Google) Code Review
17 changed files with 824 additions and 127 deletions

View File

@@ -55,7 +55,8 @@ interface ILauncherApps {
String callingPackage, String packageName, int flags, in UserHandle user);
ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
in List shortcutIds, in ComponentName componentName, in Intent intent, int flags,
in UserHandle user);
void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
in UserHandle user);
boolean startShortcut(String callingPackage, String packageName, String id,

View File

@@ -275,7 +275,18 @@ public class LauncherApps {
@Deprecated
public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
/** @hide */
/**
* Include chooser shortcuts in the result.
* STOPSHIP TODO: Unless explicitly requesting chooser fields, we should strip out chooser
* relevant fields from the Shortcut. This should also be adequately documented.
*/
public static final int FLAG_MATCH_CHOOSER = 1 << 4;
/**
* Does not retrieve CHOOSER only shortcuts.
* TODO: Add another flag for MATCH_ALL_PINNED
* @hide
*/
public static final int FLAG_MATCH_ALL_KINDS =
FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
@@ -308,6 +319,7 @@ public class LauncherApps {
FLAG_MATCH_DYNAMIC,
FLAG_MATCH_PINNED,
FLAG_MATCH_MANIFEST,
FLAG_MATCH_CHOOSER,
FLAG_GET_KEY_FIELDS_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
@@ -324,6 +336,9 @@ public class LauncherApps {
@Nullable
ComponentName mActivity;
@Nullable
Intent mIntent;
@QueryFlags
int mQueryFlags;
@@ -367,6 +382,14 @@ public class LauncherApps {
return this;
}
/**
* If non-null, returns only shortcuts with intent filters that match this intent.
*/
public ShortcutQuery setIntent(@Nullable Intent intent) {
mIntent = intent;
return this;
}
/**
* Set query options. At least one of the {@code MATCH} flags should be set. Otherwise,
* no shortcuts will be returned.
@@ -681,7 +704,7 @@ public class LauncherApps {
try {
return mService.getShortcuts(mContext.getPackageName(),
query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
query.mQueryFlags, user)
query.mIntent, query.mQueryFlags, user)
.getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();

View File

@@ -21,8 +21,10 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.TaskStackBuilder;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
@@ -38,10 +40,12 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -95,6 +99,14 @@ public final class ShortcutInfo implements Parcelable {
public static final int FLAG_MASKABLE_BITMAP = 1 << 9;
/** @hide */
public static final int FLAG_CHOOSER = 1 << 10;
/**
* TODO: Add FLAG_CHOOSER_INFO_OMITTED to reflect that chooser info was omitted in the Shortcut
* due to the context in which it was retrieved.
* TODO: Add a FLAG_LAUNCHABLE to reflect whether or not the Shortcut has a launchable intent
* @hide
*/
@IntDef(flag = true,
value = {
FLAG_DYNAMIC,
@@ -107,6 +119,7 @@ public final class ShortcutInfo implements Parcelable {
FLAG_STRINGS_RESOLVED,
FLAG_IMMUTABLE,
FLAG_MASKABLE_BITMAP,
FLAG_CHOOSER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
@@ -201,6 +214,24 @@ public final class ShortcutInfo implements Parcelable {
@Nullable
private PersistableBundle[] mIntentPersistableExtrases;
/**
* If used in a chooser, extras that should be added into the intent passed through.
*/
@Nullable
private PersistableBundle mChooserExtras;
/**
* Intent filters to be used if the shortcut is to be used in a chooser context.
*/
@Nullable
private IntentFilter[] mChooserIntentFilters;
/**
* Component names corresponding to the above intent filters.
*/
@Nullable
private ComponentName[] mChooserComponentNames;
private int mRank;
/**
@@ -250,6 +281,13 @@ public final class ShortcutInfo implements Parcelable {
mDisabledMessageResId = b.mDisabledMessageResId;
mCategories = cloneCategories(b.mCategories);
mIntents = cloneIntents(b.mIntents);
if (b.mChooserIntentFilters != null) {
mChooserIntentFilters = b.mChooserIntentFilters.toArray(new IntentFilter[0]);
}
if (b.mChooserComponentNames != null) {
mChooserComponentNames = b.mChooserComponentNames.toArray(new ComponentName[0]);
}
mChooserExtras = b.mChooserExtras;
fixUpIntentExtras();
mRank = b.mRank;
mExtras = b.mExtras;
@@ -330,8 +368,28 @@ public final class ShortcutInfo implements Parcelable {
if (mTitle == null && mTitleResId == 0) {
throw new IllegalArgumentException("Short label must be provided");
}
Preconditions.checkNotNull(mIntents, "Shortcut Intent must be provided");
Preconditions.checkArgument(mIntents.length > 0, "Shortcut Intent must be provided");
// For a shortcut to be valid, there should either be an Intent, or a non-empty set of
// intent filters.
if (mIntents == null || mIntents.length == 0) {
Preconditions.checkNotNull(mChooserIntentFilters,
"Intent must be provided if not a chooser target");
Preconditions.checkNotNull(mChooserComponentNames,
"Intent must be provided if not a chooser target");
}
// If ChooserIntentFilter are provided, they should match the length of the provided
// component names.
if (mChooserIntentFilters != null) {
if (mChooserComponentNames == null
|| mChooserIntentFilters.length != mChooserComponentNames.length) {
throw new IllegalArgumentException("Inconsistent intent filters and "
+ "component names given");
}
if (mChooserIntentFilters.length == 0 || mChooserComponentNames.length == 0) {
throw new IllegalArgumentException("Empty intent filter and component names given");
}
}
}
/**
@@ -376,6 +434,10 @@ public final class ShortcutInfo implements Parcelable {
mDisabledMessageResName = source.mDisabledMessageResName;
mIconResName = source.mIconResName;
}
// TODO: Omit these by default and add a new clone flag.
mChooserIntentFilters = source.mChooserIntentFilters;
mChooserComponentNames = source.mChooserComponentNames;
mChooserExtras = source.mChooserExtras;
} else {
// Set this bit.
mFlags |= FLAG_KEY_FIELDS_ONLY;
@@ -502,6 +564,25 @@ public final class ShortcutInfo implements Parcelable {
return fullResourceName.substring(p1 + 1);
}
/**
* Whether the shortcut has any intentFilter matching the passed in one.
* @hide
*/
@VisibleForTesting
public boolean hasMatchingFilter(ContentResolver resolver, Intent intent) {
if (mChooserIntentFilters == null) {
return false;
}
for (IntentFilter filter : mChooserIntentFilters) {
int match = filter.match(resolver, intent, false, TAG);
if (match > 0) {
return true;
}
}
return false;
}
/**
* Extract the entry name from a fully-donated resource name.
* e.g. "com.android.app1:drawable/icon1" -> "icon1"
@@ -685,6 +766,15 @@ public final class ShortcutInfo implements Parcelable {
if (source.mExtras != null) {
mExtras = source.mExtras;
}
if (source.mChooserExtras != null) {
mChooserExtras = source.mChooserExtras;
}
if (source.mChooserIntentFilters != null) {
mChooserIntentFilters = source.mChooserIntentFilters;
}
if (source.mChooserComponentNames != null) {
mChooserComponentNames = source.mChooserComponentNames;
}
}
/**
@@ -746,6 +836,12 @@ public final class ShortcutInfo implements Parcelable {
private PersistableBundle mExtras;
private PersistableBundle mChooserExtras;
private List<IntentFilter> mChooserIntentFilters;
private List<ComponentName> mChooserComponentNames;
/**
* Old style constructor.
* @hide
@@ -1031,6 +1127,40 @@ public final class ShortcutInfo implements Parcelable {
return this;
}
/**
* Extras that can be added which will be added to the Intent used to launch the app if
* launched from a chooser context.
*/
@NonNull
public Builder setChooserExtras(@NonNull PersistableBundle extras) {
mChooserExtras = extras;
return this;
}
/**
* IntentFilters and the components that should resolve a match for a given chooser target.
* If multiple matches are found, the component corresponding to the closest match will be
* used.
*
* @param filter IntendFilter that if matched will have the intent forwarded to the given
* component
* @param name The component that an intent that passes this filter will resolve to.
*/
public Builder addChooserIntentFilter(@NonNull IntentFilter filter,
@NonNull ComponentName name) {
Preconditions.checkNotNull(filter, "intent filter cannot be null");
Preconditions.checkNotNull(name, "component name cannot be null");
if (mChooserIntentFilters == null || mChooserComponentNames == null) {
mChooserIntentFilters = new ArrayList<>();
mChooserComponentNames = new ArrayList<>();
}
mChooserIntentFilters.add(filter);
mChooserComponentNames.add(name);
return this;
}
/**
* Creates a {@link ShortcutInfo} instance.
*/
@@ -1231,6 +1361,30 @@ public final class ShortcutInfo implements Parcelable {
return mIntentPersistableExtrases;
}
/**
* Retrieve the extras that will be added in to any intent launched through the chooser.
*/
@NonNull
public PersistableBundle getChooserExtras() {
return mChooserExtras;
}
/**
* Retrieve the list of intent filters for chooser targets.
*/
@NonNull
public IntentFilter[] getChooserIntentFilters() {
return mChooserIntentFilters;
}
/**
* Retrieve the list of component names corresponding to the above intent filters.
*/
@NonNull
public ComponentName[] getChooserComponentNames() {
return mChooserComponentNames;
}
/**
* "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each
* {@link #getActivity} for each of the two types of shortcuts (static and dynamic).
@@ -1352,6 +1506,11 @@ public final class ShortcutInfo implements Parcelable {
return hasFlags(FLAG_PINNED);
}
/** Return whether a shortcut can be shown in the chooser. */
public boolean isChooser() {
return hasFlags(FLAG_CHOOSER);
}
/**
* Return whether a shortcut is static; that is, whether a shortcut is
* published from AndroidManifest.xml. If {@code true}, the shortcut is
@@ -1380,6 +1539,14 @@ public final class ShortcutInfo implements Parcelable {
return isPinned() && !(isDynamic() || isManifestShortcut());
}
/**
* @return true if pinned but neither static nor dynamic.
* @hide
*/
public boolean isDynamicOrChooser() {
return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_CHOOSER);
}
/** @hide */
public boolean isOriginallyFromManifest() {
return hasFlags(FLAG_IMMUTABLE);
@@ -1661,6 +1828,19 @@ public final class ShortcutInfo implements Parcelable {
mCategories.add(source.readString().intern());
}
}
// We put a placeholder empty array in to keep the parcelable order, but can do away with
// them at this point if they're empty.
mChooserComponentNames = source.readParcelableArray(cl, ComponentName.class);
if (mChooserComponentNames.length == 0) {
mChooserComponentNames = null;
}
mChooserIntentFilters = source.readParcelableArray(cl, IntentFilter.class);
if (mChooserIntentFilters.length == 0) {
mChooserIntentFilters = null;
}
mChooserExtras = source.readPersistableBundle(cl);
}
@Override
@@ -1707,6 +1887,17 @@ public final class ShortcutInfo implements Parcelable {
} else {
dest.writeInt(0);
}
if (mChooserComponentNames != null) {
dest.writeParcelableArray(mChooserComponentNames, flags);
} else {
dest.writeParcelableArray(new ComponentName[0], flags);
}
if (mChooserIntentFilters != null) {
dest.writeParcelableArray(mChooserIntentFilters, flags);
} else {
dest.writeParcelableArray(new IntentFilter[0], flags);
}
dest.writePersistableBundle(mChooserExtras);
}
public static final Creator<ShortcutInfo> CREATOR =

View File

@@ -44,8 +44,8 @@ public abstract class ShortcutServiceInternal {
getShortcuts(int launcherUserId,
@NonNull String callingPackage, long changedSince,
@Nullable String packageName, @Nullable List<String> shortcutIds,
@Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
int userId);
@Nullable ComponentName componentName, @Nullable Intent intent,
@ShortcutQuery.QueryFlags int flags, int userId);
public abstract boolean
isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,

View File

@@ -23,15 +23,21 @@ import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.LabeledIntent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
@@ -356,6 +362,7 @@ public class ChooserActivity extends ResolverActivity {
mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets));
}
mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
mChooserRowAdapter.updateRowScales();
mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
adapterView.setAdapter(mChooserRowAdapter);
if (listView != null) {
@@ -842,7 +849,9 @@ public class ChooserActivity extends ResolverActivity {
return false;
}
intent.setComponent(mChooserTarget.getComponentName());
intent.putExtras(mChooserTarget.getIntentExtras());
if (mChooserTarget.getIntentExtras() != null) {
intent.putExtras(mChooserTarget.getIntentExtras());
}
// Important: we will ignore the target security checks in ActivityManager
// if and only if the ChooserTarget's target package is the same package
@@ -925,6 +934,8 @@ public class ChooserActivity extends ResolverActivity {
private static final int MAX_SERVICE_TARGETS = 8;
private static final int MAX_TARGETS_PER_SERVICE = 4;
private boolean mAreChooserShortcutsRetrieved;
private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
private final List<TargetInfo> mCallerTargets = new ArrayList<>();
private boolean mShowServiceTargets;
@@ -1016,6 +1027,20 @@ public class ChooserActivity extends ResolverActivity {
if (mServiceTargets != null) {
pruneServiceTargets();
}
if (DEBUG) Log.d(TAG, "Adding pushed chooser targets");
if (!mAreChooserShortcutsRetrieved) {
LauncherApps launcherApps = getLauncherApps();
LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
query.setIntent(getTargetIntent());
query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_CHOOSER);
List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, UserHandle.SYSTEM);
if (DEBUG) Log.d(TAG, "Adding " + shortcuts.size() + " chooser shortcuts");
addShortcuts(shortcuts);
mAreChooserShortcutsRetrieved = true;
}
if (DEBUG) Log.d(TAG, "List built querying services");
queryTargetServices(this);
}
@@ -1041,6 +1066,7 @@ public class ChooserActivity extends ResolverActivity {
public int getServiceTargetCount() {
if (!mShowServiceTargets) {
if (DEBUG) Log.d("TAG", "Hiding service targets");
return 0;
}
return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
@@ -1132,6 +1158,71 @@ public class ChooserActivity extends ResolverActivity {
notifyDataSetChanged();
}
// TODO: Pushed targets need to be scored correctly
public void addShortcuts(List<ShortcutInfo> infos) {
for (ShortcutInfo info : infos) {
List<ChooserTarget> newTargets = new ArrayList<>();
final ComponentName cn = info.getActivity();
ActivityInfo ai;
ResolveInfo ri = new ResolveInfo();
if (cn != null) {
try {
ai = getPackageManager().getActivityInfo(cn, 0);
ri.activityInfo = ai;
UserManager userManager =
(UserManager) getSystemService(Context.USER_SERVICE);
ri.iconResourceId = ai.icon;
ri.labelRes = ai.labelRes;
ri.resolvePackageName = ai.packageName;
ri.activityInfo.applicationInfo = new ApplicationInfo(
ri.activityInfo.applicationInfo);
ri.activityInfo.applicationInfo = ai.applicationInfo;
ri.activityInfo.applicationInfo.uid = getUserId();
} catch (PackageManager.NameNotFoundException ignored) {
if (DEBUG) Log.d(TAG, "Package not found, skipping this shortcut");
continue;
}
}
DisplayResolveInfo resolveInfo = new DisplayResolveInfo(getTargetIntent(),
ri,
info.getShortLabel(),
info.getLongLabel(),
getTargetIntent());
int bestMatch = 0;
ComponentName bestComponent = null;
for (int i = 0; i < info.getChooserIntentFilters().length; i++) {
int newMatch = info.getChooserIntentFilters()[i]
.match(getContentResolver(), getTargetIntent(), false, TAG);
if (DEBUG) Log.d(TAG, "A match was found with value: " + newMatch);
if (newMatch > bestMatch) {
bestMatch = newMatch;
bestComponent = info.getChooserComponentNames()[i];
}
}
if (bestMatch == 0) {
Log.e(TAG, "Unexpectedly, no match was found for the provided chooser intent");
return;
}
Bundle extrasToAdd =
info.getChooserExtras() == null ? null: new Bundle(info.getChooserExtras());
if (DEBUG) Log.d(TAG, "Adding service target " + info.getShortLabel());
newTargets.add(new ChooserTarget(
info.getShortLabel(),
info.getIcon(),
1,
bestComponent,
extrasToAdd));
addServiceResults(resolveInfo, newTargets);
}
if (mChooserRowAdapter != null) {
mChooserRowAdapter.updateRowScales();
}
setShowServiceTargets(true);
}
/**
* Set to true to reveal all service targets at once.
*/
@@ -1246,37 +1337,7 @@ public class ChooserActivity extends ResolverActivity {
@Override
public void onChanged() {
super.onChanged();
final int rcount = getServiceTargetRowCount();
if (mServiceTargetScale == null
|| mServiceTargetScale.length != rcount) {
RowScale[] old = mServiceTargetScale;
int oldRCount = old != null ? old.length : 0;
mServiceTargetScale = new RowScale[rcount];
if (old != null && rcount > 0) {
System.arraycopy(old, 0, mServiceTargetScale, 0,
Math.min(old.length, rcount));
}
for (int i = rcount; i < oldRCount; i++) {
old[i].cancelAnimation();
}
for (int i = oldRCount; i < rcount; i++) {
final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
.setInterpolator(mInterpolator);
mServiceTargetScale[i] = rs;
}
// Start the animations in a separate loop.
// The process of starting animations will result in
// binding views to set up initial values, and we must
// have ALL of the new RowScale objects created above before
// we get started.
for (int i = oldRCount; i < rcount; i++) {
mServiceTargetScale[i].startAnimation();
}
}
updateRowScales();
notifyDataSetChanged();
}
@@ -1293,6 +1354,40 @@ public class ChooserActivity extends ResolverActivity {
});
}
void updateRowScales() {
final int rcount = getServiceTargetRowCount();
if (mServiceTargetScale == null
|| mServiceTargetScale.length != rcount) {
if (DEBUG) Log.d(TAG, "Row scales need adjusting to " + rcount + " rows.");
RowScale[] old = mServiceTargetScale;
int oldRCount = old != null ? old.length : 0;
mServiceTargetScale = new RowScale[rcount];
if (old != null && rcount > 0) {
System.arraycopy(old, 0, mServiceTargetScale, 0,
Math.min(old.length, rcount));
}
for (int i = rcount; i < oldRCount; i++) {
old[i].cancelAnimation();
}
for (int i = oldRCount; i < rcount; i++) {
final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
.setInterpolator(mInterpolator);
mServiceTargetScale[i] = rs;
}
// Start the animations in a separate loop.
// The process of starting animations will result in
// binding views to set up initial values, and we must
// have ALL of the new RowScale objects created above before
// we get started.
for (int i = oldRCount; i < rcount; i++) {
mServiceTargetScale[i].startAnimation();
}
}
}
private float getRowScale(int rowPosition) {
final int start = getCallerTargetRowCount();
final int end = start + getServiceTargetRowCount();
@@ -1563,6 +1658,10 @@ public class ChooserActivity extends ResolverActivity {
}
}
public LauncherApps getLauncherApps() {
return (LauncherApps) getSystemService(Context.LAUNCHER_APPS_SERVICE);
}
static class ServiceResultInfo {
public final DisplayResolveInfo originalTarget;
public final List<ChooserTarget> resultTargets;