Intent chooser callback

Let an app specify an IntentSender as an extra for ACTION_CHOOSER that
will be notified of the user's chosen target. Apps can use this to
implement things like one-touch last used affordances without
implementing their own one-off chooser UI.

Bug 17363684

Change-Id: Ic5d1d0e0c7a0bae3eda6b1d6ade993f3ba865cfa
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index af6f181..7676e4b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -876,12 +876,44 @@
      * related methods.
      */
     public static Intent createChooser(Intent target, CharSequence title) {
+        return createChooser(target, title, null);
+    }
+
+    /**
+     * Convenience function for creating a {@link #ACTION_CHOOSER} Intent.
+     *
+     * <p>Builds a new {@link #ACTION_CHOOSER} Intent that wraps the given
+     * target intent, also optionally supplying a title.  If the target
+     * intent has specified {@link #FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, then these flags will also be
+     * set in the returned chooser intent, with its ClipData set appropriately:
+     * either a direct reflection of {@link #getClipData()} if that is non-null,
+     * or a new ClipData built from {@link #getData()}.</p>
+     *
+     * <p>The caller may optionally supply an {@link IntentSender} to receive a callback
+     * when the user makes a choice. This can be useful if the calling application wants
+     * to remember the last chosen target and surface it as a more prominent or one-touch
+     * affordance elsewhere in the UI for next time.</p>
+     *
+     * @param target The Intent that the user will be selecting an activity
+     * to perform.
+     * @param title Optional title that will be displayed in the chooser.
+     * @param sender Optional IntentSender to be called when a choice is made.
+     * @return Return a new Intent object that you can hand to
+     * {@link Context#startActivity(Intent) Context.startActivity()} and
+     * related methods.
+     */
+    public static Intent createChooser(Intent target, CharSequence title, IntentSender sender) {
         Intent intent = new Intent(ACTION_CHOOSER);
         intent.putExtra(EXTRA_INTENT, target);
         if (title != null) {
             intent.putExtra(EXTRA_TITLE, title);
         }
 
+        if (sender != null) {
+            intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
+        }
+
         // Migrate any clip data and flags from target.
         int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
                 | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
@@ -3140,6 +3172,26 @@
             "android.intent.extra.REPLACEMENT_EXTRAS";
 
     /**
+     * An {@link IntentSender} that will be notified if a user successfully chooses a target
+     * component to handle an action in an {@link #ACTION_CHOOSER} activity. The IntentSender
+     * will have the extra {@link #EXTRA_CHOSEN_COMPONENT} appended to it containing the
+     * {@link ComponentName} of the chosen component.
+     *
+     * <p>In some situations this callback may never come, for example if the user abandons
+     * the chooser, switches to another task or any number of other reasons. Apps should not
+     * be written assuming that this callback will always occur.</p>
+     */
+    public static final String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER =
+            "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+
+    /**
+     * The {@link ComponentName} chosen by the user to complete an action.
+     *
+     * @see #EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
+     */
+    public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
+
+    /**
      * A {@link android.view.KeyEvent} object containing the event that
      * triggered the creation of the Intent it is in.
      */
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 5267811..0bc1a8d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,13 +16,20 @@
 
 package com.android.internal.app;
 
+import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.util.Log;
+import android.util.Slog;
 
 public class ChooserActivity extends ResolverActivity {
+    private static final String TAG = "ChooserActivity";
+
     private Bundle mReplacementExtras;
+    private IntentSender mChosenComponentSender;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -60,11 +67,14 @@
                 initialIntents[i] = in;
             }
         }
+        mChosenComponentSender = intent.getParcelableExtra(
+                Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
         setSafeForwardingMode(true);
         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                 null, false);
     }
 
+    @Override
     public Intent getReplacementIntent(String packageName, Intent defIntent) {
         if (mReplacementExtras != null) {
             final Bundle replExtras = mReplacementExtras.getBundle(packageName);
@@ -77,6 +87,22 @@
         return defIntent;
     }
 
+    @Override
+    public void onActivityStarted(Intent intent) {
+        if (mChosenComponentSender != null) {
+            final ComponentName target = intent.getComponent();
+            if (target != null) {
+                final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
+                try {
+                    mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
+                } catch (IntentSender.SendIntentException e) {
+                    Slog.e(TAG, "Unable to launch supplied IntentSender to report "
+                            + "the chosen component: " + e);
+                }
+            }
+        }
+    }
+
     private void modifyTargetIntent(Intent in) {
         final String action = in.getAction();
         if (Intent.ACTION_SEND.equals(action) ||
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b9cbc62..ccffa19 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -644,10 +644,12 @@
     public void safelyStartActivity(Intent intent) {
         if (!mSafeForwardingMode) {
             startActivity(intent);
+            onActivityStarted(intent);
             return;
         }
         try {
             startActivityAsCaller(intent, null, UserHandle.USER_NULL);
+            onActivityStarted(intent);
         } catch (RuntimeException e) {
             String launchedFromPackage;
             try {
@@ -662,6 +664,10 @@
         }
     }
 
+    public void onActivityStarted(Intent intent) {
+        // Do nothing
+    }
+
     void showAppDetails(ResolveInfo ri) {
         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))