Add alternate intents and refinement callbacks to ChooserActivity

Allow a calling app to supply an array of additional Intents to the
system ChooserActivity.

The chooser will present a merged list of targets that can handle any
of the Intents supplied, including both the standard EXTRA_INTENT as
well as any of the intents supplied in EXTRA_ALTERNATE_INTENTS. These are
treated as ordered; EXTRA_INTENT is considered the first/primary
Intent and EXTRA_ALTERNATE_INTENTS are sorted most important first.

Targets are queried for all supplied Intents. If the same component is
returned for more than one Intent, the target is associated with the
most important Intent that matched.

This allows calling apps to supply several different payloads for an
action depending on what the intended targets are able to support. For
example, an app performing ACTION_SEND may supply image/jpeg data to
compatible targets or a hosted web link to targets that only support
text/plain. The user will have the opportunity to pick from a single
merged list of choices using the best available payload, and will not
be bothered with the implementation details of how the payload will be
delivered to the recipient.

If the calling app wishes to provide further disambiguation or
refinement after the user makes a choice, for example to let the user
choose which of the source intents to send from the primary or
alternates, show a progress dialog as a full-resolution version of a
photo is downloaded from the server before being sent along or while
reticulating splines, the caller can supply an IntentSender to
ACTION_CHOOSER including the extra EXTRA_REFINEMENT_INTENT_SENDER.
This should be the IntentSender obtained from a PendingIntent pointing
at an activity to launch to perform the refinement.

The refinement activity should report that it is finished by obtaining
the ResultReceiver from EXTRA_RESULT_RECEIVER. Available intents to
send to the selected target will be contained in EXTRA_INTENT and
EXTRA_ALTERNATE_INTENTS.

To complete the refinement and send the result along to the chosen
target, the refinement activity should select one of the supplied
intents and send it to the ResultReceiver in a Bundle with the key
EXTRA_INTENT and the result code RESULT_OK. To cancel the refinement,
and let the user select another choice, send RESULT_CANCEL.

While refinement activities cannot modify the filterEquals-affecting
fields of the Intent they return, they may modify extras to provide
additional or altered details to the final recipient. These extras
will be filled into the Intent sent to the final target.

Change-Id: I7ad4739eadd1a0e307675847ccf47ea948918a3a
diff --git a/api/current.txt b/api/current.txt
index 375bee7..0ef4766 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8282,6 +8282,7 @@
     field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
     field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
     field public static final deprecated java.lang.String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE";
+    field public static final java.lang.String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS";
     field public static final java.lang.String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT";
     field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
     field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
@@ -8293,6 +8294,7 @@
     field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
     field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
+    field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
     field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
@@ -8325,6 +8327,7 @@
     field public static final java.lang.String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle";
     field public static final java.lang.String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent";
     field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
+    field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER";
     field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
diff --git a/api/system-current.txt b/api/system-current.txt
index 63ba05f..3aa6ca0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8508,6 +8508,7 @@
     field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
     field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
     field public static final deprecated java.lang.String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE";
+    field public static final java.lang.String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS";
     field public static final java.lang.String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT";
     field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
     field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
@@ -8519,6 +8520,7 @@
     field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
     field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
+    field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
     field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
@@ -8554,6 +8556,7 @@
     field public static final java.lang.String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle";
     field public static final java.lang.String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent";
     field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
+    field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER";
     field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
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 @@
 
     /**
      * 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.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.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 @@
 
     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 @@
         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 @@
             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 @@
             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 @@
         }
         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 @@
         }
     }
 
+    @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 @@
                     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 @@
         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 @@
         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 @@
             } 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 @@
         }
 
         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 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 @@
             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..20486643 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -102,7 +102,7 @@
     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 @@
         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 @@
             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 @@
         }
     }
 
+    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 @@
         }
 
         TargetInfo target = mAdapter.targetInfoForPosition(which, filtered);
-        onTargetSelected(target, always);
-        finish();
+        if (onTargetSelected(target, always)) {
+            finish();
+        }
     }
 
     /**
@@ -641,7 +650,7 @@
         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 @@
                 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 @@
         if (target != null) {
             safelyStartActivity(target);
         }
+        return true;
     }
 
     void safelyStartActivity(TargetInfo cti) {
@@ -837,15 +847,17 @@
         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 @@
             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 @@
             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 @@
          * @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 @@
 
         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 @@
             mBaseResolveList = rList;
             mLaunchedFromUid = launchedFromUid;
             mInflater = LayoutInflater.from(context);
-            mList = new ArrayList<>();
+            mDisplayList = new ArrayList<>();
             mFilterLastUsed = filterLastUsed;
             rebuildList();
         }
@@ -1027,7 +1073,7 @@
         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 @@
         }
 
         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 @@
 
             // 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 @@
                 // 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 @@
             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 @@
                         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 @@
                     }
                 }
                 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 @@
                             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 @@
                     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 @@
                     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 @@
             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 @@
             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 @@
                         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 @@
                     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 @@
                 // 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 @@
             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 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 @@
         }
 
         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 @@
         }
     }
 
+    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 @@
                 && 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 @@
         }
 
         @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 @@
                 if (stats != null) {
                     return stats.getTotalTimeInForeground();
                 }
-
             }
             return 0;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index 23a65e8..ca32567 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -93,7 +93,7 @@
     }
 
     @Override
-    protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) {
+    protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
         final ResolveInfo ri = target.getResolveInfo();
         try {
             IBinder b = ServiceManager.getService(USB_SERVICE);
@@ -129,5 +129,6 @@
         } catch (RemoteException e) {
             Log.e(TAG, "onIntentSelected failed", e);
         }
+        return true;
     }
 }