Merge "Change TextClassification to use RemoteActions" into pi-dev
diff --git a/api/current.txt b/api/current.txt
index dda00ba..9e18b73 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5877,6 +5877,8 @@
     method public java.lang.CharSequence getTitle();
     method public boolean isEnabled();
     method public void setEnabled(boolean);
+    method public void setShouldShowIcon(boolean);
+    method public boolean shouldShowIcon();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR;
   }
@@ -50354,17 +50356,14 @@
 
   public final class TextClassification implements android.os.Parcelable {
     method public int describeContents();
+    method public java.util.List<android.app.RemoteAction> getActions();
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
     method public int getEntityCount();
-    method public android.graphics.drawable.Drawable getIcon();
-    method public android.content.Intent getIntent();
-    method public java.lang.CharSequence getLabel();
-    method public android.view.View.OnClickListener getOnClickListener();
-    method public int getSecondaryActionsCount();
-    method public android.graphics.drawable.Drawable getSecondaryIcon(int);
-    method public android.content.Intent getSecondaryIntent(int);
-    method public java.lang.CharSequence getSecondaryLabel(int);
+    method public deprecated android.graphics.drawable.Drawable getIcon();
+    method public deprecated android.content.Intent getIntent();
+    method public deprecated java.lang.CharSequence getLabel();
+    method public deprecated android.view.View.OnClickListener getOnClickListener();
     method public java.lang.String getSignature();
     method public java.lang.String getText();
     method public void writeToParcel(android.os.Parcel, int);
@@ -50373,15 +50372,13 @@
 
   public static final class TextClassification.Builder {
     ctor public TextClassification.Builder();
-    method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable);
+    method public android.view.textclassifier.TextClassification.Builder addAction(android.app.RemoteAction);
     method public android.view.textclassifier.TextClassification build();
-    method public android.view.textclassifier.TextClassification.Builder clearSecondaryActions();
     method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
-    method public android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
-    method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
-    method public android.view.textclassifier.TextClassification.Builder setLabel(java.lang.String);
-    method public android.view.textclassifier.TextClassification.Builder setOnClickListener(android.view.View.OnClickListener);
-    method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable);
+    method public deprecated android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
+    method public deprecated android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
+    method public deprecated android.view.textclassifier.TextClassification.Builder setLabel(java.lang.String);
+    method public deprecated android.view.textclassifier.TextClassification.Builder setOnClickListener(android.view.View.OnClickListener);
     method public android.view.textclassifier.TextClassification.Builder setSignature(java.lang.String);
     method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String);
   }
diff --git a/core/java/android/app/RemoteAction.java b/core/java/android/app/RemoteAction.java
index e7fe407..47741c0 100644
--- a/core/java/android/app/RemoteAction.java
+++ b/core/java/android/app/RemoteAction.java
@@ -18,14 +18,9 @@
 
 import android.annotation.NonNull;
 import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.text.TextUtils;
-import android.util.Log;
 
 import java.io.PrintWriter;
 
@@ -42,6 +37,7 @@
     private final CharSequence mContentDescription;
     private final PendingIntent mActionIntent;
     private boolean mEnabled;
+    private boolean mShouldShowIcon;
 
     RemoteAction(Parcel in) {
         mIcon = Icon.CREATOR.createFromParcel(in);
@@ -49,6 +45,7 @@
         mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mActionIntent = PendingIntent.CREATOR.createFromParcel(in);
         mEnabled = in.readBoolean();
+        mShouldShowIcon = in.readBoolean();
     }
 
     public RemoteAction(@NonNull Icon icon, @NonNull CharSequence title,
@@ -62,6 +59,7 @@
         mContentDescription = contentDescription;
         mActionIntent = intent;
         mEnabled = true;
+        mShouldShowIcon = true;
     }
 
     /**
@@ -79,6 +77,20 @@
     }
 
     /**
+     * Sets whether the icon should be shown.
+     */
+    public void setShouldShowIcon(boolean shouldShowIcon) {
+        mShouldShowIcon = shouldShowIcon;
+    }
+
+    /**
+     * Return whether the icon should be shown.
+     */
+    public boolean shouldShowIcon() {
+        return mShouldShowIcon;
+    }
+
+    /**
      * Return an icon representing the action.
      */
     public @NonNull Icon getIcon() {
@@ -125,6 +137,7 @@
         TextUtils.writeToParcel(mContentDescription, out, flags);
         mActionIntent.writeToParcel(out, flags);
         out.writeBoolean(mEnabled);
+        out.writeBoolean(mShouldShowIcon);
     }
 
     public void dump(String prefix, PrintWriter pw) {
@@ -134,6 +147,7 @@
         pw.print(" contentDescription=" + mContentDescription);
         pw.print(" icon=" + mIcon);
         pw.print(" action=" + mActionIntent.getIntent());
+        pw.print(" shouldShowIcon=" + mShouldShowIcon);
         pw.println();
     }
 
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index b5c9de9..630007b 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -21,6 +21,8 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -43,6 +45,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -77,25 +80,16 @@
  *   view.startActionMode(new ActionMode.Callback() {
  *
  *       public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- *           // Add the "primary" action.
- *           if (thisAppHasPermissionToInvokeIntent(classification.getIntent())) {
- *              menu.add(Menu.NONE, 0, 20, classification.getLabel())
- *                 .setIcon(classification.getIcon())
- *                 .setIntent(classification.getIntent());
- *           }
- *           // Add the "secondary" actions.
- *           for (int i = 0; i < classification.getSecondaryActionsCount(); i++) {
- *               if (thisAppHasPermissionToInvokeIntent(classification.getSecondaryIntent(i))) {
- *                  menu.add(Menu.NONE, i + 1, 20, classification.getSecondaryLabel(i))
- *                      .setIcon(classification.getSecondaryIcon(i))
- *                      .setIntent(classification.getSecondaryIntent(i));
- *               }
+ *           for (int i = 0; i < classification.getActions().size(); ++i) {
+ *              RemoteAction action = classification.getActions().get(i);
+ *              menu.add(Menu.NONE, i, 20, action.getTitle())
+ *                 .setIcon(action.getIcon());
  *           }
  *           return true;
  *       }
  *
  *       public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- *           context.startActivity(item.getIntent());
+ *           classification.getActions().get(item.getItemId()).getActionIntent().send();
  *           return true;
  *       }
  *
@@ -110,9 +104,9 @@
      */
     static final TextClassification EMPTY = new TextClassification.Builder().build();
 
+    private static final String LOG_TAG = "TextClassification";
     // TODO(toki): investigate a way to derive this based on device properties.
-    private static final int MAX_PRIMARY_ICON_SIZE = 192;
-    private static final int MAX_SECONDARY_ICON_SIZE = 144;
+    private static final int MAX_LEGACY_ICON_SIZE = 192;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {IntentType.UNSUPPORTED, IntentType.ACTIVITY, IntentType.SERVICE})
@@ -123,37 +117,29 @@
     }
 
     @NonNull private final String mText;
-    @Nullable private final Drawable mPrimaryIcon;
-    @Nullable private final String mPrimaryLabel;
-    @Nullable private final Intent mPrimaryIntent;
-    @Nullable private final OnClickListener mPrimaryOnClickListener;
-    @NonNull private final List<Drawable> mSecondaryIcons;
-    @NonNull private final List<String> mSecondaryLabels;
-    @NonNull private final List<Intent> mSecondaryIntents;
+    @Nullable private final Drawable mLegacyIcon;
+    @Nullable private final String mLegacyLabel;
+    @Nullable private final Intent mLegacyIntent;
+    @Nullable private final OnClickListener mLegacyOnClickListener;
+    @NonNull private final List<RemoteAction> mActions;
     @NonNull private final EntityConfidence mEntityConfidence;
     @NonNull private final String mSignature;
 
     private TextClassification(
             @Nullable String text,
-            @Nullable Drawable primaryIcon,
-            @Nullable String primaryLabel,
-            @Nullable Intent primaryIntent,
-            @Nullable OnClickListener primaryOnClickListener,
-            @NonNull List<Drawable> secondaryIcons,
-            @NonNull List<String> secondaryLabels,
-            @NonNull List<Intent> secondaryIntents,
+            @Nullable Drawable legacyIcon,
+            @Nullable String legacyLabel,
+            @Nullable Intent legacyIntent,
+            @Nullable OnClickListener legacyOnClickListener,
+            @NonNull List<RemoteAction> actions,
             @NonNull Map<String, Float> entityConfidence,
             @NonNull String signature) {
-        Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size());
-        Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size());
         mText = text;
-        mPrimaryIcon = primaryIcon;
-        mPrimaryLabel = primaryLabel;
-        mPrimaryIntent = primaryIntent;
-        mPrimaryOnClickListener = primaryOnClickListener;
-        mSecondaryIcons = secondaryIcons;
-        mSecondaryLabels = secondaryLabels;
-        mSecondaryIntents = secondaryIntents;
+        mLegacyIcon = legacyIcon;
+        mLegacyLabel = legacyLabel;
+        mLegacyIntent = legacyIntent;
+        mLegacyOnClickListener = legacyOnClickListener;
+        mActions = Collections.unmodifiableList(actions);
         mEntityConfidence = new EntityConfidence(entityConfidence);
         mSignature = signature;
     }
@@ -197,108 +183,57 @@
     }
 
     /**
-     * Returns the number of <i>secondary</i> actions that are available to act on the classified
-     * text.
-     *
-     * <p><strong>Note: </strong> that there may or may not be a <i>primary</i> action.
-     *
-     * @see #getSecondaryIntent(int)
-     * @see #getSecondaryLabel(int)
-     * @see #getSecondaryIcon(int)
+     * Returns a list of actions that may be performed on the text. The list is ordered based on
+     * the likelihood that a user will use the action, with the most likely action appearing first.
      */
-    @IntRange(from = 0)
-    public int getSecondaryActionsCount() {
-        return mSecondaryIntents.size();
+    public List<RemoteAction> getActions() {
+        return mActions;
     }
 
     /**
-     * Returns one of the <i>secondary</i> icons that maybe rendered on a widget used to act on the
-     * classified text.
+     * Returns an icon that may be rendered on a widget used to act on the classified text.
      *
-     * @param index Index of the action to get the icon for.
-     * @throws IndexOutOfBoundsException if the specified index is out of range.
-     * @see #getSecondaryActionsCount() for the number of actions available.
-     * @see #getSecondaryIntent(int)
-     * @see #getSecondaryLabel(int)
-     * @see #getIcon()
+     * @deprecated Use {@link #getActions()} instead.
      */
-    @Nullable
-    public Drawable getSecondaryIcon(int index) {
-        return mSecondaryIcons.get(index);
-    }
-
-    /**
-     * Returns an icon for the <i>primary</i> intent that may be rendered on a widget used to act
-     * on the classified text.
-     *
-     * @see #getSecondaryIcon(int)
-     */
+    @Deprecated
     @Nullable
     public Drawable getIcon() {
-        return mPrimaryIcon;
+        return mLegacyIcon;
     }
 
     /**
-     * Returns one of the <i>secondary</i> labels that may be rendered on a widget used to act on
-     * the classified text.
+     * Returns a label that may be rendered on a widget used to act on the classified text.
      *
-     * @param index Index of the action to get the label for.
-     * @throws IndexOutOfBoundsException if the specified index is out of range.
-     * @see #getSecondaryActionsCount()
-     * @see #getSecondaryIntent(int)
-     * @see #getSecondaryIcon(int)
-     * @see #getLabel()
+     * @deprecated Use {@link #getActions()} instead.
      */
-    @Nullable
-    public CharSequence getSecondaryLabel(int index) {
-        return mSecondaryLabels.get(index);
-    }
-
-    /**
-     * Returns a label for the <i>primary</i> intent that may be rendered on a widget used to act
-     * on the classified text.
-     *
-     * @see #getSecondaryLabel(int)
-     */
+    @Deprecated
     @Nullable
     public CharSequence getLabel() {
-        return mPrimaryLabel;
+        return mLegacyLabel;
     }
 
     /**
-     * Returns one of the <i>secondary</i> intents that may be fired to act on the classified text.
+     * Returns an intent that may be fired to act on the classified text.
      *
-     * @param index Index of the action to get the intent for.
-     * @throws IndexOutOfBoundsException if the specified index is out of range.
-     * @see #getSecondaryActionsCount()
-     * @see #getSecondaryLabel(int)
-     * @see #getSecondaryIcon(int)
-     * @see #getIntent()
+     * @deprecated Use {@link #getActions()} instead.
      */
-    @Nullable
-    public Intent getSecondaryIntent(int index) {
-        return mSecondaryIntents.get(index);
-    }
-
-    /**
-     * Returns the <i>primary</i> intent that may be fired to act on the classified text.
-     *
-     * @see #getSecondaryIntent(int)
-     */
+    @Deprecated
     @Nullable
     public Intent getIntent() {
-        return mPrimaryIntent;
+        return mLegacyIntent;
     }
 
     /**
-     * Returns the <i>primary</i> OnClickListener that may be triggered to act on the classified
-     * text. This field is not parcelable and will be null for all objects read from a parcel.
-     * Instead, call Context#startActivity(Intent) with the result of #getSecondaryIntent(int).
-     * Note that this may fail if the activity doesn't have permission to send the intent.
+     * Returns the OnClickListener that may be triggered to act on the classified text. This field
+     * is not parcelable and will be null for all objects read from a parcel. Instead, call
+     * Context#startActivity(Intent) with the result of #getSecondaryIntent(int). Note that this may
+     * fail if the activity doesn't have permission to send the intent.
+     *
+     * @deprecated Use {@link #getActions()} instead.
      */
     @Nullable
     public OnClickListener getOnClickListener() {
-        return mPrimaryOnClickListener;
+        return mLegacyOnClickListener;
     }
 
     /**
@@ -313,32 +248,42 @@
 
     @Override
     public String toString() {
-        return String.format(Locale.US, "TextClassification {"
-                        + "text=%s, entities=%s, "
-                        + "primaryLabel=%s, secondaryLabels=%s, "
-                        + "primaryIntent=%s, secondaryIntents=%s, "
-                        + "signature=%s}",
-                mText, mEntityConfidence,
-                mPrimaryLabel, mSecondaryLabels,
-                mPrimaryIntent, mSecondaryIntents,
-                mSignature);
+        return String.format(Locale.US,
+                "TextClassification {text=%s, entities=%s, actions=%s, signature=%s}",
+                mText, mEntityConfidence, mActions, mSignature);
     }
 
     /**
-     * Creates an OnClickListener that triggers the specified intent.
+     * Creates an OnClickListener that triggers the specified PendingIntent.
+     *
+     * @hide
+     */
+    public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) {
+        Preconditions.checkNotNull(intent);
+        return v -> {
+            try {
+                intent.send();
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(LOG_TAG, "Error creating OnClickListener from PendingIntent", e);
+            }
+        };
+    }
+
+    /**
+     * Creates a PendingIntent for the specified intent.
      * Returns null if the intent is not supported for the specified context.
      *
      * @throws IllegalArgumentException if context or intent is null
      * @hide
      */
     @Nullable
-    public static OnClickListener createIntentOnClickListener(
+    public static PendingIntent createPendingIntent(
             @NonNull final Context context, @NonNull final Intent intent) {
         switch (getIntentType(intent, context)) {
             case IntentType.ACTIVITY:
-                return v -> context.startActivity(intent);
+                return PendingIntent.getActivity(context, 0, intent, 0);
             case IntentType.SERVICE:
-                return v -> context.startService(intent);
+                return PendingIntent.getService(context, 0, intent, 0);
             default:
                 return null;
         }
@@ -434,33 +379,6 @@
     }
 
     /**
-     * Returns a list of drawables converted to Bitmaps
-     *
-     * @param drawables The drawables to convert.
-     * @param maxDims The maximum edge length of the resulting bitmaps (in pixels).
-     */
-    private static List<Bitmap> drawablesToBitmaps(List<Drawable> drawables, int maxDims) {
-        final List<Bitmap> bitmaps = new ArrayList<>(drawables.size());
-        for (Drawable drawable : drawables) {
-            bitmaps.add(drawableToBitmap(drawable, maxDims));
-        }
-        return bitmaps;
-    }
-
-    /** Returns a list of drawable wrappers for a list of bitmaps. */
-    private static List<Drawable> bitmapsToDrawables(List<Bitmap> bitmaps) {
-        final List<Drawable> drawables = new ArrayList<>(bitmaps.size());
-        for (Bitmap bitmap : bitmaps) {
-            if (bitmap != null) {
-                drawables.add(new BitmapDrawable(Resources.getSystem(), bitmap));
-            } else {
-                drawables.add(null);
-            }
-        }
-        return drawables;
-    }
-
-    /**
      * Builder for building {@link TextClassification} objects.
      *
      * <p>e.g.
@@ -470,23 +388,20 @@
      *          .setText(classifiedText)
      *          .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
      *          .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
-     *          .setPrimaryAction(intent, label, icon)
-     *          .addSecondaryAction(intent1, label1, icon1)
-     *          .addSecondaryAction(intent2, label2, icon2)
+     *          .addAction(remoteAction1)
+     *          .addAction(remoteAction2)
      *          .build();
      * }</pre>
      */
     public static final class Builder {
 
         @NonNull private String mText;
-        @NonNull private final List<Drawable> mSecondaryIcons = new ArrayList<>();
-        @NonNull private final List<String> mSecondaryLabels = new ArrayList<>();
-        @NonNull private final List<Intent> mSecondaryIntents = new ArrayList<>();
+        @NonNull private List<RemoteAction> mActions = new ArrayList<>();
         @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
-        @Nullable Drawable mPrimaryIcon;
-        @Nullable String mPrimaryLabel;
-        @Nullable Intent mPrimaryIntent;
-        @Nullable OnClickListener mPrimaryOnClickListener;
+        @Nullable Drawable mLegacyIcon;
+        @Nullable String mLegacyLabel;
+        @Nullable Intent mLegacyIntent;
+        @Nullable OnClickListener mLegacyOnClickListener;
         @NonNull private String mSignature = "";
 
         /**
@@ -514,60 +429,25 @@
         }
 
         /**
-         * Adds an <i>secondary</i> action that may be performed on the classified text.
-         * Secondary actions are in addition to the <i>primary</i> action which may or may not
-         * exist.
-         *
-         * <p>The label and icon are used for rendering of widgets that offer the intent.
-         * Actions should be added in order of priority.
-         *
-         * <p><stong>Note: </stong> If all input parameters are set to null, this method will be a
-         * no-op.
-         *
-         * @see #setPrimaryAction(Intent, String, Drawable)
+         * Adds an action that may be performed on the classified text. Actions should be added in
+         * order of likelihood that the user will use them, with the most likely action being added
+         * first.
          */
-        public Builder addSecondaryAction(
-                @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
-            if (intent != null || label != null || icon != null) {
-                mSecondaryIntents.add(intent);
-                mSecondaryLabels.add(label);
-                mSecondaryIcons.add(icon);
-            }
+        public Builder addAction(@NonNull RemoteAction action) {
+            Preconditions.checkArgument(action != null);
+            mActions.add(action);
             return this;
         }
 
         /**
-         * Removes all the <i>secondary</i> actions.
-         */
-        public Builder clearSecondaryActions() {
-            mSecondaryIntents.clear();
-            mSecondaryLabels.clear();
-            mSecondaryIcons.clear();
-            return this;
-        }
-
-        /**
-         * Sets the <i>primary</i> action that may be performed on the classified text. This is
-         * equivalent to calling {@code setIntent(intent).setLabel(label).setIcon(icon)}.
-         *
-         * <p><strong>Note: </strong>If all input parameters are null, there will be no
-         * <i>primary</i> action but there may still be <i>secondary</i> actions.
-         *
-         * @see #addSecondaryAction(Intent, String, Drawable)
-         */
-        public Builder setPrimaryAction(
-                @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
-            return setIntent(intent).setLabel(label).setIcon(icon);
-        }
-
-        /**
          * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
          * on the classified text.
          *
-         * @see #setPrimaryAction(Intent, String, Drawable)
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
+        @Deprecated
         public Builder setIcon(@Nullable Drawable icon) {
-            mPrimaryIcon = icon;
+            mLegacyIcon = icon;
             return this;
         }
 
@@ -575,10 +455,11 @@
          * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
          * act on the classified text.
          *
-         * @see #setPrimaryAction(Intent, String, Drawable)
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
+        @Deprecated
         public Builder setLabel(@Nullable String label) {
-            mPrimaryLabel = label;
+            mLegacyLabel = label;
             return this;
         }
 
@@ -586,10 +467,11 @@
          * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
          * text.
          *
-         * @see #setPrimaryAction(Intent, String, Drawable)
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
+        @Deprecated
         public Builder setIntent(@Nullable Intent intent) {
-            mPrimaryIntent = intent;
+            mLegacyIntent = intent;
             return this;
         }
 
@@ -597,9 +479,11 @@
          * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
          * the classified text. This field is not parcelable and will always be null when the
          * object is read from a parcel.
+         *
+         * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
         public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
-            mPrimaryOnClickListener = onClickListener;
+            mLegacyOnClickListener = onClickListener;
             return this;
         }
 
@@ -617,11 +501,8 @@
          * Builds and returns a {@link TextClassification} object.
          */
         public TextClassification build() {
-            return new TextClassification(
-                    mText,
-                    mPrimaryIcon, mPrimaryLabel, mPrimaryIntent, mPrimaryOnClickListener,
-                    mSecondaryIcons, mSecondaryLabels, mSecondaryIntents,
-                    mEntityConfidence, mSignature);
+            return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
+                    mLegacyOnClickListener, mActions, mEntityConfidence, mSignature);
         }
     }
 
@@ -721,20 +602,18 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mText);
-        final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE);
-        dest.writeInt(primaryIconBitmap != null ? 1 : 0);
-        if (primaryIconBitmap != null) {
-            primaryIconBitmap.writeToParcel(dest, flags);
+        final Bitmap legacyIconBitmap = drawableToBitmap(mLegacyIcon, MAX_LEGACY_ICON_SIZE);
+        dest.writeInt(legacyIconBitmap != null ? 1 : 0);
+        if (legacyIconBitmap != null) {
+            legacyIconBitmap.writeToParcel(dest, flags);
         }
-        dest.writeString(mPrimaryLabel);
-        dest.writeInt(mPrimaryIntent != null ? 1 : 0);
-        if (mPrimaryIntent != null) {
-            mPrimaryIntent.writeToParcel(dest, flags);
+        dest.writeString(mLegacyLabel);
+        dest.writeInt(mLegacyIntent != null ? 1 : 0);
+        if (mLegacyIntent != null) {
+            mLegacyIntent.writeToParcel(dest, flags);
         }
-        // mPrimaryOnClickListener is not parcelable.
-        dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE));
-        dest.writeStringList(mSecondaryLabels);
-        dest.writeTypedList(mSecondaryIntents);
+        // mOnClickListener is not parcelable.
+        dest.writeTypedList(mActions);
         mEntityConfidence.writeToParcel(dest, flags);
         dest.writeString(mSignature);
     }
@@ -754,15 +633,19 @@
 
     private TextClassification(Parcel in) {
         mText = in.readString();
-        mPrimaryIcon = in.readInt() == 0
+        mLegacyIcon = in.readInt() == 0
                 ? null
                 : new BitmapDrawable(Resources.getSystem(), Bitmap.CREATOR.createFromParcel(in));
-        mPrimaryLabel = in.readString();
-        mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in);
-        mPrimaryOnClickListener = null;  // not parcelable
-        mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR));
-        mSecondaryLabels = in.createStringArrayList();
-        mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR);
+        mLegacyLabel = in.readString();
+        if (in.readInt() == 0) {
+            mLegacyIntent = null;
+        } else {
+            mLegacyIntent = Intent.CREATOR.createFromParcel(in);
+            mLegacyIntent.removeFlags(
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        }
+        mLegacyOnClickListener = null;  // not parcelable
+        mActions = in.createTypedArrayList(RemoteAction.CREATOR);
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mSignature = in.readString();
     }
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index c2fb032..a0f4d5c 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
+import android.app.RemoteAction;
 import android.app.SearchManager;
 import android.content.ComponentName;
 import android.content.ContentUris;
@@ -26,7 +27,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -418,50 +419,27 @@
             }
         }
 
-        addActions(builder, IntentFactory.create(
-                mContext, referenceTime, highestScoringResult, classifiedText));
+        boolean isPrimaryAction = true;
+        for (LabeledIntent labeledIntent : IntentFactory.create(
+                mContext, referenceTime, highestScoringResult, classifiedText)) {
+            RemoteAction action = labeledIntent.asRemoteAction(mContext);
+            if (isPrimaryAction) {
+                // For O backwards compatibility, the first RemoteAction is also written to the
+                // legacy API fields.
+                builder.setIcon(action.getIcon().loadDrawable(mContext));
+                builder.setLabel(action.getTitle().toString());
+                builder.setIntent(labeledIntent.getIntent());
+                builder.setOnClickListener(TextClassification.createIntentOnClickListener(
+                        TextClassification.createPendingIntent(mContext,
+                                labeledIntent.getIntent())));
+                isPrimaryAction = false;
+            }
+            builder.addAction(action);
+        }
 
         return builder.setSignature(getSignature(text, start, end)).build();
     }
 
-    /** Extends the classification with the intents that can be resolved. */
-    private void addActions(
-            TextClassification.Builder builder, List<Intent> intents) {
-        final PackageManager pm = mContext.getPackageManager();
-        final int size = intents.size();
-        for (int i = 0; i < size; i++) {
-            final Intent intent = intents.get(i);
-            final ResolveInfo resolveInfo;
-            if (intent != null) {
-                resolveInfo = pm.resolveActivity(intent, 0);
-            } else {
-                resolveInfo = null;
-            }
-            if (resolveInfo != null && resolveInfo.activityInfo != null) {
-                final String packageName = resolveInfo.activityInfo.packageName;
-                final String label = IntentFactory.getLabel(mContext, intent);
-                Drawable icon;
-                if ("android".equals(packageName)) {
-                    // Requires the chooser to find an activity to handle the intent.
-                    icon = null;
-                } else {
-                    // A default activity will handle the intent.
-                    intent.setComponent(
-                            new ComponentName(packageName, resolveInfo.activityInfo.name));
-                    icon = resolveInfo.activityInfo.loadIcon(pm);
-                    if (icon == null) {
-                        icon = resolveInfo.loadIcon(pm);
-                    }
-                }
-                if (i == 0) {
-                    builder.setPrimaryAction(intent, label, icon);
-                } else {
-                    builder.addSecondaryAction(intent, label, icon);
-                }
-            }
-        }
-    }
-
     /**
      * Closes the ParcelFileDescriptor and logs any errors that occur.
      */
@@ -588,6 +566,60 @@
     }
 
     /**
+     * Helper class to store the information from which RemoteActions are built.
+     */
+    private static final class LabeledIntent {
+        private String mTitle;
+        private String mDescription;
+        private Intent mIntent;
+
+        LabeledIntent(String title, String description, Intent intent) {
+            mTitle = title;
+            mDescription = description;
+            mIntent = intent;
+        }
+
+        String getTitle() {
+            return mTitle;
+        }
+
+        String getDescription() {
+            return mDescription;
+        }
+
+        Intent getIntent() {
+            return mIntent;
+        }
+
+        RemoteAction asRemoteAction(Context context) {
+            final PackageManager pm = context.getPackageManager();
+            final ResolveInfo resolveInfo = pm.resolveActivity(mIntent, 0);
+            final String packageName = resolveInfo != null && resolveInfo.activityInfo != null
+                    ? resolveInfo.activityInfo.packageName : null;
+            Icon icon = null;
+            boolean shouldShowIcon = false;
+            if (packageName != null && !"android".equals(packageName)) {
+                // There is a default activity handling the intent.
+                mIntent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
+                if (resolveInfo.activityInfo.getIconResource() != 0) {
+                    icon = Icon.createWithResource(
+                            packageName, resolveInfo.activityInfo.getIconResource());
+                    shouldShowIcon = true;
+                }
+            }
+            if (icon == null) {
+                // RemoteAction requires that there be an icon.
+                icon = Icon.createWithResource("android",
+                        com.android.internal.R.drawable.ic_more_items);
+            }
+            RemoteAction action = new RemoteAction(icon, mTitle, mDescription,
+                    TextClassification.createPendingIntent(context, mIntent));
+            action.setShouldShowIcon(shouldShowIcon);
+            return action;
+        }
+    }
+
+    /**
      * Creates intents based on the classification type.
      */
     static final class IntentFactory {
@@ -598,7 +630,7 @@
         private IntentFactory() {}
 
         @NonNull
-        public static List<Intent> create(
+        public static List<LabeledIntent> create(
                 Context context,
                 @Nullable Calendar referenceTime,
                 TextClassifierImplNative.ClassificationResult classification,
@@ -607,11 +639,11 @@
             text = text.trim();
             switch (type) {
                 case TextClassifier.TYPE_EMAIL:
-                    return createForEmail(text);
+                    return createForEmail(context, text);
                 case TextClassifier.TYPE_PHONE:
                     return createForPhone(context, text);
                 case TextClassifier.TYPE_ADDRESS:
-                    return createForAddress(text);
+                    return createForAddress(context, text);
                 case TextClassifier.TYPE_URL:
                     return createForUrl(context, text);
                 case TextClassifier.TYPE_DATE:
@@ -620,62 +652,80 @@
                         Calendar eventTime = Calendar.getInstance();
                         eventTime.setTimeInMillis(
                                 classification.getDatetimeResult().getTimeMsUtc());
-                        return createForDatetime(type, referenceTime, eventTime);
+                        return createForDatetime(context, type, referenceTime, eventTime);
                     } else {
                         return new ArrayList<>();
                     }
                 case TextClassifier.TYPE_FLIGHT_NUMBER:
-                    return createForFlight(text);
+                    return createForFlight(context, text);
                 default:
                     return new ArrayList<>();
             }
         }
 
         @NonNull
-        private static List<Intent> createForEmail(String text) {
+        private static List<LabeledIntent> createForEmail(Context context, String text) {
             return Arrays.asList(
-                    new Intent(Intent.ACTION_SENDTO)
-                            .setData(Uri.parse(String.format("mailto:%s", text))),
-                    new Intent(Intent.ACTION_INSERT_OR_EDIT)
-                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
-                            .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+                    new LabeledIntent(
+                            context.getString(com.android.internal.R.string.email),
+                            context.getString(com.android.internal.R.string.email_desc),
+                            new Intent(Intent.ACTION_SENDTO)
+                                    .setData(Uri.parse(String.format("mailto:%s", text)))),
+                    new LabeledIntent(
+                            context.getString(com.android.internal.R.string.add_contact),
+                            context.getString(com.android.internal.R.string.add_contact_desc),
+                            new Intent(Intent.ACTION_INSERT_OR_EDIT)
+                                    .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                                    .putExtra(ContactsContract.Intents.Insert.EMAIL, text)));
         }
 
         @NonNull
-        private static List<Intent> createForPhone(Context context, String text) {
-            final List<Intent> intents = new ArrayList<>();
+        private static List<LabeledIntent> createForPhone(Context context, String text) {
+            final List<LabeledIntent> actions = new ArrayList<>();
             final UserManager userManager = context.getSystemService(UserManager.class);
             final Bundle userRestrictions = userManager != null
                     ? userManager.getUserRestrictions() : new Bundle();
             if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
-                intents.add(new Intent(Intent.ACTION_DIAL)
-                        .setData(Uri.parse(String.format("tel:%s", text))));
+                actions.add(new LabeledIntent(
+                        context.getString(com.android.internal.R.string.dial),
+                        context.getString(com.android.internal.R.string.dial_desc),
+                        new Intent(Intent.ACTION_DIAL).setData(
+                                Uri.parse(String.format("tel:%s", text)))));
             }
-            intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
-                    .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
-                    .putExtra(ContactsContract.Intents.Insert.PHONE, text));
+            actions.add(new LabeledIntent(
+                    context.getString(com.android.internal.R.string.add_contact),
+                    context.getString(com.android.internal.R.string.add_contact_desc),
+                    new Intent(Intent.ACTION_INSERT_OR_EDIT)
+                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                            .putExtra(ContactsContract.Intents.Insert.PHONE, text)));
             if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
-                intents.add(new Intent(Intent.ACTION_SENDTO)
-                        .setData(Uri.parse(String.format("smsto:%s", text))));
+                actions.add(new LabeledIntent(
+                        context.getString(com.android.internal.R.string.sms),
+                        context.getString(com.android.internal.R.string.sms_desc),
+                        new Intent(Intent.ACTION_SENDTO)
+                                .setData(Uri.parse(String.format("smsto:%s", text)))));
             }
-            return intents;
+            return actions;
         }
 
         @NonNull
-        private static List<Intent> createForAddress(String text) {
-            final List<Intent> intents = new ArrayList<>();
+        private static List<LabeledIntent> createForAddress(Context context, String text) {
+            final List<LabeledIntent> actions = new ArrayList<>();
             try {
                 final String encText = URLEncoder.encode(text, "UTF-8");
-                intents.add(new Intent(Intent.ACTION_VIEW)
-                        .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))));
+                actions.add(new LabeledIntent(
+                        context.getString(com.android.internal.R.string.map),
+                        context.getString(com.android.internal.R.string.map_desc),
+                        new Intent(Intent.ACTION_VIEW)
+                                .setData(Uri.parse(String.format("geo:0,0?q=%s", encText)))));
             } catch (UnsupportedEncodingException e) {
                 Log.e(LOG_TAG, "Could not encode address", e);
             }
-            return intents;
+            return actions;
         }
 
         @NonNull
-        private static List<Intent> createForUrl(Context context, String text) {
+        private static List<LabeledIntent> createForUrl(Context context, String text) {
             final String httpPrefix = "http://";
             final String httpsPrefix = "https://";
             if (text.toLowerCase().startsWith(httpPrefix)) {
@@ -685,99 +735,65 @@
             } else {
                 text = httpPrefix + text;
             }
-            return Arrays.asList(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
-                    .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
+            return Arrays.asList(new LabeledIntent(
+                    context.getString(com.android.internal.R.string.browse),
+                    context.getString(com.android.internal.R.string.browse_desc),
+                    new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+                            .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName())));
         }
 
         @NonNull
-        private static List<Intent> createForDatetime(
-                String type, @Nullable Calendar referenceTime, Calendar eventTime) {
+        private static List<LabeledIntent> createForDatetime(
+                Context context, String type, @Nullable Calendar referenceTime,
+                Calendar eventTime) {
             if (referenceTime == null) {
                 // If no reference time was given, use now.
                 referenceTime = Calendar.getInstance();
             }
-            List<Intent> intents = new ArrayList<>();
-            intents.add(createCalendarViewIntent(eventTime));
+            List<LabeledIntent> actions = new ArrayList<>();
+            actions.add(createCalendarViewIntent(context, eventTime));
             final long millisSinceReference =
                     eventTime.getTimeInMillis() - referenceTime.getTimeInMillis();
             if (millisSinceReference > MIN_EVENT_FUTURE_MILLIS) {
-                intents.add(createCalendarCreateEventIntent(eventTime, type));
+                actions.add(createCalendarCreateEventIntent(context, eventTime, type));
             }
-            return intents;
+            return actions;
         }
 
         @NonNull
-        private static List<Intent> createForFlight(String text) {
-            return Arrays.asList(new Intent(Intent.ACTION_WEB_SEARCH)
-                    .putExtra(SearchManager.QUERY, text));
+        private static List<LabeledIntent> createForFlight(Context context, String text) {
+            return Arrays.asList(new LabeledIntent(
+                    context.getString(com.android.internal.R.string.view_flight),
+                    context.getString(com.android.internal.R.string.view_flight_desc),
+                    new Intent(Intent.ACTION_WEB_SEARCH)
+                            .putExtra(SearchManager.QUERY, text)));
         }
 
         @NonNull
-        private static Intent createCalendarViewIntent(Calendar eventTime) {
+        private static LabeledIntent createCalendarViewIntent(Context context, Calendar eventTime) {
             Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
             builder.appendPath("time");
             ContentUris.appendId(builder, eventTime.getTimeInMillis());
-            return new Intent(Intent.ACTION_VIEW).setData(builder.build());
+            return new LabeledIntent(
+                    context.getString(com.android.internal.R.string.view_calendar),
+                    context.getString(com.android.internal.R.string.view_calendar_desc),
+                    new Intent(Intent.ACTION_VIEW).setData(builder.build()));
         }
 
         @NonNull
-        private static Intent createCalendarCreateEventIntent(
-                Calendar eventTime, @EntityType String type) {
+        private static LabeledIntent createCalendarCreateEventIntent(
+                Context context, Calendar eventTime, @EntityType String type) {
             final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
-            return new Intent(Intent.ACTION_INSERT)
-                    .setData(CalendarContract.Events.CONTENT_URI)
-                    .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
-                    .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, eventTime.getTimeInMillis())
-                    .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
-                            eventTime.getTimeInMillis() + DEFAULT_EVENT_DURATION);
-        }
-
-        @Nullable
-        public static String getLabel(Context context, @Nullable Intent intent) {
-            if (intent == null || intent.getAction() == null) {
-                return null;
-            }
-            final String authority =
-                    intent.getData() == null ? null : intent.getData().getAuthority();
-            switch (intent.getAction()) {
-                case Intent.ACTION_DIAL:
-                    return context.getString(com.android.internal.R.string.dial);
-                case Intent.ACTION_SENDTO:
-                    if ("mailto".equals(intent.getScheme())) {
-                        return context.getString(com.android.internal.R.string.email);
-                    } else if ("smsto".equals(intent.getScheme())) {
-                        return context.getString(com.android.internal.R.string.sms);
-                    } else {
-                        return null;
-                    }
-                case Intent.ACTION_INSERT:
-                    if (CalendarContract.AUTHORITY.equals(authority)) {
-                        return context.getString(com.android.internal.R.string.add_calendar_event);
-                    }
-                    return null;
-                case Intent.ACTION_INSERT_OR_EDIT:
-                    if (ContactsContract.Contacts.CONTENT_ITEM_TYPE.equals(
-                            intent.getType())) {
-                        return context.getString(com.android.internal.R.string.add_contact);
-                    } else {
-                        return null;
-                    }
-                case Intent.ACTION_VIEW:
-                    if (CalendarContract.AUTHORITY.equals(authority)) {
-                        return context.getString(com.android.internal.R.string.view_calendar);
-                    } else if ("geo".equals(intent.getScheme())) {
-                        return context.getString(com.android.internal.R.string.map);
-                    } else if ("http".equals(intent.getScheme())
-                            || "https".equals(intent.getScheme())) {
-                        return context.getString(com.android.internal.R.string.browse);
-                    } else {
-                        return null;
-                    }
-                case Intent.ACTION_WEB_SEARCH:
-                    return context.getString(com.android.internal.R.string.view_flight);
-                default:
-                    return null;
-            }
+            return new LabeledIntent(
+                    context.getString(com.android.internal.R.string.add_calendar_event),
+                    context.getString(com.android.internal.R.string.add_calendar_event_desc),
+                    new Intent(Intent.ACTION_INSERT)
+                            .setData(CalendarContract.Events.CONTENT_URI)
+                            .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
+                            .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
+                                    eventTime.getTimeInMillis())
+                            .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
+                                    eventTime.getTimeInMillis() + DEFAULT_EVENT_DURATION));
         }
     }
 }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 57d64b9..92f496a8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.app.RemoteAction;
 import android.content.ClipData;
 import android.content.ClipData.Item;
 import android.content.Context;
@@ -4045,43 +4046,46 @@
             if (textClassification == null) {
                 return;
             }
-            final OnClickListener onClick = getSupportedOnClickListener(
-                    textClassification.getIcon(),
-                    textClassification.getLabel(),
-                    textClassification.getIntent());
-            if (onClick != null) {
+            if (!textClassification.getActions().isEmpty()) {
+                // Primary assist action (Always shown).
+                final MenuItem item = addAssistMenuItem(menu,
+                        textClassification.getActions().get(0), TextView.ID_ASSIST,
+                        MENU_ITEM_ORDER_ASSIST, MenuItem.SHOW_AS_ACTION_ALWAYS);
+                item.setIntent(textClassification.getIntent());
+            } else if (hasLegacyAssistItem(textClassification)) {
+                // Legacy primary assist action (Always shown).
                 final MenuItem item = menu.add(
                         TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
                         textClassification.getLabel())
                         .setIcon(textClassification.getIcon())
                         .setIntent(textClassification.getIntent());
                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-                mAssistClickHandlers.put(
-                        item, TextClassification.createIntentOnClickListener(
-                                mTextView.getContext(), textClassification.getIntent()));
+                mAssistClickHandlers.put(item, TextClassification.createIntentOnClickListener(
+                        TextClassification.createPendingIntent(mTextView.getContext(),
+                                textClassification.getIntent())));
             }
-            final int count = textClassification.getSecondaryActionsCount();
-            for (int i = 0; i < count; i++) {
-                final OnClickListener onClick1 = getSupportedOnClickListener(
-                        textClassification.getSecondaryIcon(i),
-                        textClassification.getSecondaryLabel(i),
-                        textClassification.getSecondaryIntent(i));
-                if (onClick1 == null) {
-                    continue;
-                }
-                final int order = MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
-                final MenuItem item = menu.add(
-                        TextView.ID_ASSIST, Menu.NONE, order,
-                        textClassification.getSecondaryLabel(i))
-                        .setIcon(textClassification.getSecondaryIcon(i))
-                        .setIntent(textClassification.getSecondaryIntent(i));
-                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-                mAssistClickHandlers.put(item,
-                        TextClassification.createIntentOnClickListener(
-                                mTextView.getContext(), textClassification.getSecondaryIntent(i)));
+            final int count = textClassification.getActions().size();
+            for (int i = 1; i < count; i++) {
+                // Secondary assist action (Never shown).
+                addAssistMenuItem(menu, textClassification.getActions().get(i), Menu.NONE,
+                        MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1,
+                        MenuItem.SHOW_AS_ACTION_NEVER);
             }
         }
 
+        private MenuItem addAssistMenuItem(Menu menu, RemoteAction action, int intemId, int order,
+                int showAsAction) {
+            final MenuItem item = menu.add(TextView.ID_ASSIST, intemId, order, action.getTitle())
+                    .setContentDescription(action.getContentDescription());
+            if (action.shouldShowIcon()) {
+                item.setIcon(action.getIcon().loadDrawable(mTextView.getContext()));
+            }
+            item.setShowAsAction(showAsAction);
+            mAssistClickHandlers.put(item,
+                    TextClassification.createIntentOnClickListener(action.getActionIntent()));
+            return item;
+        }
+
         private void clearAssistMenuItems(Menu menu) {
             int i = 0;
             while (i < menu.size()) {
@@ -4094,15 +4098,11 @@
             }
         }
 
-        @Nullable
-        private OnClickListener getSupportedOnClickListener(
-                Drawable icon, CharSequence label, Intent intent) {
-            final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
-            if (hasUi) {
-                return TextClassification.createIntentOnClickListener(
-                        mTextView.getContext(), intent);
-            }
-            return null;
+        private boolean hasLegacyAssistItem(TextClassification classification) {
+            // Check whether we have the UI data and and action.
+            return (classification.getIcon() != null || !TextUtils.isEmpty(
+                    classification.getLabel())) && (classification.getIntent() != null
+                    || classification.getOnClickListener() != null);
         }
 
         private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) {
@@ -4120,7 +4120,7 @@
                 final Intent intent = assistMenuItem.getIntent();
                 if (intent != null) {
                     onClickListener = TextClassification.createIntentOnClickListener(
-                            mTextView.getContext(), intent);
+                            TextClassification.createPendingIntent(mTextView.getContext(), intent));
                 }
             }
             if (onClickListener != null) {
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2e8f663..149c88a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2752,30 +2752,57 @@
     <!-- Label for item in the text selection menu to trigger an Email app [CHAR LIMIT=20] -->
     <string name="email">Email</string>
 
+    <!-- Accessibility description for an item in the text selection menu to trigger an Email app [CHAR LIMIT=NONE] -->
+    <string name="email_desc">Email selected address</string>
+
     <!-- Label for item in the text selection menu to trigger a Dialer app [CHAR LIMIT=20] -->
     <string name="dial">Call</string>
 
+    <!-- Accessibility description for an item in the text selection menu to call a phone number [CHAR LIMIT=NONE] -->
+    <string name="dial_desc">Call selected phone number</string>
+
     <!-- Label for item in the text selection menu to trigger a Map app [CHAR LIMIT=20] -->
     <string name="map">Locate</string>
 
+    <!-- Accessibility description for an item in the text selection menu to open maps for an address [CHAR LIMIT=NONE] -->
+    <string name="map_desc">Locale selected address</string>
+
     <!-- Label for item in the text selection menu to trigger a Browser app [CHAR LIMIT=20] -->
     <string name="browse">Open</string>
 
+    <!-- Accessibility description for an item in the text selection menu to open a URL in a browser [CHAR LIMIT=NONE] -->
+    <string name="browse_desc">Open selected URL</string>
+
     <!-- Label for item in the text selection menu to trigger an SMS app [CHAR LIMIT=20] -->
     <string name="sms">Message</string>
 
+    <!-- Accessibility description for an item in the text selection menu to send an SMS to a phone number [CHAR LIMIT=NONE] -->
+    <string name="sms_desc">Message selected phone number</string>
+
     <!-- Label for item in the text selection menu to trigger adding a contact [CHAR LIMIT=20] -->
     <string name="add_contact">Add</string>
 
+    <!-- Accessibility description for an item in the text selection menu to add the selected detail to contacts [CHAR LIMIT=NONE] -->
+    <string name="add_contact_desc">Add to contacts</string>
+
     <!-- Label for item in the text selection menu to view the calendar for the selected time/date [CHAR LIMIT=20] -->
     <string name="view_calendar">View</string>
 
+    <!-- Accessibility description for an item in the text selection menu to view the calendar for a date [CHAR LIMIT=NONE]-->
+    <string name="view_calendar_desc">View selected time in calendar</string>
+
     <!-- Label for item in the text selection menu to create a calendar event at the selected time/date [CHAR LIMIT=20] -->
     <string name="add_calendar_event">Schedule</string>
 
+    <!-- Accessibility description for an item in the text selection menu to schedule an event for a date [CHAR LIMIT=NONE] -->
+    <string name="add_calendar_event_desc">Schedule event for selected time</string>
+
     <!-- Label for item in the text selection menu to track a selected flight number [CHAR LIMIT=20] -->
     <string name="view_flight">Track</string>
 
+    <!-- Accessibility description for an item in the text selection menu to track a flight [CHAR LIMIT=NONE] -->
+    <string name="view_flight_desc">Track selected flight</string>
+
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Storage space running out</string>
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2f7ae27..1182ca7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -545,14 +545,23 @@
   <java-symbol type="string" name="undo" />
   <java-symbol type="string" name="redo" />
   <java-symbol type="string" name="email" />
+  <java-symbol type="string" name="email_desc" />
   <java-symbol type="string" name="dial" />
+  <java-symbol type="string" name="dial_desc" />
   <java-symbol type="string" name="map" />
+  <java-symbol type="string" name="map_desc" />
   <java-symbol type="string" name="browse" />
+  <java-symbol type="string" name="browse_desc" />
   <java-symbol type="string" name="sms" />
+  <java-symbol type="string" name="sms_desc" />
   <java-symbol type="string" name="add_contact" />
+  <java-symbol type="string" name="add_contact_desc" />
   <java-symbol type="string" name="view_calendar" />
+  <java-symbol type="string" name="view_calendar_desc" />
   <java-symbol type="string" name="add_calendar_event" />
+  <java-symbol type="string" name="add_calendar_event_desc" />
   <java-symbol type="string" name="view_flight" />
+  <java-symbol type="string" name="view_flight_desc" />
   <java-symbol type="string" name="textSelectionCABTitle" />
   <java-symbol type="string" name="BaMmi" />
   <java-symbol type="string" name="CLIRDefaultOffNextCallOff" />
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 57db153..f96027d 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -126,11 +126,7 @@
 
         TextClassification classification = mClassifier.classifyText(
                 text, startIndex, endIndex, mClassificationOptions);
-        assertThat(classification,
-                isTextClassification(
-                        classifiedText,
-                        TextClassifier.TYPE_EMAIL,
-                        "mailto:" + classifiedText));
+        assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_EMAIL));
     }
 
     @Test
@@ -144,11 +140,7 @@
 
         TextClassification classification = mClassifier.classifyText(
                 text, startIndex, endIndex, mClassificationOptions);
-        assertThat(classification,
-                isTextClassification(
-                        classifiedText,
-                        TextClassifier.TYPE_URL,
-                        "http://" + classifiedText));
+        assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
     }
 
     @Test
@@ -158,11 +150,7 @@
         String text = "Brandschenkestrasse 110, Zürich, Switzerland";
         TextClassification classification = mClassifier.classifyText(
                 text, 0, text.length(), mClassificationOptions);
-        assertThat(classification,
-                isTextClassification(
-                        text,
-                        TextClassifier.TYPE_ADDRESS,
-                        "geo:0,0?q=Brandschenkestrasse+110%2C+Z%C3%BCrich%2C+Switzerland"));
+        assertThat(classification, isTextClassification(text, TextClassifier.TYPE_ADDRESS));
     }
 
     @Test
@@ -176,11 +164,7 @@
 
         TextClassification classification = mClassifier.classifyText(
                 text, startIndex, endIndex, mClassificationOptions);
-        assertThat(classification,
-                isTextClassification(
-                        classifiedText,
-                        TextClassifier.TYPE_URL,
-                        "http://ANDROID.COM"));
+        assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
     }
 
     @Test
@@ -194,11 +178,7 @@
 
         TextClassification classification = mClassifier.classifyText(
                 text, startIndex, endIndex, mClassificationOptions);
-        assertThat(classification,
-                isTextClassification(
-                        classifiedText,
-                        TextClassifier.TYPE_DATE,
-                        null));
+        assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_DATE));
     }
 
     @Test
@@ -213,10 +193,7 @@
         TextClassification classification = mClassifier.classifyText(
                 text, startIndex, endIndex, mClassificationOptions);
         assertThat(classification,
-                isTextClassification(
-                        classifiedText,
-                        TextClassifier.TYPE_DATE_TIME,
-                        null));
+                isTextClassification(classifiedText, TextClassifier.TYPE_DATE_TIME));
     }
 
     @Test
@@ -355,39 +332,15 @@
     }
 
     private static Matcher<TextClassification> isTextClassification(
-            final String text, final String type, final String intentUri) {
+            final String text, final String type) {
         return new BaseMatcher<TextClassification>() {
             @Override
             public boolean matches(Object o) {
                 if (o instanceof TextClassification) {
                     TextClassification result = (TextClassification) o;
-                    final boolean typeRequirementSatisfied;
-                    String scheme;
-                    switch (type) {
-                        case TextClassifier.TYPE_EMAIL:
-                            scheme = result.getIntent().getData().getScheme();
-                            typeRequirementSatisfied = "mailto".equals(scheme);
-                            break;
-                        case TextClassifier.TYPE_URL:
-                            scheme = result.getIntent().getData().getScheme();
-                            typeRequirementSatisfied = "http".equals(scheme)
-                                    || "https".equals(scheme);
-                            break;
-                        case TextClassifier.TYPE_ADDRESS:
-                            scheme = result.getIntent().getData().getScheme();
-                            typeRequirementSatisfied = "geo".equals(scheme);
-                            break;
-                        default:
-                            typeRequirementSatisfied = true;
-                    }
-
-                    return typeRequirementSatisfied
-                            && text.equals(result.getText())
+                    return text.equals(result.getText())
                             && result.getEntityCount() > 0
-                            && type.equals(result.getEntity(0))
-                            && (intentUri == null
-                                || intentUri.equals(result.getIntent().getDataString()));
-                    // TODO: Include other properties.
+                            && type.equals(result.getEntity(0));
                 }
                 return false;
             }
@@ -395,8 +348,7 @@
             @Override
             public void describeTo(Description description) {
                 description.appendText("text=").appendValue(text)
-                        .appendText(", type=").appendValue(type)
-                        .appendText(", intent.data=").appendValue(intentUri);
+                        .appendText(", type=").appendValue(type);
             }
         };
     }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index ada19fc..afc4bd5 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -17,15 +17,19 @@
 package android.view.textclassifier;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Icon;
 import android.os.LocaleList;
 import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
@@ -41,47 +45,44 @@
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationTest {
 
-    public BitmapDrawable generateTestDrawable(int width, int height, int colorValue) {
+    public Icon generateTestIcon(int width, int height, int colorValue) {
         final int numPixels = width * height;
         final int[] colors = new int[numPixels];
         for (int i = 0; i < numPixels; ++i) {
             colors[i] = colorValue;
         }
         final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
-        final BitmapDrawable drawable = new BitmapDrawable(Resources.getSystem(), bitmap);
-        drawable.setTargetDensity(bitmap.getDensity());
-        return drawable;
+        return Icon.createWithBitmap(bitmap);
     }
 
     @Test
     public void testParcel() {
+        final Context context = InstrumentationRegistry.getTargetContext();
         final String text = "text";
-        final BitmapDrawable primaryIcon = generateTestDrawable(16, 16, Color.RED);
-        final String primaryLabel = "primarylabel";
-        final Intent primaryIntent = new Intent("primaryintentaction");
-        final View.OnClickListener primaryOnClick = v -> { };
-        final BitmapDrawable secondaryIcon0 = generateTestDrawable(32, 288, Color.GREEN);
-        final String secondaryLabel0 = "secondarylabel0";
-        final Intent secondaryIntent0 = new Intent("secondaryintentaction0");
-        final BitmapDrawable secondaryIcon1 = generateTestDrawable(576, 288, Color.BLUE);
-        final String secondaryLabel1 = "secondaryLabel1";
-        final Intent secondaryIntent1 = null;
-        final BitmapDrawable secondaryIcon2 = null;
-        final String secondaryLabel2 = null;
-        final Intent secondaryIntent2 = new Intent("secondaryintentaction2");
-        final ColorDrawable secondaryIcon3 = new ColorDrawable(Color.CYAN);
-        final String secondaryLabel3 = null;
-        final Intent secondaryIntent3 = null;
+
+        final Icon primaryIcon = generateTestIcon(576, 288, Color.BLUE);
+        final String primaryLabel = "primaryLabel";
+        final String primaryDescription = "primaryDescription";
+        final Intent primaryIntent = new Intent("primaryIntentAction");
+        final PendingIntent primaryPendingIntent = PendingIntent.getActivity(context, 0,
+                primaryIntent, 0);
+        final RemoteAction remoteAction0 = new RemoteAction(primaryIcon, primaryLabel,
+                primaryDescription, primaryPendingIntent);
+
+        final Icon secondaryIcon = generateTestIcon(32, 288, Color.GREEN);
+        final String secondaryLabel = "secondaryLabel";
+        final String secondaryDescription = "secondaryDescription";
+        final Intent secondaryIntent = new Intent("secondaryIntentAction");
+        final PendingIntent secondaryPendingIntent = PendingIntent.getActivity(context, 0,
+                secondaryIntent, 0);
+        final RemoteAction remoteAction1 = new RemoteAction(secondaryIcon, secondaryLabel,
+                secondaryDescription, secondaryPendingIntent);
+
         final String signature = "signature";
         final TextClassification reference = new TextClassification.Builder()
                 .setText(text)
-                .setPrimaryAction(primaryIntent, primaryLabel, primaryIcon)
-                .setOnClickListener(primaryOnClick)
-                .addSecondaryAction(null, null, null)  // ignored
-                .addSecondaryAction(secondaryIntent0, secondaryLabel0, secondaryIcon0)
-                .addSecondaryAction(secondaryIntent1, secondaryLabel1, secondaryIcon1)
-                .addSecondaryAction(secondaryIntent2, secondaryLabel2, secondaryIcon2)
-                .addSecondaryAction(secondaryIntent3, secondaryLabel3, secondaryIcon3)
+                .addAction(remoteAction0)
+                .addAction(remoteAction1)
                 .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
                 .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
                 .setSignature(signature)
@@ -95,45 +96,25 @@
 
         assertEquals(text, result.getText());
         assertEquals(signature, result.getSignature());
-        assertEquals(4, result.getSecondaryActionsCount());
+        assertEquals(2, result.getActions().size());
 
-        // Primary action (re-use existing icon).
-        final Bitmap resPrimaryIcon = ((BitmapDrawable) result.getIcon()).getBitmap();
-        assertEquals(primaryIcon.getBitmap().getPixel(0, 0), resPrimaryIcon.getPixel(0, 0));
-        assertEquals(16, resPrimaryIcon.getWidth());
-        assertEquals(16, resPrimaryIcon.getHeight());
-        assertEquals(primaryLabel, result.getLabel());
-        assertEquals(primaryIntent.getAction(), result.getIntent().getAction());
-        assertEquals(null, result.getOnClickListener());  // Non-parcelable.
+        // Legacy API.
+        assertNull(result.getIcon());
+        assertNull(result.getLabel());
+        assertNull(result.getIntent());
+        assertNull(result.getOnClickListener());
 
-        // Secondary action 0 (scale with  height limit).
-        final Bitmap resSecondaryIcon0 = ((BitmapDrawable) result.getSecondaryIcon(0)).getBitmap();
-        assertEquals(secondaryIcon0.getBitmap().getPixel(0, 0), resSecondaryIcon0.getPixel(0, 0));
-        assertEquals(16, resSecondaryIcon0.getWidth());
-        assertEquals(144, resSecondaryIcon0.getHeight());
-        assertEquals(secondaryLabel0, result.getSecondaryLabel(0));
-        assertEquals(secondaryIntent0.getAction(), result.getSecondaryIntent(0).getAction());
+        // Primary action.
+        final RemoteAction primaryAction = result.getActions().get(0);
+        assertEquals(primaryLabel, primaryAction.getTitle());
+        assertEquals(primaryDescription, primaryAction.getContentDescription());
+        assertEquals(primaryPendingIntent, primaryAction.getActionIntent());
 
-        // Secondary action 1 (scale with width limit).
-        final Bitmap resSecondaryIcon1 = ((BitmapDrawable) result.getSecondaryIcon(1)).getBitmap();
-        assertEquals(secondaryIcon1.getBitmap().getPixel(0, 0), resSecondaryIcon1.getPixel(0, 0));
-        assertEquals(144, resSecondaryIcon1.getWidth());
-        assertEquals(72, resSecondaryIcon1.getHeight());
-        assertEquals(secondaryLabel1, result.getSecondaryLabel(1));
-        assertEquals(null, result.getSecondaryIntent(1));
-
-        // Secondary action 2 (no icon).
-        assertEquals(null, result.getSecondaryIcon(2));
-        assertEquals(null, result.getSecondaryLabel(2));
-        assertEquals(secondaryIntent2.getAction(), result.getSecondaryIntent(2).getAction());
-
-        // Secondary action 3 (convert non-bitmap drawable with negative size).
-        final Bitmap resSecondaryIcon3 = ((BitmapDrawable) result.getSecondaryIcon(3)).getBitmap();
-        assertEquals(secondaryIcon3.getColor(), resSecondaryIcon3.getPixel(0, 0));
-        assertEquals(1, resSecondaryIcon3.getWidth());
-        assertEquals(1, resSecondaryIcon3.getHeight());
-        assertEquals(null, result.getSecondaryLabel(3));
-        assertEquals(null, result.getSecondaryIntent(3));
+        // Secondary action.
+        final RemoteAction secondaryAction = result.getActions().get(1);
+        assertEquals(secondaryLabel, secondaryAction.getTitle());
+        assertEquals(secondaryDescription, secondaryAction.getContentDescription());
+        assertEquals(secondaryPendingIntent, secondaryAction.getActionIntent());
 
         // Entities.
         assertEquals(2, result.getEntityCount());
@@ -144,6 +125,43 @@
     }
 
     @Test
+    public void testParcelLegacy() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final String text = "text";
+
+        final Icon icon = generateTestIcon(384, 192, Color.BLUE);
+        final String label = "label";
+        final Intent intent = new Intent("intent");
+        final View.OnClickListener onClickListener = v -> { };
+
+        final String signature = "signature";
+        final TextClassification reference = new TextClassification.Builder()
+                .setText(text)
+                .setIcon(icon.loadDrawable(context))
+                .setLabel(label)
+                .setIntent(intent)
+                .setOnClickListener(onClickListener)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
+                .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
+                .setSignature(signature)
+                .build();
+
+        // Parcel and unparcel
+        final Parcel parcel = Parcel.obtain();
+        reference.writeToParcel(parcel, reference.describeContents());
+        parcel.setDataPosition(0);
+        final TextClassification result = TextClassification.CREATOR.createFromParcel(parcel);
+
+        final Bitmap resultIcon = ((BitmapDrawable) result.getIcon()).getBitmap();
+        assertEquals(icon.getBitmap().getPixel(0, 0), resultIcon.getPixel(0, 0));
+        assertEquals(192, resultIcon.getWidth());
+        assertEquals(96, resultIcon.getHeight());
+        assertEquals(label, result.getLabel());
+        assertEquals(intent.getAction(), result.getIntent().getAction());
+        assertNull(result.getOnClickListener());
+    }
+
+    @Test
     public void testParcelOptions() {
         Calendar referenceTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US);
         referenceTime.setTimeInMillis(946771200000L);  // 2000-01-02