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 
TextLinks(String fullText, ArrayList<TextLink> links)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
getText()108     public String getText() {
109         return mFullText;
110     }
111 
112     /**
113      * Returns an unmodifiable Collection of the links.
114      */
115     @NonNull
getLinks()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
apply( @onNull Spannable text, @ApplyStrategy int applyStrategy, @Nullable Function<TextLink, TextLinkSpan> spanFactory)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
toString()150     public String toString() {
151         return String.format(Locale.US, "TextLinks{fullText=%s, links=%s}", mFullText, mLinks);
152     }
153 
154     @Override
describeContents()155     public int describeContents() {
156         return 0;
157     }
158 
159     @Override
writeToParcel(Parcel dest, int flags)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 
TextLinks(Parcel in)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          */
TextLink(int start, int end, Map<String, Float> entityScores, @Nullable URLSpan urlSpan)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          */
getStart()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          */
getEnd()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          */
getEntityCount()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          */
getEntity(int index)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          */
getConfidenceScore( @ntityType String entityType)254         public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
255                 @EntityType String entityType) {
256             return mEntityScores.getConfidenceScore(entityType);
257         }
258 
259         @Override
toString()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
describeContents()267         public int describeContents() {
268             return 0;
269         }
270 
271         @Override
writeToParcel(Parcel dest, int flags)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 
TextLink(Parcel in)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 
Request( CharSequence text, LocaleList defaultLocales, TextClassifier.EntityConfig entityConfig, boolean legacyFallback, String callingPackageName)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
getText()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
getDefaultLocales()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
getEntityConfig()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          */
isLegacyFallback()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          */
setCallingPackageName(@ullable String callingPackageName)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 
Builder(@onNull CharSequence text)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
setDefaultLocales(@ullable LocaleList defaultLocales)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
setEntityConfig(@ullable TextClassifier.EntityConfig entityConfig)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
setLegacyFallback(boolean legacyFallback)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
setCallingPackageName(@ullable String callingPackageName)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
build()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
getCallingPackageName()453         public String getCallingPackageName() {
454             return mCallingPackageName;
455         }
456 
457         @Override
describeContents()458         public int describeContents() {
459             return 0;
460         }
461 
462         @Override
writeToParcel(Parcel dest, int flags)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 
Request(Parcel in)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 
TextLinkSpan(@onNull TextLink textLink)524         public TextLinkSpan(@NonNull TextLink textLink) {
525             mTextLink = textLink;
526         }
527 
528         @Override
onClick(View widget)529         public void onClick(View widget) {
530             onClick(widget, INVOCATION_METHOD_UNSPECIFIED);
531         }
532 
533         /** @hide */
onClick(View widget, @InvocationMethod int invocationMethod)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 
getTextLink()559         public final TextLink getTextLink() {
560             return mTextLink;
561         }
562 
563         /** @hide */
564         @VisibleForTesting(visibility = Visibility.PRIVATE)
565         @Nullable
getUrl()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          */
Builder(@onNull String fullText)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
addLink(int start, int end, Map<String, Float> entityScores)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
addLink(int start, int end, Map<String, Float> entityScores, @Nullable URLSpan urlSpan)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
clearTextLinks()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
build()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 
Options()655         public Options() {
656             this(null, null);
657         }
658 
Options( @ullable TextClassificationSessionId sessionId, @Nullable Request request)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. */
from(TextClassificationSessionId sessionId, Request 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. */
fromLinkMask(@inkifyMask int 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. */
setDefaultLocales(@ullable LocaleList defaultLocales)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. */
setEntityConfig(@ullable TextClassifier.EntityConfig entityConfig)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. */
setApplyStrategy(@pplyStrategy int applyStrategy)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. */
setSpanFactory(@ullable Function<TextLink, TextLinkSpan> spanFactory)714         public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
715             mSpanFactory = spanFactory;
716             return this;
717         }
718 
719         @Nullable
getDefaultLocales()720         public LocaleList getDefaultLocales() {
721             return mDefaultLocales;
722         }
723 
724         @Nullable
getEntityConfig()725         public TextClassifier.EntityConfig getEntityConfig() {
726             return mEntityConfig;
727         }
728 
729         @ApplyStrategy
getApplyStrategy()730         public int getApplyStrategy() {
731             return mApplyStrategy;
732         }
733 
734         @Nullable
getSpanFactory()735         public Function<TextLink, TextLinkSpan> getSpanFactory() {
736             return mSpanFactory;
737         }
738 
739         @Nullable
getRequest()740         public Request getRequest() {
741             return mRequest;
742         }
743 
744         @Nullable
getSessionId()745         public TextClassificationSessionId getSessionId() {
746             return mSessionId;
747         }
748 
checkValidApplyStrategy(int applyStrategy)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