/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.textclassifier; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.FloatRange; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.collection.ArrayMap; import androidx.core.os.LocaleListCompat; import androidx.core.util.Preconditions; import androidx.textclassifier.TextClassifier.EntityType; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.Map; /** * Information for generating a widget to handle classified text. * *
A TextClassification object contains icons, labels, and intents that may be used to build a * widget that can be used to act on classified text. There is the concept of a primary * action and other secondary actions. * *
e.g. building a view that, when clicked, shares the classified text with the preferred app: * *
{@code * // Called preferably outside the UiThread. * TextClassification classification = textClassifier.classifyText(allText, 10, 25); * * // Called on the UiThread. * Button button = new Button(context); * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); * button.setText(classification.getLabel()); * button.setOnClickListener(v -> context.startActivity(classification.getIntent())); * }* * TODO: describe how to start action mode for classified text. */ public final class TextClassification implements Parcelable { /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) static final TextClassification EMPTY = new TextClassification.Builder().build(); // TODO: 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; @Nullable private final String mText; @Nullable private final Drawable mPrimaryIcon; @Nullable private final String mPrimaryLabel; @Nullable private final Intent mPrimaryIntent; @NonNull private final List
Note: that there may or may not be a primary action.
*
* @see #getSecondaryIntent(int)
* @see #getSecondaryLabel(int)
* @see #getSecondaryIcon(int)
*/
@IntRange(from = 0)
public int getSecondaryActionsCount() {
return mSecondaryIntents.size();
}
/**
* Returns one of the secondary icons that maybe 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 primary intent that may be rendered on a widget used to act
* on the classified text.
*
* @see #getSecondaryIcon(int)
*/
@Nullable
public Drawable getIcon() {
return mPrimaryIcon;
}
/**
* Returns one of the secondary labels 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 primary intent that may be rendered on a widget used to act
* on the classified text.
*
* @see #getSecondaryLabel(int)
*/
@Nullable
public CharSequence getLabel() {
return mPrimaryLabel;
}
/**
* Returns one of the secondary intents 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 primary intent that may be fired to act on the classified text.
*
* @see #getSecondaryIntent(int)
*/
@Nullable
public Intent getIntent() {
return mPrimaryIntent;
}
/**
* Returns the signature for this object.
* The TextClassifier that generates this object may use it as a way to internally identify
* this object.
*/
@NonNull
public String getSignature() {
return mSignature;
}
@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);
}
@Override
public int describeContents() {
return 0;
}
@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);
}
dest.writeString(mPrimaryLabel);
dest.writeInt(mPrimaryIntent != null ? 1 : 0);
if (mPrimaryIntent != null) {
mPrimaryIntent.writeToParcel(dest, flags);
}
dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE));
dest.writeStringList(mSecondaryLabels);
dest.writeTypedList(mSecondaryIntents);
mEntityConfidence.writeToParcel(dest, flags);
dest.writeString(mSignature);
}
public static final Parcelable.Creator e.g.
*
* The label and icon are used for rendering of widgets that offer the intent.
* Actions should be added in order of priority.
*
* Note: If all input parameters are null, there will be no
* primary action but there may still be secondary 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 primary action that may be rendered on a widget used to act
* on the classified text.
*
* @see #setPrimaryAction(Intent, String, Drawable)
*/
public Builder setIcon(@Nullable Drawable icon) {
mPrimaryIcon = icon;
return this;
}
/**
* Sets the label for the primary action that may be rendered on a widget used to
* act on the classified text.
*
* @see #setPrimaryAction(Intent, String, Drawable)
*/
public Builder setLabel(@Nullable String label) {
mPrimaryLabel = label;
return this;
}
/**
* Sets the intent for the primary action that may be fired to act on the classified
* text.
*
* @see #setPrimaryAction(Intent, String, Drawable)
*/
public Builder setIntent(@Nullable Intent intent) {
mPrimaryIntent = intent;
return this;
}
/**
* Sets a signature for the TextClassification object.
* The TextClassifier that generates the TextClassification object may use it as a way to
* internally identify the TextClassification object.
*/
public Builder setSignature(@NonNull String signature) {
mSignature = Preconditions.checkNotNull(signature);
return this;
}
/**
* Builds and returns a {@link TextClassification} object.
*/
public TextClassification build() {
return new TextClassification(
mText,
mPrimaryIcon, mPrimaryLabel, mPrimaryIntent,
mSecondaryIcons, mSecondaryLabels, mSecondaryIntents,
mEntityConfidence, mSignature);
}
}
/**
* Optional input parameters for generating TextClassification.
*/
public static final class Options implements Parcelable {
private @Nullable LocaleListCompat mDefaultLocales;
private @Nullable Calendar mReferenceTime;
private @Nullable String mCallingPackageName;
public Options() {}
/**
* @param defaultLocales ordered list of locale preferences that may be used to disambiguate
* the provided text. If no locale preferences exist, set this to null or an empty
* locale list.
*/
public Options setDefaultLocales(@Nullable LocaleListCompat defaultLocales) {
mDefaultLocales = defaultLocales;
return this;
}
/**
* @param referenceTime reference time based on which relative dates (e.g. "tomorrow" should
* be interpreted. This should usually be the time when the text was originally
* composed. If no reference time is set, now is used.
*/
public Options setReferenceTime(Calendar referenceTime) {
mReferenceTime = referenceTime;
return this;
}
/**
* @param packageName name of the package from which the call was made.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public Options setCallingPackageName(@Nullable String packageName) {
mCallingPackageName = packageName;
return this;
}
/**
* @return ordered list of locale preferences that can be used to disambiguate
* the provided text.
*/
@Nullable
public LocaleListCompat getDefaultLocales() {
return mDefaultLocales;
}
/**
* @return reference time based on which relative dates (e.g. "tomorrow") should be
* interpreted.
*/
@Nullable
public Calendar getReferenceTime() {
return mReferenceTime;
}
/**
* @return name of the package from which the call was made.
*/
@Nullable
public String getCallingPackageName() {
return mCallingPackageName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mDefaultLocales != null ? 1 : 0);
if (mDefaultLocales != null) {
dest.writeString(mDefaultLocales.toLanguageTags());
}
dest.writeInt(mReferenceTime != null ? 1 : 0);
if (mReferenceTime != null) {
dest.writeSerializable(mReferenceTime);
}
dest.writeString(mCallingPackageName);
}
public static final Parcelable.Creator{@code
* TextClassification classification = new TextClassification.Builder()
* .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)
* .build();
* }
*/
public static final class Builder {
@NonNull private String mText;
@NonNull private final List