blob: cde27a08fc798f0f41a9bec495cddb22b18b4456 [file] [log] [blame]
Richard Ledley68d94522017-10-05 10:52:19 +01001/*
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
17package android.view.textclassifier;
18
19import android.annotation.FloatRange;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000020import android.annotation.IntDef;
Richard Ledley68d94522017-10-05 10:52:19 +010021import android.annotation.NonNull;
22import android.annotation.Nullable;
Abodunrinwa Toki65638332018-03-16 21:08:50 +000023import android.content.Context;
Tony Mak916bb9e2018-11-08 20:45:20 +000024import android.os.Bundle;
Richard Ledley68d94522017-10-05 10:52:19 +010025import android.os.LocaleList;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010026import android.os.Parcel;
27import android.os.Parcelable;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000028import android.text.Spannable;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010029import android.text.method.MovementMethod;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000030import android.text.style.ClickableSpan;
Abodunrinwa Toki65638332018-03-16 21:08:50 +000031import android.text.style.URLSpan;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000032import android.view.View;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000033import android.view.textclassifier.TextClassifier.EntityConfig;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000034import android.view.textclassifier.TextClassifier.EntityType;
Jan Althausdd68de52018-01-31 17:54:48 +010035import android.widget.TextView;
Richard Ledley68d94522017-10-05 10:52:19 +010036
Abodunrinwa Toki65638332018-03-16 21:08:50 +000037import com.android.internal.annotations.VisibleForTesting;
38import com.android.internal.annotations.VisibleForTesting.Visibility;
Richard Ledley68d94522017-10-05 10:52:19 +010039import com.android.internal.util.Preconditions;
40
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000041import java.lang.annotation.Retention;
42import java.lang.annotation.RetentionPolicy;
Richard Ledley68d94522017-10-05 10:52:19 +010043import java.util.ArrayList;
44import java.util.Collection;
45import java.util.Collections;
46import java.util.List;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010047import java.util.Locale;
Richard Ledley68d94522017-10-05 10:52:19 +010048import java.util.Map;
49import 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 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +010055public final class TextLinks implements Parcelable {
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000056
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,
Abodunrinwa Tokiadc19402018-11-22 17:10:25 +000063 STATUS_DIFFERENT_TEXT, STATUS_UNSUPPORTED_CHARACTER})
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000064 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
Abodunrinwa Tokiadc19402018-11-22 17:10:25 +000078 /** The specified text contains unsupported characters. */
79 public static final int STATUS_UNSUPPORTED_CHARACTER = 4;
80
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000081 /** @hide */
82 @Retention(RetentionPolicy.SOURCE)
83 @IntDef({APPLY_STRATEGY_IGNORE, APPLY_STRATEGY_REPLACE})
84 public @interface ApplyStrategy {}
85
86 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010087 * Do not replace {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to
88 * be applied to. Do not apply the TextLinkSpan.
89 */
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000090 public static final int APPLY_STRATEGY_IGNORE = 0;
91
92 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010093 * Replace any {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to be
94 * applied to.
95 */
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +000096 public static final int APPLY_STRATEGY_REPLACE = 1;
97
Richard Ledley68d94522017-10-05 10:52:19 +010098 private final String mFullText;
99 private final List<TextLink> mLinks;
Tony Mak916bb9e2018-11-08 20:45:20 +0000100 private final Bundle mExtras;
Richard Ledley68d94522017-10-05 10:52:19 +0100101
Tony Mak916bb9e2018-11-08 20:45:20 +0000102 private TextLinks(String fullText, ArrayList<TextLink> links, Bundle extras) {
Richard Ledley68d94522017-10-05 10:52:19 +0100103 mFullText = fullText;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000104 mLinks = Collections.unmodifiableList(links);
Tony Mak916bb9e2018-11-08 20:45:20 +0000105 mExtras = extras;
Richard Ledley68d94522017-10-05 10:52:19 +0100106 }
107
108 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100109 * Returns the text that was used to generate these links.
110 * @hide
111 */
112 @NonNull
113 public String getText() {
114 return mFullText;
115 }
116
117 /**
Richard Ledley68d94522017-10-05 10:52:19 +0100118 * Returns an unmodifiable Collection of the links.
119 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100120 @NonNull
Richard Ledley68d94522017-10-05 10:52:19 +0100121 public Collection<TextLink> getLinks() {
122 return mLinks;
123 }
124
125 /**
Tony Mak916bb9e2018-11-08 20:45:20 +0000126 * Returns the extended data.
127 *
128 * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
129 * prefer to hold a reference to the returned bundle rather than frequently calling this
130 * method.
131 */
132 @NonNull
133 public Bundle getExtras() {
134 return mExtras.deepCopy();
135 }
136
137 /**
Richard Ledley68d94522017-10-05 10:52:19 +0100138 * Annotates the given text with the generated links. It will fail if the provided text doesn't
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100139 * match the original text used to create the TextLinks.
140 *
141 * <p><strong>NOTE: </strong>It may be necessary to set a LinkMovementMethod on the TextView
142 * widget to properly handle links. See {@link TextView#setMovementMethod(MovementMethod)}
Richard Ledley68d94522017-10-05 10:52:19 +0100143 *
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000144 * @param text the text to apply the links to. Must match the original text
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100145 * @param applyStrategy the apply strategy used to determine how to apply links to text.
146 * e.g {@link TextLinks#APPLY_STRATEGY_IGNORE}
147 * @param spanFactory a custom span factory for converting TextLinks to TextLinkSpans.
148 * Set to {@code null} to use the default span factory.
Richard Ledley68d94522017-10-05 10:52:19 +0100149 *
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000150 * @return a status code indicating whether or not the links were successfully applied
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100151 * e.g. {@link #STATUS_LINKS_APPLIED}
Richard Ledley68d94522017-10-05 10:52:19 +0100152 */
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000153 @Status
154 public int apply(
155 @NonNull Spannable text,
156 @ApplyStrategy int applyStrategy,
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100157 @Nullable Function<TextLink, TextLinkSpan> spanFactory) {
Richard Ledley68d94522017-10-05 10:52:19 +0100158 Preconditions.checkNotNull(text);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100159 return new TextLinksParams.Builder()
160 .setApplyStrategy(applyStrategy)
161 .setSpanFactory(spanFactory)
162 .build()
163 .apply(text, this);
164 }
Richard Ledley68d94522017-10-05 10:52:19 +0100165
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100166 @Override
167 public String toString() {
168 return String.format(Locale.US, "TextLinks{fullText=%s, links=%s}", mFullText, mLinks);
Richard Ledley68d94522017-10-05 10:52:19 +0100169 }
170
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100171 @Override
172 public int describeContents() {
173 return 0;
174 }
175
176 @Override
177 public void writeToParcel(Parcel dest, int flags) {
178 dest.writeString(mFullText);
179 dest.writeTypedList(mLinks);
Tony Mak916bb9e2018-11-08 20:45:20 +0000180 dest.writeBundle(mExtras);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100181 }
182
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700183 public static final @android.annotation.NonNull Parcelable.Creator<TextLinks> CREATOR =
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100184 new Parcelable.Creator<TextLinks>() {
185 @Override
186 public TextLinks createFromParcel(Parcel in) {
187 return new TextLinks(in);
188 }
189
190 @Override
191 public TextLinks[] newArray(int size) {
192 return new TextLinks[size];
193 }
194 };
195
196 private TextLinks(Parcel in) {
197 mFullText = in.readString();
198 mLinks = in.createTypedArrayList(TextLink.CREATOR);
Tony Mak916bb9e2018-11-08 20:45:20 +0000199 mExtras = in.readBundle();
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100200 }
201
Richard Ledley68d94522017-10-05 10:52:19 +0100202 /**
203 * A link, identifying a substring of text and possible entity types for it.
204 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100205 public static final class TextLink implements Parcelable {
206 private final EntityConfidence mEntityScores;
Richard Ledley68d94522017-10-05 10:52:19 +0100207 private final int mStart;
208 private final int mEnd;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000209 private final Bundle mExtras;
210 @Nullable private final URLSpan mUrlSpan;
Richard Ledley68d94522017-10-05 10:52:19 +0100211
212 /**
213 * Create a new TextLink.
214 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000215 * @param start The start index of the identified subsequence
216 * @param end The end index of the identified subsequence
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000217 * @param entityConfidence A mapping of entity type to confidence score
218 * @param extras A bundle containing custom data related to this TextLink
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000219 * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled
Richard Ledley3e43fc52018-01-29 11:58:13 +0000220 *
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000221 * @throws IllegalArgumentException if {@code entityConfidence} is null or empty
222 * @throws IllegalArgumentException if {@code start} is greater than {@code end}
Richard Ledley68d94522017-10-05 10:52:19 +0100223 */
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000224 private TextLink(int start, int end, @NonNull EntityConfidence entityConfidence,
225 @NonNull Bundle extras, @Nullable URLSpan urlSpan) {
226 Preconditions.checkNotNull(entityConfidence);
227 Preconditions.checkArgument(!entityConfidence.getEntities().isEmpty());
Richard Ledley68d94522017-10-05 10:52:19 +0100228 Preconditions.checkArgument(start <= end);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000229 Preconditions.checkNotNull(extras);
Richard Ledley68d94522017-10-05 10:52:19 +0100230 mStart = start;
231 mEnd = end;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000232 mEntityScores = entityConfidence;
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000233 mUrlSpan = urlSpan;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000234 mExtras = extras;
Richard Ledley68d94522017-10-05 10:52:19 +0100235 }
236
237 /**
238 * Returns the start index of this link in the original text.
239 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000240 * @return the start index
Richard Ledley68d94522017-10-05 10:52:19 +0100241 */
242 public int getStart() {
243 return mStart;
244 }
245
246 /**
247 * Returns the end index of this link in the original text.
248 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000249 * @return the end index
Richard Ledley68d94522017-10-05 10:52:19 +0100250 */
251 public int getEnd() {
252 return mEnd;
253 }
254
255 /**
256 * Returns the number of entity types that have confidence scores.
257 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000258 * @return the entity count
Richard Ledley68d94522017-10-05 10:52:19 +0100259 */
260 public int getEntityCount() {
261 return mEntityScores.getEntities().size();
262 }
263
264 /**
265 * Returns the entity type at a given index. Entity types are sorted by confidence.
266 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000267 * @return the entity type at the provided index
Richard Ledley68d94522017-10-05 10:52:19 +0100268 */
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000269 @NonNull public @EntityType String getEntity(int index) {
Richard Ledley68d94522017-10-05 10:52:19 +0100270 return mEntityScores.getEntities().get(index);
271 }
272
273 /**
274 * Returns the confidence score for a particular entity type.
275 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000276 * @param entityType the entity type
Richard Ledley68d94522017-10-05 10:52:19 +0100277 */
278 public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000279 @EntityType String entityType) {
Richard Ledley68d94522017-10-05 10:52:19 +0100280 return mEntityScores.getConfidenceScore(entityType);
281 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100282
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000283 /**
284 * Returns a bundle containing custom data related to this TextLink.
285 */
286 public Bundle getExtras() {
287 return mExtras;
288 }
289
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100290 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100291 public String toString() {
292 return String.format(Locale.US,
293 "TextLink{start=%s, end=%s, entityScores=%s, urlSpan=%s}",
294 mStart, mEnd, mEntityScores, mUrlSpan);
295 }
296
297 @Override
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100298 public int describeContents() {
299 return 0;
300 }
301
302 @Override
303 public void writeToParcel(Parcel dest, int flags) {
304 mEntityScores.writeToParcel(dest, flags);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100305 dest.writeInt(mStart);
306 dest.writeInt(mEnd);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000307 dest.writeBundle(mExtras);
308 }
309
310 private static TextLink readFromParcel(Parcel in) {
311 final EntityConfidence entityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
312 final int start = in.readInt();
313 final int end = in.readInt();
314 final Bundle extras = in.readBundle();
315 return new TextLink(start, end, entityConfidence, extras, null /* urlSpan */);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100316 }
317
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700318 public static final @android.annotation.NonNull Parcelable.Creator<TextLink> CREATOR =
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100319 new Parcelable.Creator<TextLink>() {
320 @Override
321 public TextLink createFromParcel(Parcel in) {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000322 return readFromParcel(in);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100323 }
324
325 @Override
326 public TextLink[] newArray(int size) {
327 return new TextLink[size];
328 }
329 };
Richard Ledley68d94522017-10-05 10:52:19 +0100330 }
331
332 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100333 * A request object for generating TextLinks.
Richard Ledley68d94522017-10-05 10:52:19 +0100334 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100335 public static final class Request implements Parcelable {
Richard Ledley68d94522017-10-05 10:52:19 +0100336
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100337 private final CharSequence mText;
338 @Nullable private final LocaleList mDefaultLocales;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000339 @Nullable private final EntityConfig mEntityConfig;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100340 private final boolean mLegacyFallback;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000341 @Nullable private String mCallingPackageName;
Tony Mak916bb9e2018-11-08 20:45:20 +0000342 private final Bundle mExtras;
Jan Althaus31efdc32018-02-19 22:23:13 +0100343
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100344 private Request(
345 CharSequence text,
346 LocaleList defaultLocales,
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000347 EntityConfig entityConfig,
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100348 boolean legacyFallback,
Tony Mak916bb9e2018-11-08 20:45:20 +0000349 Bundle extras) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100350 mText = text;
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000351 mDefaultLocales = defaultLocales;
Richard Ledleydb18a572017-11-30 17:33:51 +0000352 mEntityConfig = entityConfig;
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000353 mLegacyFallback = legacyFallback;
Tony Mak916bb9e2018-11-08 20:45:20 +0000354 mExtras = extras;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100355 }
356
357 /**
358 * Returns the text to generate links for.
359 */
360 @NonNull
361 public CharSequence getText() {
362 return mText;
Jan Althaus31efdc32018-02-19 22:23:13 +0100363 }
364
365 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000366 * @return ordered list of locale preferences that can be used to disambiguate
Richard Ledley3e43fc52018-01-29 11:58:13 +0000367 * the provided text
Richard Ledley68d94522017-10-05 10:52:19 +0100368 */
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000369 @Nullable
370 public LocaleList getDefaultLocales() {
371 return mDefaultLocales;
Richard Ledley68d94522017-10-05 10:52:19 +0100372 }
Richard Ledleydb18a572017-11-30 17:33:51 +0000373
374 /**
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000375 * @return The config representing the set of entities to look for
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000376 * @see Builder#setEntityConfig(EntityConfig)
Richard Ledleydb18a572017-11-30 17:33:51 +0000377 */
378 @Nullable
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000379 public EntityConfig getEntityConfig() {
Richard Ledleydb18a572017-11-30 17:33:51 +0000380 return mEntityConfig;
381 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100382
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000383 /**
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000384 * Returns whether the TextClassifier can fallback to legacy links if smart linkify is
385 * disabled.
386 * <strong>Note: </strong>This is not parcelled.
387 * @hide
388 */
389 public boolean isLegacyFallback() {
390 return mLegacyFallback;
391 }
392
393 /**
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000394 * Sets the name of the package that is sending this request.
395 * <p>
396 * Package-private for SystemTextClassifier's use.
397 * @hide
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000398 */
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000399 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
400 public void setCallingPackageName(@Nullable String callingPackageName) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100401 mCallingPackageName = callingPackageName;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000402 }
403
404 /**
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000405 * Returns the name of the package that sent this request.
406 * This returns {@code null} if no calling package name is set.
407 */
408 @Nullable
409 public String getCallingPackageName() {
410 return mCallingPackageName;
411 }
412
413 /**
Tony Mak916bb9e2018-11-08 20:45:20 +0000414 * Returns the extended data.
415 *
416 * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
417 * prefer to hold a reference to the returned bundle rather than frequently calling this
418 * method.
419 */
420 @NonNull
421 public Bundle getExtras() {
422 return mExtras.deepCopy();
423 }
424
425 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100426 * A builder for building TextLinks requests.
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000427 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100428 public static final class Builder {
429
430 private final CharSequence mText;
431
432 @Nullable private LocaleList mDefaultLocales;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000433 @Nullable private EntityConfig mEntityConfig;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100434 private boolean mLegacyFallback = true; // Use legacy fall back by default.
Tony Mak916bb9e2018-11-08 20:45:20 +0000435 @Nullable private Bundle mExtras;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100436
437 public Builder(@NonNull CharSequence text) {
438 mText = Preconditions.checkNotNull(text);
439 }
440
441 /**
442 * @param defaultLocales ordered list of locale preferences that may be used to
443 * disambiguate the provided text. If no locale preferences exist,
444 * set this to null or an empty locale list.
445 * @return this builder
446 */
447 @NonNull
448 public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
449 mDefaultLocales = defaultLocales;
450 return this;
451 }
452
453 /**
454 * Sets the entity configuration to use. This determines what types of entities the
455 * TextClassifier will look for.
456 * Set to {@code null} for the default entity config and teh TextClassifier will
457 * automatically determine what links to generate.
458 *
459 * @return this builder
460 */
461 @NonNull
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000462 public Builder setEntityConfig(@Nullable EntityConfig entityConfig) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100463 mEntityConfig = entityConfig;
464 return this;
465 }
466
467 /**
468 * Sets whether the TextClassifier can fallback to legacy links if smart linkify is
469 * disabled.
470 *
471 * <p><strong>Note: </strong>This is not parcelled.
472 *
473 * @return this builder
474 * @hide
475 */
476 @NonNull
477 public Builder setLegacyFallback(boolean legacyFallback) {
478 mLegacyFallback = legacyFallback;
479 return this;
480 }
481
482 /**
Tony Mak916bb9e2018-11-08 20:45:20 +0000483 * Sets the extended data.
484 *
485 * @return this builder
486 */
487 public Builder setExtras(@Nullable Bundle extras) {
488 mExtras = extras;
489 return this;
490 }
491
492 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100493 * Builds and returns the request object.
494 */
495 @NonNull
496 public Request build() {
497 return new Request(
498 mText, mDefaultLocales, mEntityConfig,
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000499 mLegacyFallback,
Tony Mak916bb9e2018-11-08 20:45:20 +0000500 mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100501 }
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000502 }
503
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100504 @Override
505 public int describeContents() {
506 return 0;
507 }
508
509 @Override
510 public void writeToParcel(Parcel dest, int flags) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100511 dest.writeString(mText.toString());
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000512 dest.writeParcelable(mDefaultLocales, flags);
513 dest.writeParcelable(mEntityConfig, flags);
Jan Althaus31efdc32018-02-19 22:23:13 +0100514 dest.writeString(mCallingPackageName);
Tony Mak916bb9e2018-11-08 20:45:20 +0000515 dest.writeBundle(mExtras);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100516 }
517
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000518 private static Request readFromParcel(Parcel in) {
519 final String text = in.readString();
520 final LocaleList defaultLocales = in.readParcelable(null);
521 final EntityConfig entityConfig = in.readParcelable(null);
522 final String callingPackageName = in.readString();
523 final Bundle extras = in.readBundle();
524
525 final Request request = new Request(text, defaultLocales, entityConfig,
526 /* legacyFallback= */ true, extras);
527 request.setCallingPackageName(callingPackageName);
528 return request;
529 }
530
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700531 public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR =
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100532 new Parcelable.Creator<Request>() {
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100533 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100534 public Request createFromParcel(Parcel in) {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000535 return readFromParcel(in);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100536 }
537
538 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100539 public Request[] newArray(int size) {
540 return new Request[size];
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100541 }
542 };
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000543 }
Richard Ledley68d94522017-10-05 10:52:19 +0100544
545 /**
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000546 * A ClickableSpan for a TextLink.
547 *
548 * <p>Applies only to TextViews.
549 */
550 public static class TextLinkSpan extends ClickableSpan {
551
Abodunrinwa Toki33fa3822018-04-16 10:05:16 +0100552 /**
553 * How the clickspan is triggered.
554 * @hide
555 */
556 @Retention(RetentionPolicy.SOURCE)
557 @IntDef({INVOCATION_METHOD_UNSPECIFIED, INVOCATION_METHOD_TOUCH,
558 INVOCATION_METHOD_KEYBOARD})
559 public @interface InvocationMethod {}
560
561 /** @hide */
562 public static final int INVOCATION_METHOD_UNSPECIFIED = -1;
563 /** @hide */
564 public static final int INVOCATION_METHOD_TOUCH = 0;
565 /** @hide */
566 public static final int INVOCATION_METHOD_KEYBOARD = 1;
567
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000568 private final TextLink mTextLink;
569
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000570 public TextLinkSpan(@NonNull TextLink textLink) {
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000571 mTextLink = textLink;
572 }
573
574 @Override
575 public void onClick(View widget) {
Abodunrinwa Toki33fa3822018-04-16 10:05:16 +0100576 onClick(widget, INVOCATION_METHOD_UNSPECIFIED);
577 }
578
579 /** @hide */
580 public final void onClick(View widget, @InvocationMethod int invocationMethod) {
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000581 if (widget instanceof TextView) {
582 final TextView textView = (TextView) widget;
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000583 final Context context = textView.getContext();
584 if (TextClassificationManager.getSettings(context).isSmartLinkifyEnabled()) {
Abodunrinwa Toki33fa3822018-04-16 10:05:16 +0100585 switch (invocationMethod) {
586 case INVOCATION_METHOD_TOUCH:
587 textView.requestActionMode(this);
588 break;
589 case INVOCATION_METHOD_KEYBOARD:// fall though
590 case INVOCATION_METHOD_UNSPECIFIED: // fall through
591 default:
592 textView.handleClick(this);
593 break;
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000594 }
595 } else {
596 if (mTextLink.mUrlSpan != null) {
597 mTextLink.mUrlSpan.onClick(textView);
598 } else {
599 textView.handleClick(this);
600 }
601 }
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000602 }
603 }
604
605 public final TextLink getTextLink() {
606 return mTextLink;
607 }
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000608
609 /** @hide */
610 @VisibleForTesting(visibility = Visibility.PRIVATE)
611 @Nullable
612 public final String getUrl() {
613 if (mTextLink.mUrlSpan != null) {
614 return mTextLink.mUrlSpan.getURL();
615 }
616 return null;
617 }
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000618 }
Richard Ledley68d94522017-10-05 10:52:19 +0100619
620 /**
621 * A builder to construct a TextLinks instance.
622 */
623 public static final class Builder {
624 private final String mFullText;
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000625 private final ArrayList<TextLink> mLinks;
Tony Mak916bb9e2018-11-08 20:45:20 +0000626 private Bundle mExtras;
Richard Ledley68d94522017-10-05 10:52:19 +0100627
628 /**
629 * Create a new TextLinks.Builder.
630 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000631 * @param fullText The full text to annotate with links
Richard Ledley68d94522017-10-05 10:52:19 +0100632 */
633 public Builder(@NonNull String fullText) {
634 mFullText = Preconditions.checkNotNull(fullText);
635 mLinks = new ArrayList<>();
636 }
637
638 /**
639 * Adds a TextLink.
640 *
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000641 * @param start The start index of the identified subsequence
642 * @param end The end index of the identified subsequence
643 * @param entityScores A mapping of entity type to confidence score
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000644 *
645 * @throws IllegalArgumentException if entityScores is null or empty.
Richard Ledley68d94522017-10-05 10:52:19 +0100646 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100647 @NonNull
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000648 public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores) {
649 return addLink(start, end, entityScores, Bundle.EMPTY, null);
650 }
651
652 /**
653 * Adds a TextLink.
654 *
655 * @see #addLink(int, int, Map)
656 * @param extras An optional bundle containing custom data related to this TextLink
657 */
658 @NonNull
659 public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores,
660 @NonNull Bundle extras) {
661 return addLink(start, end, entityScores, extras, null);
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000662 }
663
664 /**
665 * @see #addLink(int, int, Map)
666 * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled.
667 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100668 @NonNull
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000669 Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores,
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000670 @Nullable URLSpan urlSpan) {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000671 return addLink(start, end, entityScores, Bundle.EMPTY, urlSpan);
672 }
673
674 private Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores,
675 @NonNull Bundle extras, @Nullable URLSpan urlSpan) {
676 mLinks.add(new TextLink(
677 start, end, new EntityConfidence(entityScores), extras, urlSpan));
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000678 return this;
679 }
680
681 /**
682 * Removes all {@link TextLink}s.
683 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100684 @NonNull
Abodunrinwa Tokife20cdd2017-12-12 02:31:25 +0000685 public Builder clearTextLinks() {
686 mLinks.clear();
Richard Ledley68d94522017-10-05 10:52:19 +0100687 return this;
688 }
689
690 /**
Tony Mak916bb9e2018-11-08 20:45:20 +0000691 * Sets the extended data.
692 *
693 * @return this builder
694 */
Tony Mak42ab9842019-03-05 15:38:51 +0000695 @NonNull
Tony Mak916bb9e2018-11-08 20:45:20 +0000696 public Builder setExtras(@Nullable Bundle extras) {
697 mExtras = extras;
698 return this;
699 }
700
701 /**
Richard Ledley68d94522017-10-05 10:52:19 +0100702 * Constructs a TextLinks instance.
703 *
Richard Ledley3e43fc52018-01-29 11:58:13 +0000704 * @return the constructed TextLinks
Richard Ledley68d94522017-10-05 10:52:19 +0100705 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100706 @NonNull
Richard Ledley68d94522017-10-05 10:52:19 +0100707 public TextLinks build() {
Tony Mak916bb9e2018-11-08 20:45:20 +0000708 return new TextLinks(mFullText, mLinks,
709 mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
Richard Ledley68d94522017-10-05 10:52:19 +0100710 }
711 }
Richard Ledley68d94522017-10-05 10:52:19 +0100712}