Home | History | Annotate | Download | only in textclassifier
      1 /*
      2  * Copyright 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.NonNull;
     22 import android.annotation.Nullable;
     23 import android.content.Context;
     24 import android.os.LocaleList;
     25 import android.os.Parcel;
     26 import android.os.Parcelable;
     27 import android.text.Spannable;
     28 import android.text.method.MovementMethod;
     29 import android.text.style.ClickableSpan;
     30 import android.text.style.URLSpan;
     31 import android.text.util.Linkify;
     32 import android.text.util.Linkify.LinkifyMask;
     33 import android.view.View;
     34 import android.view.textclassifier.TextClassifier.EntityType;
     35 import android.widget.TextView;
     36 
     37 import com.android.internal.annotations.VisibleForTesting;
     38 import com.android.internal.annotations.VisibleForTesting.Visibility;
     39 import com.android.internal.util.Preconditions;
     40 
     41 import java.lang.annotation.Retention;
     42 import java.lang.annotation.RetentionPolicy;
     43 import java.util.ArrayList;
     44 import java.util.Collection;
     45 import java.util.Collections;
     46 import java.util.List;
     47 import java.util.Locale;
     48 import java.util.Map;
     49 import java.util.function.Function;
     50 
     51 /**
     52  * A collection of links, representing subsequences of text and the entity types (phone number,
     53  * address, url, etc) they may be.
     54  */
     55 public final class TextLinks implements Parcelable {
     56 
     57     /**
     58      * Return status of an attempt to apply TextLinks to text.
     59      * @hide
     60      */
     61     @Retention(RetentionPolicy.SOURCE)
     62     @IntDef({STATUS_LINKS_APPLIED, STATUS_NO_LINKS_FOUND, STATUS_NO_LINKS_APPLIED,
     63             STATUS_DIFFERENT_TEXT})
     64     public @interface Status {}
     65 
     66     /** Links were successfully applied to the text. */
     67     public static final int STATUS_LINKS_APPLIED = 0;
     68 
     69     /** No links exist to apply to text. Links count is zero. */
     70     public static final int STATUS_NO_LINKS_FOUND = 1;
     71 
     72     /** No links applied to text. The links were filtered out. */
     73     public static final int STATUS_NO_LINKS_APPLIED = 2;
     74 
     75     /** The specified text does not match the text used to generate the links. */
     76     public static final int STATUS_DIFFERENT_TEXT = 3;
     77 
     78     /** @hide */
     79     @Retention(RetentionPolicy.SOURCE)
     80     @IntDef({APPLY_STRATEGY_IGNORE, APPLY_STRATEGY_REPLACE})
     81     public @interface ApplyStrategy {}
     82 
     83     /**
     84      * Do not replace {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to
     85      * be applied to. Do not apply the TextLinkSpan.
     86      */
     87     public static final int APPLY_STRATEGY_IGNORE = 0;
     88 
     89     /**
     90      * Replace any {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to be
     91      * applied to.
     92      */
     93     public static final int APPLY_STRATEGY_REPLACE = 1;
     94 
     95     private final String mFullText;
     96     private final List<TextLink> mLinks;
     97 
     98     private TextLinks(String fullText, ArrayList<TextLink> links) {
     99         mFullText = fullText;
    100         mLinks = Collections.unmodifiableList(links);
    101     }
    102 
    103     /**
    104      * Returns the text that was used to generate these links.
    105      * @hide
    106      */
    107     @NonNull
    108     public String getText() {
    109         return mFullText;
    110     }
    111 
    112     /**
    113      * Returns an unmodifiable Collection of the links.
    114      */
    115     @NonNull
    116     public Collection<TextLink> getLinks() {
    117         return mLinks;
    118     }
    119 
    120     /**
    121      * Annotates the given text with the generated links. It will fail if the provided text doesn't
    122      * match the original text used to create the TextLinks.
    123      *
    124      * <p><strong>NOTE: </strong>It may be necessary to set a LinkMovementMethod on the TextView
    125      * widget to properly handle links. See {@link TextView#setMovementMethod(MovementMethod)}
    126      *
    127      * @param text the text to apply the links to. Must match the original text
    128      * @param applyStrategy the apply strategy used to determine how to apply links to text.
    129      *      e.g {@link TextLinks#APPLY_STRATEGY_IGNORE}
    130      * @param spanFactory a custom span factory for converting TextLinks to TextLinkSpans.
    131      *      Set to {@code null} to use the default span factory.
    132      *
    133      * @return a status code indicating whether or not the links were successfully applied
    134      *      e.g. {@link #STATUS_LINKS_APPLIED}
    135      */
    136     @Status
    137     public int apply(
    138             @NonNull Spannable text,
    139             @ApplyStrategy int applyStrategy,
    140             @Nullable Function<TextLink, TextLinkSpan> spanFactory) {
    141         Preconditions.checkNotNull(text);
    142         return new TextLinksParams.Builder()
    143                 .setApplyStrategy(applyStrategy)
    144                 .setSpanFactory(spanFactory)
    145                 .build()
    146                 .apply(text, this);
    147     }
    148 
    149     @Override
    150     public String toString() {
    151         return String.format(Locale.US, "TextLinks{fullText=%s, links=%s}", mFullText, mLinks);
    152     }
    153 
    154     @Override
    155     public int describeContents() {
    156         return 0;
    157     }
    158 
    159     @Override
    160     public void writeToParcel(Parcel dest, int flags) {
    161         dest.writeString(mFullText);
    162         dest.writeTypedList(mLinks);
    163     }
    164 
    165     public static final Parcelable.Creator<TextLinks> CREATOR =
    166             new Parcelable.Creator<TextLinks>() {
    167                 @Override
    168                 public TextLinks createFromParcel(Parcel in) {
    169                     return new TextLinks(in);
    170                 }
    171 
    172                 @Override
    173                 public TextLinks[] newArray(int size) {
    174                     return new TextLinks[size];
    175                 }
    176             };
    177 
    178     private TextLinks(Parcel in) {
    179         mFullText = in.readString();
    180         mLinks = in.createTypedArrayList(TextLink.CREATOR);
    181     }
    182 
    183     /**
    184      * A link, identifying a substring of text and possible entity types for it.
    185      */
    186     public static final class TextLink implements Parcelable {
    187         private final EntityConfidence mEntityScores;
    188         private final int mStart;
    189         private final int mEnd;
    190         @Nullable final URLSpan mUrlSpan;
    191 
    192         /**
    193          * Create a new TextLink.
    194          *
    195          * @param start The start index of the identified subsequence
    196          * @param end The end index of the identified subsequence
    197          * @param entityScores A mapping of entity type to confidence score
    198          * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled
    199          *
    200          * @throws IllegalArgumentException if entityScores is null or empty
    201          */
    202         TextLink(int start, int end, Map<String, Float> entityScores,
    203                 @Nullable URLSpan urlSpan) {
    204             Preconditions.checkNotNull(entityScores);
    205             Preconditions.checkArgument(!entityScores.isEmpty());
    206             Preconditions.checkArgument(start <= end);
    207             mStart = start;
    208             mEnd = end;
    209             mEntityScores = new EntityConfidence(entityScores);
    210             mUrlSpan = urlSpan;
    211         }
    212 
    213         /**
    214          * Returns the start index of this link in the original text.
    215          *
    216          * @return the start index
    217          */
    218         public int getStart() {
    219             return mStart;
    220         }
    221 
    222         /**
    223          * Returns the end index of this link in the original text.
    224          *
    225          * @return the end index
    226          */
    227         public int getEnd() {
    228             return mEnd;
    229         }
    230 
    231         /**
    232          * Returns the number of entity types that have confidence scores.
    233          *
    234          * @return the entity count
    235          */
    236         public int getEntityCount() {
    237             return mEntityScores.getEntities().size();
    238         }
    239 
    240         /**
    241          * Returns the entity type at a given index. Entity types are sorted by confidence.
    242          *
    243          * @return the entity type at the provided index
    244          */
    245         @NonNull public @EntityType String getEntity(int index) {
    246             return mEntityScores.getEntities().get(index);
    247         }
    248 
    249         /**
    250          * Returns the confidence score for a particular entity type.
    251          *
    252          * @param entityType the entity type
    253          */
    254         public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
    255                 @EntityType String entityType) {
    256             return mEntityScores.getConfidenceScore(entityType);
    257         }
    258 
    259         @Override
    260         public String toString() {
    261             return String.format(Locale.US,
    262                     "TextLink{start=%s, end=%s, entityScores=%s, urlSpan=%s}",
    263                     mStart, mEnd, mEntityScores, mUrlSpan);
    264         }
    265 
    266         @Override
    267         public int describeContents() {
    268             return 0;
    269         }
    270 
    271         @Override
    272         public void writeToParcel(Parcel dest, int flags) {
    273             mEntityScores.writeToParcel(dest, flags);
    274             dest.writeInt(mStart);
    275             dest.writeInt(mEnd);
    276         }
    277 
    278         public static final Parcelable.Creator<TextLink> CREATOR =
    279                 new Parcelable.Creator<TextLink>() {
    280                     @Override
    281                     public TextLink createFromParcel(Parcel in) {
    282                         return new TextLink(in);
    283                     }
    284 
    285                     @Override
    286                     public TextLink[] newArray(int size) {
    287                         return new TextLink[size];
    288                     }
    289                 };
    290 
    291         private TextLink(Parcel in) {
    292             mEntityScores = EntityConfidence.CREATOR.createFromParcel(in);
    293             mStart = in.readInt();
    294             mEnd = in.readInt();
    295             mUrlSpan = null;
    296         }
    297     }
    298 
    299     /**
    300      * A request object for generating TextLinks.
    301      */
    302     public static final class Request implements Parcelable {
    303 
    304         private final CharSequence mText;
    305         @Nullable private final LocaleList mDefaultLocales;
    306         @Nullable private final TextClassifier.EntityConfig mEntityConfig;
    307         private final boolean mLegacyFallback;
    308         private String mCallingPackageName;
    309 
    310         private Request(
    311                 CharSequence text,
    312                 LocaleList defaultLocales,
    313                 TextClassifier.EntityConfig entityConfig,
    314                 boolean legacyFallback,
    315                 String callingPackageName) {
    316             mText = text;
    317             mDefaultLocales = defaultLocales;
    318             mEntityConfig = entityConfig;
    319             mLegacyFallback = legacyFallback;
    320             mCallingPackageName = callingPackageName;
    321         }
    322 
    323         /**
    324          * Returns the text to generate links for.
    325          */
    326         @NonNull
    327         public CharSequence getText() {
    328             return mText;
    329         }
    330 
    331         /**
    332          * @return ordered list of locale preferences that can be used to disambiguate
    333          *      the provided text
    334          */
    335         @Nullable
    336         public LocaleList getDefaultLocales() {
    337             return mDefaultLocales;
    338         }
    339 
    340         /**
    341          * @return The config representing the set of entities to look for
    342          * @see Builder#setEntityConfig(TextClassifier.EntityConfig)
    343          */
    344         @Nullable
    345         public TextClassifier.EntityConfig getEntityConfig() {
    346             return mEntityConfig;
    347         }
    348 
    349         /**
    350          * Returns whether the TextClassifier can fallback to legacy links if smart linkify is
    351          * disabled.
    352          * <strong>Note: </strong>This is not parcelled.
    353          * @hide
    354          */
    355         public boolean isLegacyFallback() {
    356             return mLegacyFallback;
    357         }
    358 
    359         /**
    360          * Sets the name of the package that requested the links to get generated.
    361          */
    362         void setCallingPackageName(@Nullable String callingPackageName) {
    363             mCallingPackageName = callingPackageName;
    364         }
    365 
    366         /**
    367          * A builder for building TextLinks requests.
    368          */
    369         public static final class Builder {
    370 
    371             private final CharSequence mText;
    372 
    373             @Nullable private LocaleList mDefaultLocales;
    374             @Nullable private TextClassifier.EntityConfig mEntityConfig;
    375             private boolean mLegacyFallback = true; // Use legacy fall back by default.
    376             private String mCallingPackageName;
    377 
    378             public Builder(@NonNull CharSequence text) {
    379                 mText = Preconditions.checkNotNull(text);
    380             }
    381 
    382             /**
    383              * @param defaultLocales ordered list of locale preferences that may be used to
    384              *                       disambiguate the provided text. If no locale preferences exist,
    385              *                       set this to null or an empty locale list.
    386              * @return this builder
    387              */
    388             @NonNull
    389             public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
    390                 mDefaultLocales = defaultLocales;
    391                 return this;
    392             }
    393 
    394             /**
    395              * Sets the entity configuration to use. This determines what types of entities the
    396              * TextClassifier will look for.
    397              * Set to {@code null} for the default entity config and teh TextClassifier will
    398              * automatically determine what links to generate.
    399              *
    400              * @return this builder
    401              */
    402             @NonNull
    403             public Builder setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
    404                 mEntityConfig = entityConfig;
    405                 return this;
    406             }
    407 
    408             /**
    409              * Sets whether the TextClassifier can fallback to legacy links if smart linkify is
    410              * disabled.
    411              *
    412              * <p><strong>Note: </strong>This is not parcelled.
    413              *
    414              * @return this builder
    415              * @hide
    416              */
    417             @NonNull
    418             public Builder setLegacyFallback(boolean legacyFallback) {
    419                 mLegacyFallback = legacyFallback;
    420                 return this;
    421             }
    422 
    423             /**
    424              * Sets the name of the package that requested the links to get generated.
    425              *
    426              * @return this builder
    427              * @hide
    428              */
    429             @NonNull
    430             public Builder setCallingPackageName(@Nullable String callingPackageName) {
    431                 mCallingPackageName = callingPackageName;
    432                 return this;
    433             }
    434 
    435             /**
    436              * Builds and returns the request object.
    437              */
    438             @NonNull
    439             public Request build() {
    440                 return new Request(
    441                         mText, mDefaultLocales, mEntityConfig,
    442                         mLegacyFallback, mCallingPackageName);
    443             }
    444 
    445         }
    446 
    447         /**
    448          * @return the name of the package that requested the links to get generated.
    449          * TODO: make available as system API
    450          * @hide
    451          */
    452         @Nullable
    453         public String getCallingPackageName() {
    454             return mCallingPackageName;
    455         }
    456 
    457         @Override
    458         public int describeContents() {
    459             return 0;
    460         }
    461 
    462         @Override
    463         public void writeToParcel(Parcel dest, int flags) {
    464             dest.writeString(mText.toString());
    465             dest.writeInt(mDefaultLocales != null ? 1 : 0);
    466             if (mDefaultLocales != null) {
    467                 mDefaultLocales.writeToParcel(dest, flags);
    468             }
    469             dest.writeInt(mEntityConfig != null ? 1 : 0);
    470             if (mEntityConfig != null) {
    471                 mEntityConfig.writeToParcel(dest, flags);
    472             }
    473             dest.writeString(mCallingPackageName);
    474         }
    475 
    476         public static final Parcelable.Creator<Request> CREATOR =
    477                 new Parcelable.Creator<Request>() {
    478                     @Override
    479                     public Request createFromParcel(Parcel in) {
    480                         return new Request(in);
    481                     }
    482 
    483                     @Override
    484                     public Request[] newArray(int size) {
    485                         return new Request[size];
    486                     }
    487                 };
    488 
    489         private Request(Parcel in) {
    490             mText = in.readString();
    491             mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
    492             mEntityConfig = in.readInt() == 0
    493                     ? null : TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
    494             mLegacyFallback = true;
    495             mCallingPackageName = in.readString();
    496         }
    497     }
    498 
    499     /**
    500      * A ClickableSpan for a TextLink.
    501      *
    502      * <p>Applies only to TextViews.
    503      */
    504     public static class TextLinkSpan extends ClickableSpan {
    505 
    506         /**
    507          * How the clickspan is triggered.
    508          * @hide
    509          */
    510         @Retention(RetentionPolicy.SOURCE)
    511         @IntDef({INVOCATION_METHOD_UNSPECIFIED, INVOCATION_METHOD_TOUCH,
    512                 INVOCATION_METHOD_KEYBOARD})
    513         public @interface InvocationMethod {}
    514 
    515         /** @hide */
    516         public static final int INVOCATION_METHOD_UNSPECIFIED = -1;
    517         /** @hide */
    518         public static final int INVOCATION_METHOD_TOUCH = 0;
    519         /** @hide */
    520         public static final int INVOCATION_METHOD_KEYBOARD = 1;
    521 
    522         private final TextLink mTextLink;
    523 
    524         public TextLinkSpan(@NonNull TextLink textLink) {
    525             mTextLink = textLink;
    526         }
    527 
    528         @Override
    529         public void onClick(View widget) {
    530             onClick(widget, INVOCATION_METHOD_UNSPECIFIED);
    531         }
    532 
    533         /** @hide */
    534         public final void onClick(View widget, @InvocationMethod int invocationMethod) {
    535             if (widget instanceof TextView) {
    536                 final TextView textView = (TextView) widget;
    537                 final Context context = textView.getContext();
    538                 if (TextClassificationManager.getSettings(context).isSmartLinkifyEnabled()) {
    539                     switch (invocationMethod) {
    540                         case INVOCATION_METHOD_TOUCH:
    541                             textView.requestActionMode(this);
    542                             break;
    543                         case INVOCATION_METHOD_KEYBOARD:// fall though
    544                         case INVOCATION_METHOD_UNSPECIFIED:  // fall through
    545                         default:
    546                             textView.handleClick(this);
    547                             break;
    548                     }
    549                 } else {
    550                     if (mTextLink.mUrlSpan != null) {
    551                         mTextLink.mUrlSpan.onClick(textView);
    552                     } else {
    553                         textView.handleClick(this);
    554                     }
    555                 }
    556             }
    557         }
    558 
    559         public final TextLink getTextLink() {
    560             return mTextLink;
    561         }
    562 
    563         /** @hide */
    564         @VisibleForTesting(visibility = Visibility.PRIVATE)
    565         @Nullable
    566         public final String getUrl() {
    567             if (mTextLink.mUrlSpan != null) {
    568                 return mTextLink.mUrlSpan.getURL();
    569             }
    570             return null;
    571         }
    572     }
    573 
    574     /**
    575      * A builder to construct a TextLinks instance.
    576      */
    577     public static final class Builder {
    578         private final String mFullText;
    579         private final ArrayList<TextLink> mLinks;
    580 
    581         /**
    582          * Create a new TextLinks.Builder.
    583          *
    584          * @param fullText The full text to annotate with links
    585          */
    586         public Builder(@NonNull String fullText) {
    587             mFullText = Preconditions.checkNotNull(fullText);
    588             mLinks = new ArrayList<>();
    589         }
    590 
    591         /**
    592          * Adds a TextLink.
    593          *
    594          * @param start The start index of the identified subsequence
    595          * @param end The end index of the identified subsequence
    596          * @param entityScores A mapping of entity type to confidence score
    597          *
    598          * @throws IllegalArgumentException if entityScores is null or empty.
    599          */
    600         @NonNull
    601         public Builder addLink(int start, int end, Map<String, Float> entityScores) {
    602             mLinks.add(new TextLink(start, end, entityScores, null));
    603             return this;
    604         }
    605 
    606         /**
    607          * @see #addLink(int, int, Map)
    608          * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled.
    609          */
    610         @NonNull
    611         Builder addLink(int start, int end, Map<String, Float> entityScores,
    612                 @Nullable URLSpan urlSpan) {
    613             mLinks.add(new TextLink(start, end, entityScores, urlSpan));
    614             return this;
    615         }
    616 
    617         /**
    618          * Removes all {@link TextLink}s.
    619          */
    620         @NonNull
    621         public Builder clearTextLinks() {
    622             mLinks.clear();
    623             return this;
    624         }
    625 
    626         /**
    627          * Constructs a TextLinks instance.
    628          *
    629          * @return the constructed TextLinks
    630          */
    631         @NonNull
    632         public TextLinks build() {
    633             return new TextLinks(mFullText, mLinks);
    634         }
    635     }
    636 
    637     // TODO: Remove once apps can build against the latest sdk.
    638     /**
    639      * Optional input parameters for generating TextLinks.
    640      * @hide
    641      */
    642     public static final class Options {
    643 
    644         @Nullable private final TextClassificationSessionId mSessionId;
    645         @Nullable private final Request mRequest;
    646         @Nullable private LocaleList mDefaultLocales;
    647         @Nullable private TextClassifier.EntityConfig mEntityConfig;
    648         private boolean mLegacyFallback;
    649 
    650         private @ApplyStrategy int mApplyStrategy;
    651         private Function<TextLink, TextLinkSpan> mSpanFactory;
    652 
    653         private String mCallingPackageName;
    654 
    655         public Options() {
    656             this(null, null);
    657         }
    658 
    659         private Options(
    660                 @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
    661             mSessionId = sessionId;
    662             mRequest = request;
    663         }
    664 
    665         /** Helper to create Options from a Request. */
    666         public static Options from(TextClassificationSessionId sessionId, Request request) {
    667             final Options options = new Options(sessionId, request);
    668             options.setDefaultLocales(request.getDefaultLocales());
    669             options.setEntityConfig(request.getEntityConfig());
    670             return options;
    671         }
    672 
    673         /** Returns a new options object based on the specified link mask. */
    674         public static Options fromLinkMask(@LinkifyMask int mask) {
    675             final List<String> entitiesToFind = new ArrayList<>();
    676 
    677             if ((mask & Linkify.WEB_URLS) != 0) {
    678                 entitiesToFind.add(TextClassifier.TYPE_URL);
    679             }
    680             if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
    681                 entitiesToFind.add(TextClassifier.TYPE_EMAIL);
    682             }
    683             if ((mask & Linkify.PHONE_NUMBERS) != 0) {
    684                 entitiesToFind.add(TextClassifier.TYPE_PHONE);
    685             }
    686             if ((mask & Linkify.MAP_ADDRESSES) != 0) {
    687                 entitiesToFind.add(TextClassifier.TYPE_ADDRESS);
    688             }
    689 
    690             return new Options().setEntityConfig(
    691                     TextClassifier.EntityConfig.createWithEntityList(entitiesToFind));
    692         }
    693 
    694         /** @param defaultLocales ordered list of locale preferences. */
    695         public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
    696             mDefaultLocales = defaultLocales;
    697             return this;
    698         }
    699 
    700         /** @param entityConfig definition of which entity types to look for. */
    701         public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
    702             mEntityConfig = entityConfig;
    703             return this;
    704         }
    705 
    706         /** @param applyStrategy strategy to use when resolving conflicts. */
    707         public Options setApplyStrategy(@ApplyStrategy int applyStrategy) {
    708             checkValidApplyStrategy(applyStrategy);
    709             mApplyStrategy = applyStrategy;
    710             return this;
    711         }
    712 
    713         /** @param spanFactory factory for converting TextLink to TextLinkSpan. */
    714         public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
    715             mSpanFactory = spanFactory;
    716             return this;
    717         }
    718 
    719         @Nullable
    720         public LocaleList getDefaultLocales() {
    721             return mDefaultLocales;
    722         }
    723 
    724         @Nullable
    725         public TextClassifier.EntityConfig getEntityConfig() {
    726             return mEntityConfig;
    727         }
    728 
    729         @ApplyStrategy
    730         public int getApplyStrategy() {
    731             return mApplyStrategy;
    732         }
    733 
    734         @Nullable
    735         public Function<TextLink, TextLinkSpan> getSpanFactory() {
    736             return mSpanFactory;
    737         }
    738 
    739         @Nullable
    740         public Request getRequest() {
    741             return mRequest;
    742         }
    743 
    744         @Nullable
    745         public TextClassificationSessionId getSessionId() {
    746             return mSessionId;
    747         }
    748 
    749         private static void checkValidApplyStrategy(int applyStrategy) {
    750             if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) {
    751                 throw new IllegalArgumentException(
    752                         "Invalid apply strategy. See TextLinks.ApplyStrategy for options.");
    753             }
    754         }
    755     }
    756 }
    757