Home | History | Annotate | Download | only in textclassifier
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view.textclassifier;
     18 
     19 import android.annotation.FloatRange;
     20 import android.annotation.IntDef;
     21 import android.annotation.IntRange;
     22 import android.annotation.NonNull;
     23 import android.annotation.Nullable;
     24 import android.app.PendingIntent;
     25 import android.app.RemoteAction;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.pm.PackageManager;
     29 import android.content.pm.ResolveInfo;
     30 import android.content.res.Resources;
     31 import android.graphics.BitmapFactory;
     32 import android.graphics.drawable.AdaptiveIconDrawable;
     33 import android.graphics.drawable.BitmapDrawable;
     34 import android.graphics.drawable.Drawable;
     35 import android.graphics.drawable.Icon;
     36 import android.os.LocaleList;
     37 import android.os.Parcel;
     38 import android.os.Parcelable;
     39 import android.util.ArrayMap;
     40 import android.view.View.OnClickListener;
     41 import android.view.textclassifier.TextClassifier.EntityType;
     42 import android.view.textclassifier.TextClassifier.Utils;
     43 
     44 import com.android.internal.util.Preconditions;
     45 
     46 import java.lang.annotation.Retention;
     47 import java.lang.annotation.RetentionPolicy;
     48 import java.time.ZonedDateTime;
     49 import java.util.ArrayList;
     50 import java.util.Collections;
     51 import java.util.List;
     52 import java.util.Locale;
     53 import java.util.Map;
     54 
     55 /**
     56  * Information for generating a widget to handle classified text.
     57  *
     58  * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
     59  * be used to build a widget that can be used to act on classified text. There is the concept of a
     60  * <i>primary action</i> and other <i>secondary actions</i>.
     61  *
     62  * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
     63  *
     64  * <pre>{@code
     65  *   // Called preferably outside the UiThread.
     66  *   TextClassification classification = textClassifier.classifyText(allText, 10, 25);
     67  *
     68  *   // Called on the UiThread.
     69  *   Button button = new Button(context);
     70  *   button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
     71  *   button.setText(classification.getLabel());
     72  *   button.setOnClickListener(v -> classification.getActions().get(0).getActionIntent().send());
     73  * }</pre>
     74  *
     75  * <p>e.g. starting an action mode with menu items that can handle the classified text:
     76  *
     77  * <pre>{@code
     78  *   // Called preferably outside the UiThread.
     79  *   final TextClassification classification = textClassifier.classifyText(allText, 10, 25);
     80  *
     81  *   // Called on the UiThread.
     82  *   view.startActionMode(new ActionMode.Callback() {
     83  *
     84  *       public boolean onCreateActionMode(ActionMode mode, Menu menu) {
     85  *           for (int i = 0; i < classification.getActions().size(); ++i) {
     86  *              RemoteAction action = classification.getActions().get(i);
     87  *              menu.add(Menu.NONE, i, 20, action.getTitle())
     88  *                 .setIcon(action.getIcon());
     89  *           }
     90  *           return true;
     91  *       }
     92  *
     93  *       public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
     94  *           classification.getActions().get(item.getItemId()).getActionIntent().send();
     95  *           return true;
     96  *       }
     97  *
     98  *       ...
     99  *   });
    100  * }</pre>
    101  */
    102 public final class TextClassification implements Parcelable {
    103 
    104     /**
    105      * @hide
    106      */
    107     static final TextClassification EMPTY = new TextClassification.Builder().build();
    108 
    109     private static final String LOG_TAG = "TextClassification";
    110     // TODO(toki): investigate a way to derive this based on device properties.
    111     private static final int MAX_LEGACY_ICON_SIZE = 192;
    112 
    113     @Retention(RetentionPolicy.SOURCE)
    114     @IntDef(value = {IntentType.UNSUPPORTED, IntentType.ACTIVITY, IntentType.SERVICE})
    115     private @interface IntentType {
    116         int UNSUPPORTED = -1;
    117         int ACTIVITY = 0;
    118         int SERVICE = 1;
    119     }
    120 
    121     @NonNull private final String mText;
    122     @Nullable private final Drawable mLegacyIcon;
    123     @Nullable private final String mLegacyLabel;
    124     @Nullable private final Intent mLegacyIntent;
    125     @Nullable private final OnClickListener mLegacyOnClickListener;
    126     @NonNull private final List<RemoteAction> mActions;
    127     @NonNull private final EntityConfidence mEntityConfidence;
    128     @Nullable private final String mId;
    129 
    130     private TextClassification(
    131             @Nullable String text,
    132             @Nullable Drawable legacyIcon,
    133             @Nullable String legacyLabel,
    134             @Nullable Intent legacyIntent,
    135             @Nullable OnClickListener legacyOnClickListener,
    136             @NonNull List<RemoteAction> actions,
    137             @NonNull Map<String, Float> entityConfidence,
    138             @Nullable String id) {
    139         mText = text;
    140         mLegacyIcon = legacyIcon;
    141         mLegacyLabel = legacyLabel;
    142         mLegacyIntent = legacyIntent;
    143         mLegacyOnClickListener = legacyOnClickListener;
    144         mActions = Collections.unmodifiableList(actions);
    145         mEntityConfidence = new EntityConfidence(entityConfidence);
    146         mId = id;
    147     }
    148 
    149     /**
    150      * Gets the classified text.
    151      */
    152     @Nullable
    153     public String getText() {
    154         return mText;
    155     }
    156 
    157     /**
    158      * Returns the number of entities found in the classified text.
    159      */
    160     @IntRange(from = 0)
    161     public int getEntityCount() {
    162         return mEntityConfidence.getEntities().size();
    163     }
    164 
    165     /**
    166      * Returns the entity at the specified index. Entities are ordered from high confidence
    167      * to low confidence.
    168      *
    169      * @throws IndexOutOfBoundsException if the specified index is out of range.
    170      * @see #getEntityCount() for the number of entities available.
    171      */
    172     @NonNull
    173     public @EntityType String getEntity(int index) {
    174         return mEntityConfidence.getEntities().get(index);
    175     }
    176 
    177     /**
    178      * Returns the confidence score for the specified entity. The value ranges from
    179      * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
    180      * classified text.
    181      */
    182     @FloatRange(from = 0.0, to = 1.0)
    183     public float getConfidenceScore(@EntityType String entity) {
    184         return mEntityConfidence.getConfidenceScore(entity);
    185     }
    186 
    187     /**
    188      * Returns a list of actions that may be performed on the text. The list is ordered based on
    189      * the likelihood that a user will use the action, with the most likely action appearing first.
    190      */
    191     public List<RemoteAction> getActions() {
    192         return mActions;
    193     }
    194 
    195     /**
    196      * Returns an icon that may be rendered on a widget used to act on the classified text.
    197      *
    198      * <p><strong>NOTE: </strong>This field is not parcelable and only represents the icon of the
    199      * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
    200      *
    201      * @deprecated Use {@link #getActions()} instead.
    202      */
    203     @Deprecated
    204     @Nullable
    205     public Drawable getIcon() {
    206         return mLegacyIcon;
    207     }
    208 
    209     /**
    210      * Returns a label that may be rendered on a widget used to act on the classified text.
    211      *
    212      * <p><strong>NOTE: </strong>This field is not parcelable and only represents the label of the
    213      * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
    214      *
    215      * @deprecated Use {@link #getActions()} instead.
    216      */
    217     @Deprecated
    218     @Nullable
    219     public CharSequence getLabel() {
    220         return mLegacyLabel;
    221     }
    222 
    223     /**
    224      * Returns an intent that may be fired to act on the classified text.
    225      *
    226      * <p><strong>NOTE: </strong>This field is not parcelled and will always return null when this
    227      * object is read from a parcel.
    228      *
    229      * @deprecated Use {@link #getActions()} instead.
    230      */
    231     @Deprecated
    232     @Nullable
    233     public Intent getIntent() {
    234         return mLegacyIntent;
    235     }
    236 
    237     /**
    238      * Returns the OnClickListener that may be triggered to act on the classified text.
    239      *
    240      * <p><strong>NOTE: </strong>This field is not parcelable and only represents the first
    241      * {@link RemoteAction} (if one exists) when this object is read from a parcel.
    242      *
    243      * @deprecated Use {@link #getActions()} instead.
    244      */
    245     @Nullable
    246     public OnClickListener getOnClickListener() {
    247         return mLegacyOnClickListener;
    248     }
    249 
    250     /**
    251      * Returns the id, if one exists, for this object.
    252      */
    253     @Nullable
    254     public String getId() {
    255         return mId;
    256     }
    257 
    258     @Override
    259     public String toString() {
    260         return String.format(Locale.US,
    261                 "TextClassification {text=%s, entities=%s, actions=%s, id=%s}",
    262                 mText, mEntityConfidence, mActions, mId);
    263     }
    264 
    265     /**
    266      * Creates an OnClickListener that triggers the specified PendingIntent.
    267      *
    268      * @hide
    269      */
    270     public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) {
    271         Preconditions.checkNotNull(intent);
    272         return v -> {
    273             try {
    274                 intent.send();
    275             } catch (PendingIntent.CanceledException e) {
    276                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
    277             }
    278         };
    279     }
    280 
    281     /**
    282      * Creates a PendingIntent for the specified intent.
    283      * Returns null if the intent is not supported for the specified context.
    284      *
    285      * @throws IllegalArgumentException if context or intent is null
    286      * @hide
    287      */
    288     @Nullable
    289     public static PendingIntent createPendingIntent(
    290             @NonNull final Context context, @NonNull final Intent intent, int requestCode) {
    291         final int flags = PendingIntent.FLAG_UPDATE_CURRENT;
    292         switch (getIntentType(intent, context)) {
    293             case IntentType.ACTIVITY:
    294                 return PendingIntent.getActivity(context, requestCode, intent, flags);
    295             case IntentType.SERVICE:
    296                 return PendingIntent.getService(context, requestCode, intent, flags);
    297             default:
    298                 return null;
    299         }
    300     }
    301 
    302     @IntentType
    303     private static int getIntentType(@NonNull Intent intent, @NonNull Context context) {
    304         Preconditions.checkArgument(context != null);
    305         Preconditions.checkArgument(intent != null);
    306 
    307         final ResolveInfo activityRI = context.getPackageManager().resolveActivity(intent, 0);
    308         if (activityRI != null) {
    309             if (context.getPackageName().equals(activityRI.activityInfo.packageName)) {
    310                 return IntentType.ACTIVITY;
    311             }
    312             final boolean exported = activityRI.activityInfo.exported;
    313             if (exported && hasPermission(context, activityRI.activityInfo.permission)) {
    314                 return IntentType.ACTIVITY;
    315             }
    316         }
    317 
    318         final ResolveInfo serviceRI = context.getPackageManager().resolveService(intent, 0);
    319         if (serviceRI != null) {
    320             if (context.getPackageName().equals(serviceRI.serviceInfo.packageName)) {
    321                 return IntentType.SERVICE;
    322             }
    323             final boolean exported = serviceRI.serviceInfo.exported;
    324             if (exported && hasPermission(context, serviceRI.serviceInfo.permission)) {
    325                 return IntentType.SERVICE;
    326             }
    327         }
    328 
    329         return IntentType.UNSUPPORTED;
    330     }
    331 
    332     private static boolean hasPermission(@NonNull Context context, @NonNull String permission) {
    333         return permission == null
    334                 || context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
    335     }
    336 
    337     /**
    338      * Builder for building {@link TextClassification} objects.
    339      *
    340      * <p>e.g.
    341      *
    342      * <pre>{@code
    343      *   TextClassification classification = new TextClassification.Builder()
    344      *          .setText(classifiedText)
    345      *          .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
    346      *          .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
    347      *          .addAction(remoteAction1)
    348      *          .addAction(remoteAction2)
    349      *          .build();
    350      * }</pre>
    351      */
    352     public static final class Builder {
    353 
    354         @NonNull private List<RemoteAction> mActions = new ArrayList<>();
    355         @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
    356         @Nullable private String mText;
    357         @Nullable private Drawable mLegacyIcon;
    358         @Nullable private String mLegacyLabel;
    359         @Nullable private Intent mLegacyIntent;
    360         @Nullable private OnClickListener mLegacyOnClickListener;
    361         @Nullable private String mId;
    362 
    363         /**
    364          * Sets the classified text.
    365          */
    366         @NonNull
    367         public Builder setText(@Nullable String text) {
    368             mText = text;
    369             return this;
    370         }
    371 
    372         /**
    373          * Sets an entity type for the classification result and assigns a confidence score.
    374          * If a confidence score had already been set for the specified entity type, this will
    375          * override that score.
    376          *
    377          * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
    378          *      0 implies the entity does not exist for the classified text.
    379          *      Values greater than 1 are clamped to 1.
    380          */
    381         @NonNull
    382         public Builder setEntityType(
    383                 @NonNull @EntityType String type,
    384                 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
    385             mEntityConfidence.put(type, confidenceScore);
    386             return this;
    387         }
    388 
    389         /**
    390          * Adds an action that may be performed on the classified text. Actions should be added in
    391          * order of likelihood that the user will use them, with the most likely action being added
    392          * first.
    393          */
    394         @NonNull
    395         public Builder addAction(@NonNull RemoteAction action) {
    396             Preconditions.checkArgument(action != null);
    397             mActions.add(action);
    398             return this;
    399         }
    400 
    401         /**
    402          * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
    403          * on the classified text.
    404          *
    405          * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
    406          * returned icon represents the icon of the first {@link RemoteAction} (if one exists).
    407          *
    408          * @deprecated Use {@link #addAction(RemoteAction)} instead.
    409          */
    410         @Deprecated
    411         @NonNull
    412         public Builder setIcon(@Nullable Drawable icon) {
    413             mLegacyIcon = icon;
    414             return this;
    415         }
    416 
    417         /**
    418          * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
    419          * act on the classified text.
    420          *
    421          * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
    422          * returned label represents the label of the first {@link RemoteAction} (if one exists).
    423          *
    424          * @deprecated Use {@link #addAction(RemoteAction)} instead.
    425          */
    426         @Deprecated
    427         @NonNull
    428         public Builder setLabel(@Nullable String label) {
    429             mLegacyLabel = label;
    430             return this;
    431         }
    432 
    433         /**
    434          * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
    435          * text.
    436          *
    437          * <p><strong>NOTE: </strong>This field is not parcelled.
    438          *
    439          * @deprecated Use {@link #addAction(RemoteAction)} instead.
    440          */
    441         @Deprecated
    442         @NonNull
    443         public Builder setIntent(@Nullable Intent intent) {
    444             mLegacyIntent = intent;
    445             return this;
    446         }
    447 
    448         /**
    449          * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
    450          * the classified text.
    451          *
    452          * <p><strong>NOTE: </strong>This field is not parcelable. If read from a parcel, the
    453          * returned OnClickListener represents the first {@link RemoteAction} (if one exists).
    454          *
    455          * @deprecated Use {@link #addAction(RemoteAction)} instead.
    456          */
    457         @Deprecated
    458         @NonNull
    459         public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
    460             mLegacyOnClickListener = onClickListener;
    461             return this;
    462         }
    463 
    464         /**
    465          * Sets an id for the TextClassification object.
    466          */
    467         @NonNull
    468         public Builder setId(@Nullable String id) {
    469             mId = id;
    470             return this;
    471         }
    472 
    473         /**
    474          * Builds and returns a {@link TextClassification} object.
    475          */
    476         @NonNull
    477         public TextClassification build() {
    478             return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
    479                     mLegacyOnClickListener, mActions, mEntityConfidence, mId);
    480         }
    481     }
    482 
    483     /**
    484      * A request object for generating TextClassification.
    485      */
    486     public static final class Request implements Parcelable {
    487 
    488         private final CharSequence mText;
    489         private final int mStartIndex;
    490         private final int mEndIndex;
    491         @Nullable private final LocaleList mDefaultLocales;
    492         @Nullable private final ZonedDateTime mReferenceTime;
    493 
    494         private Request(
    495                 CharSequence text,
    496                 int startIndex,
    497                 int endIndex,
    498                 LocaleList defaultLocales,
    499                 ZonedDateTime referenceTime) {
    500             mText = text;
    501             mStartIndex = startIndex;
    502             mEndIndex = endIndex;
    503             mDefaultLocales = defaultLocales;
    504             mReferenceTime = referenceTime;
    505         }
    506 
    507         /**
    508          * Returns the text providing context for the text to classify (which is specified
    509          *      by the sub sequence starting at startIndex and ending at endIndex)
    510          */
    511         @NonNull
    512         public CharSequence getText() {
    513             return mText;
    514         }
    515 
    516         /**
    517          * Returns start index of the text to classify.
    518          */
    519         @IntRange(from = 0)
    520         public int getStartIndex() {
    521             return mStartIndex;
    522         }
    523 
    524         /**
    525          * Returns end index of the text to classify.
    526          */
    527         @IntRange(from = 0)
    528         public int getEndIndex() {
    529             return mEndIndex;
    530         }
    531 
    532         /**
    533          * @return ordered list of locale preferences that can be used to disambiguate
    534          *      the provided text.
    535          */
    536         @Nullable
    537         public LocaleList getDefaultLocales() {
    538             return mDefaultLocales;
    539         }
    540 
    541         /**
    542          * @return reference time based on which relative dates (e.g. "tomorrow") should be
    543          *      interpreted.
    544          */
    545         @Nullable
    546         public ZonedDateTime getReferenceTime() {
    547             return mReferenceTime;
    548         }
    549 
    550         /**
    551          * A builder for building TextClassification requests.
    552          */
    553         public static final class Builder {
    554 
    555             private final CharSequence mText;
    556             private final int mStartIndex;
    557             private final int mEndIndex;
    558 
    559             @Nullable private LocaleList mDefaultLocales;
    560             @Nullable private ZonedDateTime mReferenceTime;
    561 
    562             /**
    563              * @param text text providing context for the text to classify (which is specified
    564              *      by the sub sequence starting at startIndex and ending at endIndex)
    565              * @param startIndex start index of the text to classify
    566              * @param endIndex end index of the text to classify
    567              */
    568             public Builder(
    569                     @NonNull CharSequence text,
    570                     @IntRange(from = 0) int startIndex,
    571                     @IntRange(from = 0) int endIndex) {
    572                 Utils.checkArgument(text, startIndex, endIndex);
    573                 mText = text;
    574                 mStartIndex = startIndex;
    575                 mEndIndex = endIndex;
    576             }
    577 
    578             /**
    579              * @param defaultLocales ordered list of locale preferences that may be used to
    580              *      disambiguate the provided text. If no locale preferences exist, set this to null
    581              *      or an empty locale list.
    582              *
    583              * @return this builder
    584              */
    585             @NonNull
    586             public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
    587                 mDefaultLocales = defaultLocales;
    588                 return this;
    589             }
    590 
    591             /**
    592              * @param referenceTime reference time based on which relative dates (e.g. "tomorrow"
    593              *      should be interpreted. This should usually be the time when the text was
    594              *      originally composed. If no reference time is set, now is used.
    595              *
    596              * @return this builder
    597              */
    598             @NonNull
    599             public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
    600                 mReferenceTime = referenceTime;
    601                 return this;
    602             }
    603 
    604             /**
    605              * Builds and returns the request object.
    606              */
    607             @NonNull
    608             public Request build() {
    609                 return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime);
    610             }
    611         }
    612 
    613         @Override
    614         public int describeContents() {
    615             return 0;
    616         }
    617 
    618         @Override
    619         public void writeToParcel(Parcel dest, int flags) {
    620             dest.writeString(mText.toString());
    621             dest.writeInt(mStartIndex);
    622             dest.writeInt(mEndIndex);
    623             dest.writeInt(mDefaultLocales != null ? 1 : 0);
    624             if (mDefaultLocales != null) {
    625                 mDefaultLocales.writeToParcel(dest, flags);
    626             }
    627             dest.writeInt(mReferenceTime != null ? 1 : 0);
    628             if (mReferenceTime != null) {
    629                 dest.writeString(mReferenceTime.toString());
    630             }
    631         }
    632 
    633         public static final Parcelable.Creator<Request> CREATOR =
    634                 new Parcelable.Creator<Request>() {
    635                     @Override
    636                     public Request createFromParcel(Parcel in) {
    637                         return new Request(in);
    638                     }
    639 
    640                     @Override
    641                     public Request[] newArray(int size) {
    642                         return new Request[size];
    643                     }
    644                 };
    645 
    646         private Request(Parcel in) {
    647             mText = in.readString();
    648             mStartIndex = in.readInt();
    649             mEndIndex = in.readInt();
    650             mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
    651             mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse(in.readString());
    652         }
    653     }
    654 
    655     @Override
    656     public int describeContents() {
    657         return 0;
    658     }
    659 
    660     @Override
    661     public void writeToParcel(Parcel dest, int flags) {
    662         dest.writeString(mText);
    663         // NOTE: legacy fields are not parcelled.
    664         dest.writeTypedList(mActions);
    665         mEntityConfidence.writeToParcel(dest, flags);
    666         dest.writeString(mId);
    667     }
    668 
    669     public static final Parcelable.Creator<TextClassification> CREATOR =
    670             new Parcelable.Creator<TextClassification>() {
    671                 @Override
    672                 public TextClassification createFromParcel(Parcel in) {
    673                     return new TextClassification(in);
    674                 }
    675 
    676                 @Override
    677                 public TextClassification[] newArray(int size) {
    678                     return new TextClassification[size];
    679                 }
    680             };
    681 
    682     private TextClassification(Parcel in) {
    683         mText = in.readString();
    684         mActions = in.createTypedArrayList(RemoteAction.CREATOR);
    685         if (!mActions.isEmpty()) {
    686             final RemoteAction action = mActions.get(0);
    687             mLegacyIcon = maybeLoadDrawable(action.getIcon());
    688             mLegacyLabel = action.getTitle().toString();
    689             mLegacyOnClickListener = createIntentOnClickListener(mActions.get(0).getActionIntent());
    690         } else {
    691             mLegacyIcon = null;
    692             mLegacyLabel = null;
    693             mLegacyOnClickListener = null;
    694         }
    695         mLegacyIntent = null; // mLegacyIntent is not parcelled.
    696         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
    697         mId = in.readString();
    698     }
    699 
    700     // Best effort attempt to try to load a drawable from the provided icon.
    701     @Nullable
    702     private static Drawable maybeLoadDrawable(Icon icon) {
    703         if (icon == null) {
    704             return null;
    705         }
    706         switch (icon.getType()) {
    707             case Icon.TYPE_BITMAP:
    708                 return new BitmapDrawable(Resources.getSystem(), icon.getBitmap());
    709             case Icon.TYPE_ADAPTIVE_BITMAP:
    710                 return new AdaptiveIconDrawable(null,
    711                         new BitmapDrawable(Resources.getSystem(), icon.getBitmap()));
    712             case Icon.TYPE_DATA:
    713                 return new BitmapDrawable(
    714                         Resources.getSystem(),
    715                         BitmapFactory.decodeByteArray(
    716                                 icon.getDataBytes(), icon.getDataOffset(), icon.getDataLength()));
    717         }
    718         return null;
    719     }
    720 
    721     // TODO: Remove once apps can build against the latest sdk.
    722     /**
    723      * Optional input parameters for generating TextClassification.
    724      * @hide
    725      */
    726     public static final class Options {
    727 
    728         @Nullable private final TextClassificationSessionId mSessionId;
    729         @Nullable private final Request mRequest;
    730         @Nullable private LocaleList mDefaultLocales;
    731         @Nullable private ZonedDateTime mReferenceTime;
    732 
    733         public Options() {
    734             this(null, null);
    735         }
    736 
    737         private Options(
    738                 @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
    739             mSessionId = sessionId;
    740             mRequest = request;
    741         }
    742 
    743         /** Helper to create Options from a Request. */
    744         public static Options from(TextClassificationSessionId sessionId, Request request) {
    745             final Options options = new Options(sessionId, request);
    746             options.setDefaultLocales(request.getDefaultLocales());
    747             options.setReferenceTime(request.getReferenceTime());
    748             return options;
    749         }
    750 
    751         /** @param defaultLocales ordered list of locale preferences. */
    752         public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
    753             mDefaultLocales = defaultLocales;
    754             return this;
    755         }
    756 
    757         /** @param referenceTime refrence time used for interpreting relatives dates */
    758         public Options setReferenceTime(@Nullable ZonedDateTime referenceTime) {
    759             mReferenceTime = referenceTime;
    760             return this;
    761         }
    762 
    763         @Nullable
    764         public LocaleList getDefaultLocales() {
    765             return mDefaultLocales;
    766         }
    767 
    768         @Nullable
    769         public ZonedDateTime getReferenceTime() {
    770             return mReferenceTime;
    771         }
    772 
    773         @Nullable
    774         public Request getRequest() {
    775             return mRequest;
    776         }
    777 
    778         @Nullable
    779         public TextClassificationSessionId getSessionId() {
    780             return mSessionId;
    781         }
    782     }
    783 }
    784