summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorAdam Powell <adamp@google.com>2015-05-06 21:08:37 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-05-06 21:09:20 +0000
commitcfd6e9dfd063cba795497f251aa6f7fe2554f10b (patch)
treedbe31f4920cc667bb98d59a07b91377ce8dfc888 /core/java
parent0a323036e2351ce4e9347ca1a1b5664e0f6e7672 (diff)
parent2ed547e55f820a9c705872d802b051d8ae9c906b (diff)
downloadframeworks_base-cfd6e9dfd063cba795497f251aa6f7fe2554f10b.zip
frameworks_base-cfd6e9dfd063cba795497f251aa6f7fe2554f10b.tar.gz
frameworks_base-cfd6e9dfd063cba795497f251aa6f7fe2554f10b.tar.bz2
Merge "Add alternate intents and refinement callbacks to ChooserActivity" into mnc-dev
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/content/Intent.java71
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java233
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java289
3 files changed, 516 insertions, 77 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 54fe786..d0298cd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -17,6 +17,7 @@
package android.content;
import android.content.pm.ApplicationInfo;
+import android.os.ResultReceiver;
import android.provider.MediaStore;
import android.util.ArraySet;
@@ -3291,11 +3292,79 @@ public class Intent implements Parcelable, Cloneable {
/**
* An Intent describing the choices you would like shown with
- * {@link #ACTION_PICK_ACTIVITY}.
+ * {@link #ACTION_PICK_ACTIVITY} or {@link #ACTION_CHOOSER}.
*/
public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
/**
+ * An Intent[] describing additional, alternate choices you would like shown with
+ * {@link #ACTION_CHOOSER}.
+ *
+ * <p>An app may be capable of providing several different payload types to complete a
+ * user's intended action. For example, an app invoking {@link #ACTION_SEND} to share photos
+ * with another app may use EXTRA_ALTERNATE_INTENTS to have the chooser transparently offer
+ * several different supported sending mechanisms for sharing, such as the actual "image/*"
+ * photo data or a hosted link where the photos can be viewed.</p>
+ *
+ * <p>The intent present in {@link #EXTRA_INTENT} will be treated as the
+ * first/primary/preferred intent in the set. Additional intents specified in
+ * this extra are ordered; by default intents that appear earlier in the array will be
+ * preferred over intents that appear later in the array as matches for the same
+ * target component. To alter this preference, a calling app may also supply
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p>
+ */
+ public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS";
+
+ /**
+ * An {@link IntentSender} for an Activity that will be invoked when the user makes a selection
+ * from the chooser activity presented by {@link #ACTION_CHOOSER}.
+ *
+ * <p>An app preparing an action for another app to complete may wish to allow the user to
+ * disambiguate between several options for completing the action based on the chosen target
+ * or otherwise refine the action before it is invoked.
+ * </p>
+ *
+ * <p>When sent, this IntentSender may be filled in with the following extras:</p>
+ * <ul>
+ * <li>{@link #EXTRA_INTENT} The first intent that matched the user's chosen target</li>
+ * <li>{@link #EXTRA_ALTERNATE_INTENTS} Any additional intents that also matched the user's
+ * chosen target beyond the first</li>
+ * <li>{@link #EXTRA_RESULT_RECEIVER} A {@link ResultReceiver} that the refinement activity
+ * should fill in and send once the disambiguation is complete</li>
+ * </ul>
+ */
+ public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER
+ = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
+
+ /**
+ * A {@link ResultReceiver} used to return data back to the sender.
+ *
+ * <p>Used to complete an app-specific
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER refinement} for {@link #ACTION_CHOOSER}.</p>
+ *
+ * <p>If {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} is present in the intent
+ * used to start a {@link #ACTION_CHOOSER} activity this extra will be
+ * {@link #fillIn(Intent, int) filled in} to that {@link IntentSender} and sent
+ * when the user selects a target component from the chooser. It is up to the recipient
+ * to send a result to this ResultReceiver to signal that disambiguation is complete
+ * and that the chooser should invoke the user's choice.</p>
+ *
+ * <p>The disambiguator should provide a Bundle to the ResultReceiver with an intent
+ * assigned to the key {@link #EXTRA_INTENT}. This supplied intent will be used by the chooser
+ * to match and fill in the final Intent or ChooserTarget before starting it.
+ * The supplied intent must {@link #filterEquals(Intent) match} one of the intents from
+ * {@link #EXTRA_INTENT} or {@link #EXTRA_ALTERNATE_INTENTS} passed to
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} to be accepted.</p>
+ *
+ * <p>The result code passed to the ResultReceiver should be
+ * {@link android.app.Activity#RESULT_OK} if the refinement succeeded and the supplied intent's
+ * target in the chooser should be started, or {@link android.app.Activity#RESULT_CANCELED} if
+ * the chooser should finish without starting a target.</p>
+ */
+ public static final String EXTRA_RESULT_RECEIVER
+ = "android.intent.extra.RESULT_RECEIVER";
+
+ /**
* A CharSequence dialog title to provide to the user when used with a
* {@link #ACTION_CHOOSER}.
*/
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index e347faa..62ca1f0 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -21,6 +21,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -34,6 +35,7 @@ import android.os.IBinder;
import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.UserHandle;
import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
@@ -53,11 +55,13 @@ public class ChooserActivity extends ResolverActivity {
private static final boolean DEBUG = false;
- private static final int QUERY_TARGET_LIMIT = 5;
+ private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
+ private IntentSender mRefinementIntentSender;
+ private RefinementResultReceiver mRefinementResultReceiver;
private ChooserTarget[] mCallerChooserTargets;
@@ -113,6 +117,32 @@ public class ChooserActivity extends ResolverActivity {
if (target != null) {
modifyTargetIntent(target);
}
+ Parcelable[] targetsParcelable
+ = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
+ if (targetsParcelable != null) {
+ final boolean offset = target == null;
+ Intent[] additionalTargets =
+ new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
+ for (int i = 0; i < targetsParcelable.length; i++) {
+ if (!(targetsParcelable[i] instanceof Intent)) {
+ Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
+ + targetsParcelable[i]);
+ finish();
+ super.onCreate(null);
+ return;
+ }
+ final Intent additionalTarget = (Intent) targetsParcelable[i];
+ if (i == 0 && target == null) {
+ target = additionalTarget;
+ modifyTargetIntent(target);
+ } else {
+ additionalTargets[offset ? i - 1 : i] = additionalTarget;
+ modifyTargetIntent(additionalTarget);
+ }
+ }
+ setAdditionalTargets(additionalTargets);
+ }
+
mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
int defaultTitleRes = 0;
@@ -125,7 +155,7 @@ public class ChooserActivity extends ResolverActivity {
initialIntents = new Intent[pa.length];
for (int i=0; i<pa.length; i++) {
if (!(pa[i] instanceof Intent)) {
- Log.w("ChooserActivity", "Initial intent #" + i + " not an Intent: " + pa[i]);
+ Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
finish();
super.onCreate(null);
return;
@@ -141,8 +171,7 @@ public class ChooserActivity extends ResolverActivity {
final ChooserTarget[] targets = new ChooserTarget[pa.length];
for (int i = 0; i < pa.length; i++) {
if (!(pa[i] instanceof ChooserTarget)) {
- Log.w("ChooserActivity", "Chooser target #" + i + " is not a ChooserTarget: " +
- pa[i]);
+ Log.w(TAG, "Chooser target #" + i + " is not a ChooserTarget: " + pa[i]);
finish();
super.onCreate(null);
return;
@@ -153,12 +182,23 @@ public class ChooserActivity extends ResolverActivity {
}
mChosenComponentSender = intent.getParcelableExtra(
Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
+ mRefinementIntentSender = intent.getParcelableExtra(
+ Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
setSafeForwardingMode(true);
super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
null, false);
}
@Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mRefinementResultReceiver != null) {
+ mRefinementResultReceiver.destroy();
+ mRefinementResultReceiver = null;
+ }
+ }
+
+ @Override
public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
Intent result = defIntent;
if (mReplacementExtras != null) {
@@ -211,6 +251,37 @@ public class ChooserActivity extends ResolverActivity {
}
}
+ @Override
+ protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
+ if (mRefinementIntentSender != null) {
+ final Intent fillIn = new Intent();
+ final List<Intent> sourceIntents = target.getAllSourceIntents();
+ if (!sourceIntents.isEmpty()) {
+ fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
+ if (sourceIntents.size() > 1) {
+ final Intent[] alts = new Intent[sourceIntents.size() - 1];
+ for (int i = 1, N = sourceIntents.size(); i < N; i++) {
+ alts[i - 1] = sourceIntents.get(i);
+ }
+ fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
+ }
+ if (mRefinementResultReceiver != null) {
+ mRefinementResultReceiver.destroy();
+ }
+ mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
+ fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
+ mRefinementResultReceiver);
+ try {
+ mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
+ return false;
+ } catch (SendIntentException e) {
+ Log.e(TAG, "Refinement IntentSender failed to send", e);
+ }
+ }
+ }
+ return super.onTargetSelected(target, alwaysCheck);
+ }
+
void queryTargetServices(ChooserListAdapter adapter) {
final PackageManager pm = getPackageManager();
int targetsToQuery = 0;
@@ -258,8 +329,9 @@ public class ChooserActivity extends ResolverActivity {
targetsToQuery++;
}
}
- if (targetsToQuery >= QUERY_TARGET_LIMIT) {
- if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + QUERY_TARGET_LIMIT);
+ if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
+ if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
+ + QUERY_TARGET_SERVICE_LIMIT);
break;
}
}
@@ -303,6 +375,43 @@ public class ChooserActivity extends ResolverActivity {
mTargetResultHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
}
+ void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
+ if (mRefinementResultReceiver != null) {
+ mRefinementResultReceiver.destroy();
+ mRefinementResultReceiver = null;
+ }
+
+ if (selectedTarget == null) {
+ Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
+ } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
+ Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
+ + " cannot match refined source intent " + matchingIntent);
+ } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) {
+ finish();
+ return;
+ }
+ onRefinementCanceled();
+ }
+
+ void onRefinementCanceled() {
+ if (mRefinementResultReceiver != null) {
+ mRefinementResultReceiver.destroy();
+ mRefinementResultReceiver = null;
+ }
+ finish();
+ }
+
+ boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
+ final List<Intent> targetIntents = target.getAllSourceIntents();
+ for (int i = 0, N = targetIntents.size(); i < N; i++) {
+ final Intent targetIntent = targetIntents.get(i);
+ if (targetIntent.filterEquals(matchingIntent)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
ResolveListAdapter createAdapter(Context context, Intent[] initialIntents,
List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) {
@@ -313,17 +422,19 @@ public class ChooserActivity extends ResolverActivity {
return adapter;
}
- class ChooserTargetInfo implements TargetInfo {
- private final TargetInfo mSourceInfo;
+ final class ChooserTargetInfo implements TargetInfo {
+ private final DisplayResolveInfo mSourceInfo;
private final ResolveInfo mBackupResolveInfo;
private final ChooserTarget mChooserTarget;
private final Drawable mDisplayIcon;
+ private final Intent mFillInIntent;
+ private final int mFillInFlags;
public ChooserTargetInfo(ChooserTarget target) {
this(null, target);
}
- public ChooserTargetInfo(TargetInfo sourceInfo, ChooserTarget chooserTarget) {
+ public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget) {
mSourceInfo = sourceInfo;
mChooserTarget = chooserTarget;
mDisplayIcon = new BitmapDrawable(getResources(), chooserTarget.getIcon());
@@ -333,6 +444,18 @@ public class ChooserActivity extends ResolverActivity {
} else {
mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
}
+
+ mFillInIntent = null;
+ mFillInFlags = 0;
+ }
+
+ private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
+ mSourceInfo = other.mSourceInfo;
+ mBackupResolveInfo = other.mBackupResolveInfo;
+ mChooserTarget = other.mChooserTarget;
+ mDisplayIcon = other.mDisplayIcon;
+ mFillInIntent = fillInIntent;
+ mFillInFlags = flags;
}
@Override
@@ -358,22 +481,42 @@ public class ChooserActivity extends ResolverActivity {
}
private Intent getFillInIntent() {
- return mSourceInfo != null ? mSourceInfo.getResolvedIntent() : getTargetIntent();
+ Intent result = mSourceInfo != null
+ ? mSourceInfo.getResolvedIntent() : getTargetIntent();
+ if (result == null) {
+ Log.e(TAG, "ChooserTargetInfo#getFillInIntent: no fillIn intent available");
+ } else if (mFillInIntent != null) {
+ result = new Intent(result);
+ result.fillIn(mFillInIntent, mFillInFlags);
+ }
+ return result;
}
@Override
public boolean start(Activity activity, Bundle options) {
- return mChooserTarget.sendIntent(activity, getFillInIntent());
+ final Intent intent = getFillInIntent();
+ if (intent == null) {
+ return false;
+ }
+ return mChooserTarget.sendIntent(activity, intent);
}
@Override
public boolean startAsCaller(Activity activity, Bundle options, int userId) {
- return mChooserTarget.sendIntentAsCaller(activity, getFillInIntent(), userId);
+ final Intent intent = getFillInIntent();
+ if (intent == null) {
+ return false;
+ }
+ return mChooserTarget.sendIntentAsCaller(activity, intent, userId);
}
@Override
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
- return mChooserTarget.sendIntentAsUser(activity, getFillInIntent(), user);
+ final Intent intent = getFillInIntent();
+ if (intent == null) {
+ return false;
+ }
+ return mChooserTarget.sendIntentAsUser(activity, intent, user);
}
@Override
@@ -395,6 +538,21 @@ public class ChooserActivity extends ResolverActivity {
public Drawable getDisplayIcon() {
return mDisplayIcon;
}
+
+ @Override
+ public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+ return new ChooserTargetInfo(this, fillInIntent, flags);
+ }
+
+ @Override
+ public List<Intent> getAllSourceIntents() {
+ final List<Intent> results = new ArrayList<>();
+ if (mSourceInfo != null) {
+ // We only queried the service for the first one in our sourceinfo.
+ results.add(mSourceInfo.getAllSourceIntents().get(0));
+ }
+ return results;
+ }
}
public class ChooserListAdapter extends ResolveListAdapter {
@@ -542,4 +700,53 @@ public class ChooserActivity extends ResolverActivity {
connection = c;
}
}
+
+ static class RefinementResultReceiver extends ResultReceiver {
+ private ChooserActivity mChooserActivity;
+ private TargetInfo mSelectedTarget;
+
+ public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
+ Handler handler) {
+ super(handler);
+ mChooserActivity = host;
+ mSelectedTarget = target;
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (mChooserActivity == null) {
+ Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
+ return;
+ }
+ if (resultData == null) {
+ Log.e(TAG, "RefinementResultReceiver received null resultData");
+ return;
+ }
+
+ switch (resultCode) {
+ case RESULT_CANCELED:
+ mChooserActivity.onRefinementCanceled();
+ break;
+ case RESULT_OK:
+ Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
+ if (intentParcelable instanceof Intent) {
+ mChooserActivity.onRefinementResult(mSelectedTarget,
+ (Intent) intentParcelable);
+ } else {
+ Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
+ + " in resultData with key Intent.EXTRA_INTENT");
+ }
+ break;
+ default:
+ Log.w(TAG, "Unknown result code " + resultCode
+ + " sent to RefinementResultReceiver");
+ break;
+ }
+ }
+
+ public void destroy() {
+ mChooserActivity = null;
+ mSelectedTarget = null;
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 8dd7836..2048664 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -102,7 +102,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
private int mLastSelected = AbsListView.INVALID_POSITION;
private boolean mResolvingHome = false;
private int mProfileSwitchMessageId = -1;
- private Intent mIntent;
+ private final ArrayList<Intent> mIntents = new ArrayList<>();
private UsageStatsManager mUsm;
private Map<String, UsageStats> mStats;
@@ -229,7 +229,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
mIconDpi = am.getLauncherLargeIconDensity();
- mIntent = new Intent(intent);
+ mIntents.add(0, new Intent(intent));
mAdapter = createAdapter(this, initialIntents, rList, mLaunchedFromUid, alwaysUseOption);
final int layoutId;
@@ -250,7 +250,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
return;
}
- int count = mAdapter.mList.size();
+ int count = mAdapter.mDisplayList.size();
if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) {
setContentView(layoutId);
mAdapterView = (AbsListView) findViewById(R.id.resolver_list);
@@ -376,8 +376,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
}
+ protected final void setAdditionalTargets(Intent[] intents) {
+ if (intents != null) {
+ for (Intent intent : intents) {
+ mIntents.add(intent);
+ }
+ }
+ }
+
public Intent getTargetIntent() {
- return mIntent;
+ return mIntents.isEmpty() ? null : mIntents.get(0);
}
private String getReferrerPackageName() {
@@ -630,8 +638,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
TargetInfo target = mAdapter.targetInfoForPosition(which, filtered);
- onTargetSelected(target, always);
- finish();
+ if (onTargetSelected(target, always)) {
+ finish();
+ }
}
/**
@@ -641,7 +650,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
return defIntent;
}
- protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) {
+ protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
final ResolveInfo ri = target.getResolveInfo();
final Intent intent = target != null ? target.getResolvedIntent() : null;
@@ -728,7 +737,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
ComponentName[] set = new ComponentName[N];
int bestMatch = 0;
for (int i=0; i<N; i++) {
- ResolveInfo r = mAdapter.mOrigResolveList.get(i);
+ ResolveInfo r = mAdapter.mOrigResolveList.get(i).getResolveInfoAt(0);
set[i] = new ComponentName(r.activityInfo.packageName,
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
@@ -774,6 +783,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if (target != null) {
safelyStartActivity(target);
}
+ return true;
}
void safelyStartActivity(TargetInfo cti) {
@@ -837,15 +847,17 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
private Drawable mDisplayIcon;
private final CharSequence mExtendedInfo;
private final Intent mResolvedIntent;
+ private final List<Intent> mSourceIntents = new ArrayList<>();
- DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel,
+ DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
CharSequence pInfo, Intent pOrigIntent) {
+ mSourceIntents.add(originalIntent);
mResolveInfo = pri;
mDisplayLabel = pLabel;
mExtendedInfo = pInfo;
final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent :
- getReplacementIntent(pri.activityInfo, mIntent));
+ getReplacementIntent(pri.activityInfo, getTargetIntent()));
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
final ActivityInfo ai = mResolveInfo.activityInfo;
@@ -854,6 +866,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
mResolvedIntent = intent;
}
+ private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) {
+ mSourceIntents.addAll(other.getAllSourceIntents());
+ mResolveInfo = other.mResolveInfo;
+ mDisplayLabel = other.mDisplayLabel;
+ mDisplayIcon = other.mDisplayIcon;
+ mExtendedInfo = other.mExtendedInfo;
+ mResolvedIntent = new Intent(other.mResolvedIntent);
+ mResolvedIntent.fillIn(fillInIntent, flags);
+ }
+
public ResolveInfo getResolveInfo() {
return mResolveInfo;
}
@@ -866,6 +888,20 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
return mDisplayIcon;
}
+ @Override
+ public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+ return new DisplayResolveInfo(this, fillInIntent, flags);
+ }
+
+ @Override
+ public List<Intent> getAllSourceIntents() {
+ return mSourceIntents;
+ }
+
+ public void addAlternateSourceIntent(Intent alt) {
+ mSourceIntents.add(alt);
+ }
+
public void setDisplayIcon(Drawable icon) {
mDisplayIcon = icon;
}
@@ -986,6 +1022,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
* @return The drawable that should be used to represent this target
*/
public Drawable getDisplayIcon();
+
+ /**
+ * Clone this target with the given fill-in information.
+ */
+ public TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
+
+ /**
+ * @return the list of supported source intents deduped against this single target
+ */
+ public List<Intent> getAllSourceIntents();
}
class ResolveListAdapter extends BaseAdapter {
@@ -998,8 +1044,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
protected final LayoutInflater mInflater;
- List<DisplayResolveInfo> mList;
- List<ResolveInfo> mOrigResolveList;
+ List<DisplayResolveInfo> mDisplayList;
+ List<ResolvedComponentInfo> mOrigResolveList;
private int mLastChosenPosition = -1;
private boolean mFilterLastUsed;
@@ -1010,7 +1056,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
mBaseResolveList = rList;
mLaunchedFromUid = launchedFromUid;
mInflater = LayoutInflater.from(context);
- mList = new ArrayList<>();
+ mDisplayList = new ArrayList<>();
mFilterLastUsed = filterLastUsed;
rebuildList();
}
@@ -1027,7 +1073,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
public DisplayResolveInfo getFilteredItem() {
if (mFilterLastUsed && mLastChosenPosition >= 0) {
// Not using getItem since it offsets to dodge this position for the list
- return mList.get(mLastChosenPosition);
+ return mDisplayList.get(mLastChosenPosition);
}
return null;
}
@@ -1048,11 +1094,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
private void rebuildList() {
- List<ResolveInfo> currentResolveList;
+ List<ResolvedComponentInfo> currentResolveList = null;
try {
+ final Intent primaryIntent = getTargetIntent();
mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity(
- mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()),
+ primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()),
PackageManager.MATCH_DEFAULT_ONLY);
} catch (RemoteException re) {
Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
@@ -1060,15 +1107,27 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
// Clear the value of mOtherProfile from previous call.
mOtherProfile = null;
- mList.clear();
+ mDisplayList.clear();
if (mBaseResolveList != null) {
- currentResolveList = mOrigResolveList = mBaseResolveList;
+ currentResolveList = mOrigResolveList = new ArrayList<>();
+ addResolveListDedupe(currentResolveList, getTargetIntent(), mBaseResolveList);
} else {
- currentResolveList = mOrigResolveList = mPm.queryIntentActivities(mIntent,
- PackageManager.MATCH_DEFAULT_ONLY
- | (shouldGetResolvedFilter() ? PackageManager.GET_RESOLVED_FILTER : 0)
- | (shouldGetActivityMetadata() ? PackageManager.GET_META_DATA : 0)
- );
+ final boolean shouldGetResolvedFilter = shouldGetResolvedFilter();
+ final boolean shouldGetActivityMetadata = shouldGetActivityMetadata();
+ for (int i = 0, N = mIntents.size(); i < N; i++) {
+ final Intent intent = mIntents.get(i);
+ final List<ResolveInfo> infos = mPm.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY
+ | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
+ | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0));
+ if (infos != null) {
+ if (currentResolveList == null) {
+ currentResolveList = mOrigResolveList = new ArrayList<>();
+ }
+ addResolveListDedupe(currentResolveList, intent, infos);
+ }
+ }
+
// Filter out any activities that the launched uid does not
// have permission for. We don't do this when we have an explicit
// list of resolved activities, because that only happens when
@@ -1076,14 +1135,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
// they gave us.
if (currentResolveList != null) {
for (int i=currentResolveList.size()-1; i >= 0; i--) {
- ActivityInfo ai = currentResolveList.get(i).activityInfo;
+ ActivityInfo ai = currentResolveList.get(i)
+ .getResolveInfoAt(0).activityInfo;
int granted = ActivityManager.checkComponentPermission(
ai.permission, mLaunchedFromUid,
ai.applicationInfo.uid, ai.exported);
if (granted != PackageManager.PERMISSION_GRANTED) {
// Access not allowed!
if (mOrigResolveList == currentResolveList) {
- mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
+ mOrigResolveList = new ArrayList<>(mOrigResolveList);
}
currentResolveList.remove(i);
}
@@ -1094,9 +1154,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
// Only display the first matches that are either of equal
// priority or have asked to be default options.
- ResolveInfo r0 = currentResolveList.get(0);
+ ResolvedComponentInfo rci0 = currentResolveList.get(0);
+ ResolveInfo r0 = rci0.getResolveInfoAt(0);
for (int i=1; i<N; i++) {
- ResolveInfo ri = currentResolveList.get(i);
+ ResolveInfo ri = currentResolveList.get(i).getResolveInfoAt(0);
if (DEBUG) Log.v(
TAG,
r0.activityInfo.name + "=" +
@@ -1107,7 +1168,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
r0.isDefault != ri.isDefault) {
while (i < N) {
if (mOrigResolveList == currentResolveList) {
- mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
+ mOrigResolveList = new ArrayList<>(mOrigResolveList);
}
currentResolveList.remove(i);
N--;
@@ -1115,9 +1176,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
}
if (N > 1) {
- Comparator<ResolveInfo> rComparator =
- new ResolverComparator(ResolverActivity.this, mIntent);
- Collections.sort(currentResolveList, rComparator);
+ Collections.sort(currentResolveList,
+ new ResolverComparator(ResolverActivity.this, getTargetIntent()));
}
// First put the initial items at the top.
if (mInitialIntents != null) {
@@ -1146,14 +1206,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
ri.nonLocalizedLabel = li.getNonLocalizedLabel();
ri.icon = li.getIconResource();
}
- addResolveInfo(new DisplayResolveInfo(ri,
+ addResolveInfo(new DisplayResolveInfo(ii, ri,
ri.loadLabel(getPackageManager()), null, ii));
}
}
// Check for applications with same name and use application name or
// package name if necessary
- r0 = currentResolveList.get(0);
+ rci0 = currentResolveList.get(0);
+ r0 = rci0.getResolveInfoAt(0);
int start = 0;
CharSequence r0Label = r0.loadLabel(mPm);
mHasExtendedInfo = false;
@@ -1161,7 +1222,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if (r0Label == null) {
r0Label = r0.activityInfo.packageName;
}
- ResolveInfo ri = currentResolveList.get(i);
+ ResolvedComponentInfo rci = currentResolveList.get(i);
+ ResolveInfo ri = rci.getResolveInfoAt(0);
CharSequence riLabel = ri.loadLabel(mPm);
if (riLabel == null) {
riLabel = ri.activityInfo.packageName;
@@ -1169,13 +1231,14 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if (riLabel.equals(r0Label)) {
continue;
}
- processGroup(currentResolveList, start, (i-1), r0, r0Label);
+ processGroup(currentResolveList, start, (i-1), rci0, r0Label);
+ rci0 = rci;
r0 = ri;
r0Label = riLabel;
start = i;
}
// Process last group
- processGroup(currentResolveList, start, (N-1), r0, r0Label);
+ processGroup(currentResolveList, start, (N-1), rci0, r0Label);
}
// Layout doesn't handle both profile button and last chosen
@@ -1188,6 +1251,36 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
onListRebuilt();
}
+ private void addResolveListDedupe(List<ResolvedComponentInfo> into, Intent intent,
+ List<ResolveInfo> from) {
+ final int fromCount = from.size();
+ final int intoCount = into.size();
+ for (int i = 0; i < fromCount; i++) {
+ final ResolveInfo newInfo = from.get(i);
+ boolean found = false;
+ // Only loop to the end of into as it was before we started; no dupes in from.
+ for (int j = 0; j < intoCount; j++) {
+ final ResolvedComponentInfo rci = into.get(i);
+ if (isSameResolvedComponent(newInfo, rci)) {
+ found = true;
+ rci.add(intent, newInfo);
+ break;
+ }
+ }
+ if (!found) {
+ into.add(new ResolvedComponentInfo(new ComponentName(
+ newInfo.activityInfo.packageName, newInfo.activityInfo.name),
+ intent, newInfo));
+ }
+ }
+ }
+
+ private boolean isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b) {
+ final ActivityInfo ai = a.activityInfo;
+ return ai.packageName.equals(b.name.getPackageName())
+ && ai.name.equals(b.name.getClassName());
+ }
+
public void onListRebuilt() {
// This space for rent
}
@@ -1196,18 +1289,18 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
return mFilterLastUsed;
}
- private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
- CharSequence roLabel) {
+ private void processGroup(List<ResolvedComponentInfo> rList, int start, int end,
+ ResolvedComponentInfo ro, CharSequence roLabel) {
// Process labels from start to i
int num = end - start+1;
if (num == 1) {
// No duplicate labels. Use label for entry at start
- addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null));
- updateLastChosenPosition(ro);
+ addResolveInfoWithAlternates(ro, null, roLabel);
} else {
mHasExtendedInfo = true;
boolean usePkg = false;
- CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm);
+ CharSequence startApp = ro.getResolveInfoAt(0).activityInfo.applicationInfo
+ .loadLabel(mPm);
if (startApp == null) {
usePkg = true;
}
@@ -1217,7 +1310,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
new HashSet<CharSequence>();
duplicates.add(startApp);
for (int j = start+1; j <= end ; j++) {
- ResolveInfo jRi = rList.get(j);
+ ResolveInfo jRi = rList.get(j).getResolveInfoAt(0);
CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
if ( (jApp == null) || (duplicates.contains(jApp))) {
usePkg = true;
@@ -1230,26 +1323,46 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
duplicates.clear();
}
for (int k = start; k <= end; k++) {
- ResolveInfo add = rList.get(k);
+ final ResolvedComponentInfo rci = rList.get(k);
+ final ResolveInfo add = rci.getResolveInfoAt(0);
+ final CharSequence extraInfo;
if (usePkg) {
- // Use application name for all entries from start to end-1
- addResolveInfo(new DisplayResolveInfo(add, roLabel,
- add.activityInfo.packageName, null));
- } else {
// Use package name for all entries from start to end-1
- addResolveInfo(new DisplayResolveInfo(add, roLabel,
- add.activityInfo.applicationInfo.loadLabel(mPm), null));
+ extraInfo = add.activityInfo.packageName;
+ } else {
+ // Use application name for all entries from start to end-1
+ extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm);
}
- updateLastChosenPosition(add);
+ addResolveInfoWithAlternates(rci, extraInfo, roLabel);
+ }
+ }
+ }
+
+ private void addResolveInfoWithAlternates(ResolvedComponentInfo rci,
+ CharSequence extraInfo, CharSequence roLabel) {
+ final int count = rci.getCount();
+ final Intent intent = rci.getIntentAt(0);
+ final ResolveInfo add = rci.getResolveInfoAt(0);
+ final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
+ final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
+ extraInfo, replaceIntent);
+ addResolveInfo(dri);
+ if (replaceIntent == intent) {
+ // Only add alternates if we didn't get a specific replacement from
+ // the caller. If we have one it trumps potential alternates.
+ for (int i = 1, N = count; i < N; i++) {
+ final Intent altIntent = rci.getIntentAt(i);
+ dri.addAlternateSourceIntent(altIntent);
}
}
+ updateLastChosenPosition(add);
}
private void updateLastChosenPosition(ResolveInfo info) {
if (mLastChosen != null
&& mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
&& mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
- mLastChosenPosition = mList.size() - 1;
+ mLastChosenPosition = mDisplayList.size() - 1;
}
}
@@ -1259,20 +1372,21 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
// The first one we see gets special treatment.
mOtherProfile = dri;
} else {
- mList.add(dri);
+ mDisplayList.add(dri);
}
}
public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
- return (filtered ? getItem(position) : mList.get(position)).getResolveInfo();
+ return (filtered ? getItem(position) : mDisplayList.get(position))
+ .getResolveInfo();
}
public TargetInfo targetInfoForPosition(int position, boolean filtered) {
- return filtered ? getItem(position) : mList.get(position);
+ return filtered ? getItem(position) : mDisplayList.get(position);
}
public int getCount() {
- int result = mList.size();
+ int result = mDisplayList.size();
if (mFilterLastUsed && mLastChosenPosition >= 0) {
result--;
}
@@ -1283,7 +1397,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
position++;
}
- return mList.get(position);
+ return mDisplayList.get(position);
}
public long getItemId(int position) {
@@ -1295,8 +1409,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
public boolean hasResolvedTarget(ResolveInfo info) {
- for (int i = 0, N = mList.size(); i < N; i++) {
- if (info.equals(mList.get(i).getResolveInfo())) {
+ for (int i = 0, N = mDisplayList.size(); i < N; i++) {
+ if (info.equals(mDisplayList.get(i).getResolveInfo())) {
return true;
}
}
@@ -1304,11 +1418,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
protected int getDisplayResolveInfoCount() {
- return mList.size();
+ return mDisplayList.size();
}
protected DisplayResolveInfo getDisplayResolveInfo(int index) {
- return mList.get(index);
+ // Used to query services. We only query services for primary targets, not alternates.
+ return mDisplayList.get(index);
}
public final View getView(int position, View convertView, ViewGroup parent) {
@@ -1349,6 +1464,52 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
}
+ static final class ResolvedComponentInfo {
+ public final ComponentName name;
+ private final List<Intent> mIntents = new ArrayList<>();
+ private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
+
+ public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) {
+ this.name = name;
+ add(intent, info);
+ }
+
+ public void add(Intent intent, ResolveInfo info) {
+ mIntents.add(intent);
+ mResolveInfos.add(info);
+ }
+
+ public int getCount() {
+ return mIntents.size();
+ }
+
+ public Intent getIntentAt(int index) {
+ return index >= 0 ? mIntents.get(index) : null;
+ }
+
+ public ResolveInfo getResolveInfoAt(int index) {
+ return index >= 0 ? mResolveInfos.get(index) : null;
+ }
+
+ public int findIntent(Intent intent) {
+ for (int i = 0, N = mIntents.size(); i < N; i++) {
+ if (intent.equals(mIntents.get(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int findResolveInfo(ResolveInfo info) {
+ for (int i = 0, N = mResolveInfos.size(); i < N; i++) {
+ if (info.equals(mResolveInfos.get(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ }
+
static class ViewHolder {
public TextView text;
public TextView text2;
@@ -1435,7 +1596,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
&& match <= IntentFilter.MATCH_CATEGORY_PATH;
}
- class ResolverComparator implements Comparator<ResolveInfo> {
+ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
private final Collator mCollator;
private final boolean mHttp;
@@ -1446,7 +1607,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
}
@Override
- public int compare(ResolveInfo lhs, ResolveInfo rhs) {
+ public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) {
+ final ResolveInfo lhs = lhsp.getResolveInfoAt(0);
+ final ResolveInfo rhs = rhsp.getResolveInfoAt(0);
+
// We want to put the one targeted to another user at the end of the dialog.
if (lhs.targetUserId != UserHandle.USER_CURRENT) {
return 1;
@@ -1487,7 +1651,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if (stats != null) {
return stats.getTotalTimeInForeground();
}
-
}
return 0;
}