Merge "Change TextClassification to use RemoteActions" into pi-dev
am: 08a75e0ee7
Change-Id: I411c8b207e24c6a4e9ca8660619fae1e0a3a8ef7
This commit is contained in:
@@ -5877,6 +5877,8 @@ package android.app {
|
||||
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 @@ package android.view.textclassifier {
|
||||
|
||||
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 @@ package android.view.textclassifier {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -18,14 +18,9 @@ package android.app;
|
||||
|
||||
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 @@ public final class RemoteAction implements Parcelable {
|
||||
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 @@ public final class RemoteAction implements Parcelable {
|
||||
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 @@ public final class RemoteAction implements Parcelable {
|
||||
mContentDescription = contentDescription;
|
||||
mActionIntent = intent;
|
||||
mEnabled = true;
|
||||
mShouldShowIcon = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +76,20 @@ public final class RemoteAction implements Parcelable {
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -125,6 +137,7 @@ public final class RemoteAction implements Parcelable {
|
||||
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 @@ public final class RemoteAction implements Parcelable {
|
||||
pw.print(" contentDescription=" + mContentDescription);
|
||||
pw.print(" icon=" + mIcon);
|
||||
pw.print(" action=" + mActionIntent.getIntent());
|
||||
pw.print(" shouldShowIcon=" + mShouldShowIcon);
|
||||
pw.println();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.annotation.IntDef;
|
||||
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.Retention;
|
||||
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 @@ import java.util.Map;
|
||||
* 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 @@ public final class TextClassification implements Parcelable {
|
||||
*/
|
||||
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 @@ public final class TextClassification implements Parcelable {
|
||||
}
|
||||
|
||||
@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 @@ public final class TextClassification implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
*/
|
||||
@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 Use {@link #getActions()} instead.
|
||||
*/
|
||||
@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()
|
||||
*/
|
||||
@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 Use {@link #getActions()} instead.
|
||||
*/
|
||||
@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()
|
||||
*/
|
||||
@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 Use {@link #getActions()} instead.
|
||||
*/
|
||||
@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 @@ public final class TextClassification implements Parcelable {
|
||||
|
||||
@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;
|
||||
}
|
||||
@@ -433,33 +378,6 @@ public final class TextClassification implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -470,23 +388,20 @@ public final class TextClassification implements Parcelable {
|
||||
* .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 @@ public final class TextClassification implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public final class TextClassification implements Parcelable {
|
||||
* 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 @@ public final class TextClassification implements Parcelable {
|
||||
* 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 @@ public final class TextClassification implements Parcelable {
|
||||
* 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 @@ public final class TextClassification implements Parcelable {
|
||||
* 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 @@ public final class TextClassification implements Parcelable {
|
||||
@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 @@ public final class TextClassification implements Parcelable {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package android.view.textclassifier;
|
||||
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.Context;
|
||||
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 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -587,6 +565,60 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -598,7 +630,7 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
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 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
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 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
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 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
|
||||
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 @@ public class Editor {
|
||||
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 @@ public class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
@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 @@ public class Editor {
|
||||
final Intent intent = assistMenuItem.getIntent();
|
||||
if (intent != null) {
|
||||
onClickListener = TextClassification.createIntentOnClickListener(
|
||||
mTextView.getContext(), intent);
|
||||
TextClassification.createPendingIntent(mTextView.getContext(), intent));
|
||||
}
|
||||
}
|
||||
if (onClickListener != null) {
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -126,11 +126,7 @@ public class TextClassificationManagerTest {
|
||||
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
}
|
||||
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("text=").appendValue(text)
|
||||
.appendText(", type=").appendValue(type)
|
||||
.appendText(", intent.data=").appendValue(intentUri);
|
||||
.appendText(", type=").appendValue(type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 @@ import java.util.TimeZone;
|
||||
@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 @@ public class TextClassificationTest {
|
||||
|
||||
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());
|
||||
@@ -143,6 +124,43 @@ public class TextClassificationTest {
|
||||
assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
Reference in New Issue
Block a user