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