public final class

TextLinks

extends java.lang.Object

 java.lang.Object

↳androidx.textclassifier.TextLinks

Gradle dependencies

compile group: 'androidx.textclassifier', name: 'textclassifier', version: '1.0.0-alpha04'

  • groupId: androidx.textclassifier
  • artifactId: textclassifier
  • version: 1.0.0-alpha04

Artifact androidx.textclassifier:textclassifier:1.0.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.textclassifier:textclassifier com.android.support:textclassifier

Overview

A collection of links, representing subsequences of text and the entity types (phone number, address, url, etc) they may be.

Summary

Fields
public static final intAPPLY_STRATEGY_IGNORE

Do not replace s that exist where the TextLinks.TextLinkSpan needs to be applied to.

public static final intAPPLY_STRATEGY_REPLACE

Replace any s that exist where the TextLinks.TextLinkSpan needs to be applied to.

public static final intSTATUS_DIFFERENT_TEXT

The specified text does not match the text used to generate the links.

public static final intSTATUS_LINKS_APPLIED

Links were successfully applied to the text.

public static final intSTATUS_NO_LINKS_APPLIED

No links applied to text.

public static final intSTATUS_NO_LINKS_FOUND

No links exist to apply to text.

public static final intSTATUS_UNKNOWN

Status unknown.

Methods
public intapply(Spannable text, TextClassifier textClassifier, TextLinksParams textLinksParams)

Annotates the given text with the generated links.

public static TextLinkscreateFromBundle(Bundle bundle)

Extracts an TextLinks object from a bundle that was added using TextLinks.toBundle().

public BundlegetExtras()

Returns the extended, vendor specific data.

public java.util.Collection<TextLinks.TextLink>getLinks()

Returns an unmodifiable Collection of the links.

public java.lang.CharSequencegetText()

Returns the text that was used to generate these links.

public BundletoBundle()

Adds a TextLinks object to a Bundle that can be read back with the same parameters to TextLinks.createFromBundle(Bundle).

public java.lang.StringtoString()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Fields

public static final int STATUS_UNKNOWN

Status unknown.

public static final int STATUS_LINKS_APPLIED

Links were successfully applied to the text.

public static final int STATUS_NO_LINKS_FOUND

No links exist to apply to text. Links count is zero.

public static final int STATUS_NO_LINKS_APPLIED

No links applied to text. The links were filtered out.

public static final int STATUS_DIFFERENT_TEXT

The specified text does not match the text used to generate the links.

public static final int APPLY_STRATEGY_IGNORE

Do not replace s that exist where the TextLinks.TextLinkSpan needs to be applied to. Do not apply the TextLinkSpan.

public static final int APPLY_STRATEGY_REPLACE

Replace any s that exist where the TextLinks.TextLinkSpan needs to be applied to.

Methods

public java.lang.CharSequence getText()

Returns the text that was used to generate these links.

public java.util.Collection<TextLinks.TextLink> getLinks()

Returns an unmodifiable Collection of the links.

public Bundle getExtras()

Returns the extended, vendor specific data.

NOTE: Each call to this method returns a new bundle copy so clients should prefer to hold a reference to the returned bundle rather than frequently calling this method. Avoid updating the content of this bundle. On pre-O devices, the values in the Bundle are not deep copied.

public java.lang.String toString()

public Bundle toBundle()

Adds a TextLinks object to a Bundle that can be read back with the same parameters to TextLinks.createFromBundle(Bundle).

public static TextLinks createFromBundle(Bundle bundle)

Extracts an TextLinks object from a bundle that was added using TextLinks.toBundle().

public int apply(Spannable text, TextClassifier textClassifier, TextLinksParams textLinksParams)

Annotates the given text with the generated links.

NOTE: It may be necessary to set a LinkMovementMethod on the TextView widget to properly handle links. See TextView. It is also necessary that the TextView be focusable. See TextView} and TextView.

Parameters:

text: the text to apply the links to. Must match the original text
textClassifier: the TextClassifier to use to classify a clicked link. Should usually be the one used to generate the links
textLinksParams: the param that specifies how the links should be applied

Returns:

the status code which indicates the operation is success or not.

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.textclassifier;

import static androidx.textclassifier.ConvertUtils.toPlatformEntityConfig;
import static androidx.textclassifier.ConvertUtils.unwrapLocalListCompat;

import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.os.Build;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.method.MovementMethod;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.CallSuper;
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.RemoteActionCompat;
import androidx.core.os.LocaleListCompat;
import androidx.core.util.Preconditions;
import androidx.textclassifier.TextClassifier.EntityConfig;
import androidx.textclassifier.TextClassifier.EntityType;
import androidx.textclassifier.widget.ToolbarController;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Function;

/**
 * A collection of links, representing subsequences of text and the entity types (phone number,
 * address, url, etc) they may be.
 *
 * @deprecated Use {@link android.view.textclassifier.TextLinks} instead.
 */
@Deprecated
public final class TextLinks {

    private static final String LOG_TAG = "TextLinks";
    private static final String EXTRA_FULL_TEXT = "text";
    private static final String EXTRA_LINKS = "links";
    private static final String EXTRA_EXTRAS = "extras";

    private final CharSequence mFullText;
    private final List<TextLink> mLinks;
    private final Bundle mExtras;

    static final Executor sWorkerExecutor = Executors.newFixedThreadPool(1);
    static final MainThreadExecutor sMainThreadExecutor = new MainThreadExecutor();

    /** Status unknown. */
    public static final int STATUS_UNKNOWN = -1;
    /** Links were successfully applied to the text. */
    public static final int STATUS_LINKS_APPLIED = 0;
    /** No links exist to apply to text. Links count is zero. */
    public static final int STATUS_NO_LINKS_FOUND = 1;
    /** No links applied to text. The links were filtered out. */
    public static final int STATUS_NO_LINKS_APPLIED = 2;
    /** The specified text does not match the text used to generate the links. */
    public static final int STATUS_DIFFERENT_TEXT = 3;

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            STATUS_UNKNOWN,
            STATUS_LINKS_APPLIED,
            STATUS_NO_LINKS_FOUND,
            STATUS_NO_LINKS_APPLIED,
            STATUS_DIFFERENT_TEXT
    })
    public @interface Status {}

    /** Do not replace {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to
     * be applied to. Do not apply the TextLinkSpan. **/
    public static final int APPLY_STRATEGY_IGNORE = 0;
    /** Replace any {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to be
     * applied to. **/
    public static final int APPLY_STRATEGY_REPLACE = 1;

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({APPLY_STRATEGY_IGNORE, APPLY_STRATEGY_REPLACE})
    public @interface ApplyStrategy {}

    TextLinks(CharSequence fullText, List<TextLink> links, Bundle extras) {
        mFullText = fullText;
        mLinks = Collections.unmodifiableList(links);
        mExtras = extras;
    }

    /**
     * Returns the text that was used to generate these links.
     */
    @NonNull
    public CharSequence getText() {
        return mFullText;
    }

    /**
     * Returns an unmodifiable Collection of the links.
     */
    @NonNull
    public Collection<TextLink> getLinks() {
        return mLinks;
    }

    /**
     * Returns the extended, vendor specific data.
     *
     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
     * prefer to hold a reference to the returned bundle rather than frequently calling this
     * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
     * Bundle are not deep copied.
     */
    @NonNull
    public Bundle getExtras() {
        return BundleUtils.deepCopy(mExtras);
    }

    @Override
    @NonNull
    public String toString() {
        return String.format(Locale.US, "TextLinks{fullText=%s, links=%s}", mFullText, mLinks);
    }

    /**
     * Adds a TextLinks object to a Bundle that can be read back with the same parameters
     * to {@link #createFromBundle(Bundle)}.
     */
    @NonNull
    public Bundle toBundle() {
        final Bundle bundle = new Bundle();
        bundle.putString(EXTRA_FULL_TEXT, mFullText.toString());
        BundleUtils.putTextLinkList(bundle, EXTRA_LINKS, mLinks);
        bundle.putBundle(EXTRA_EXTRAS, mExtras);
        return bundle;
    }

    /**
     * Extracts an TextLinks object from a bundle that was added using {@link #toBundle()}.
     *
     * @throws IllegalArgumentException if the bundle is malformed.
     */
    @NonNull
    public static TextLinks createFromBundle(@NonNull Bundle bundle) {
        Bundle extras = bundle.getBundle(EXTRA_EXTRAS);
        return new TextLinks(
                bundle.getString(EXTRA_FULL_TEXT),
                BundleUtils.getTextLinkListOrThrow(bundle, EXTRA_LINKS),
                extras == null ? Bundle.EMPTY : extras);
    }

    /**
     * Annotates the given text with the generated links.
     *
     * <p><strong>NOTE: </strong>It may be necessary to set a LinkMovementMethod on the TextView
     * widget to properly handle links. See {@link TextView#setMovementMethod(MovementMethod)}.
     * It is also necessary that the TextView be focusable.
     * See {@link TextView#setFocusable(boolean)}} and
     * {@link TextView#setFocusableInTouchMode(boolean)}.
     *
     * @param text the text to apply the links to. Must match the original text
     * @param textClassifier the TextClassifier to use to classify a clicked link. Should usually
     *                       be the one used to generate the links
     * @param textLinksParams the param that specifies how the links should be applied
     *
     * @return the status code which indicates the operation is success or not.
     */
    @Status
    public int apply(
            @NonNull Spannable text,
            @NonNull TextClassifier textClassifier,
            @NonNull TextLinksParams textLinksParams) {
        Preconditions.checkNotNull(text);
        Preconditions.checkNotNull(textClassifier);
        Preconditions.checkNotNull(textLinksParams);

        return textLinksParams.apply(text, this, textClassifier);
    }

    /**
     * A link, identifying a substring of text and possible entity types for it.
     *
     * @deprecated Use {@link android.view.textclassifier.TextLinks.TextLink} instead.
     */
    @Deprecated
    public static final class TextLink {

        private static final String EXTRA_ENTITY_SCORES = "scores";
        private static final String EXTRA_START = "start";
        private static final String EXTRA_END = "end";

        private final EntityConfidence mEntityScores;
        private final int mStart;
        private final int mEnd;

        /**
         * Create a new TextLink.
         *
         * @throws IllegalArgumentException if entityScores is null or empty.
         * @hide
         */
        @VisibleForTesting
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        TextLink(int start, int end, @NonNull Map<String, Float> entityScores) {
            Preconditions.checkNotNull(entityScores);
            Preconditions.checkArgument(!entityScores.isEmpty());
            Preconditions.checkArgument(start <= end);
            mStart = start;
            mEnd = end;
            mEntityScores = new EntityConfidence(entityScores);
        }

        /**
         * Returns the start index of this link in the original text.
         *
         * @return the start index.
         */
        public int getStart() {
            return mStart;
        }

        /**
         * Returns the end index of this link in the original text.
         *
         * @return the end index.
         */
        public int getEnd() {
            return mEnd;
        }

        /**
         * Returns the number of entity types that have confidence scores.
         *
         * @return the entity type count.
         */
        public int getEntityTypeCount() {
            return mEntityScores.getEntities().size();
        }

        /**
         * Returns the entity type at a given index. Entity types are sorted by confidence.
         *
         * @return the entity type at the provided index.
         */
        @NonNull public @EntityType String getEntityType(int index) {
            return mEntityScores.getEntities().get(index);
        }

        /**
         * Returns the confidence score for a particular entity type.
         *
         * @param entityType the entity type.
         */
        public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
                @EntityType String entityType) {
            return mEntityScores.getConfidenceScore(entityType);
        }

        @Override
        @NonNull
        public String toString() {
            return String.format(Locale.US,
                    "TextLink{start=%s, end=%s, entityScores=%s}",
                    mStart, mEnd, mEntityScores);
        }

        /**
         * Adds this TextLink to a Bundle that can be read back with the same parameters
         * to {@link #createFromBundle(Bundle)}.
         */
        @NonNull
        public Bundle toBundle() {
            final Bundle bundle = new Bundle();
            BundleUtils.putMap(bundle, EXTRA_ENTITY_SCORES, mEntityScores.getConfidenceMap());
            bundle.putInt(EXTRA_START, mStart);
            bundle.putInt(EXTRA_END, mEnd);
            return bundle;
        }

        @NonNull
        EntityConfidence getEntityScores() {
            return mEntityScores;
        }

        /**
         * Extracts a TextLink from a bundle that was added using {@link #toBundle()}.
         */
        @NonNull
        public static TextLink createFromBundle(@NonNull Bundle bundle) {
            return new TextLink(
                    bundle.getInt(EXTRA_START),
                    bundle.getInt(EXTRA_END),
                    BundleUtils.getFloatStringMapOrThrow(bundle, EXTRA_ENTITY_SCORES));
        }
    }

    /**
     * A request object for generating TextLinks.
     *
     * @deprecated Use {@link android.view.textclassifier.TextLinks.Request} instead.
     */
    @Deprecated
    public static final class Request {

        private static final String EXTRA_TEXT = "text";
        private static final String EXTRA_DEFAULT_LOCALES = "locales";
        private static final String EXTRA_ENTITY_CONFIG = "entity_config";
        private static final String EXTRA_REFERENCE_TIME = "reference_time";

        private final CharSequence mText;
        @Nullable private final LocaleListCompat mDefaultLocales;
        @NonNull private final EntityConfig mEntityConfig;
        @Nullable private Long mReferenceTime = null;
        @NonNull private final Bundle mExtras;

        Request(
                @NonNull CharSequence text,
                @Nullable LocaleListCompat defaultLocales,
                @Nullable EntityConfig entityConfig,
                @Nullable Long referenceTime,
                @NonNull Bundle extras) {
            mText = SpannedString.valueOf(text);
            mDefaultLocales = defaultLocales;
            mEntityConfig = entityConfig == null
                    ? new TextClassifier.EntityConfig.Builder().build()
                    : entityConfig;
            mReferenceTime = referenceTime;
            mExtras = extras;
        }

        /**
         * Returns the text to generate links for.
         */
        @NonNull
        public CharSequence getText() {
            return mText;
        }

        /**
         * @return ordered list of locale preferences that can be used to disambiguate
         *      the provided text
         */
        @Nullable
        public LocaleListCompat getDefaultLocales() {
            return mDefaultLocales;
        }

        /**
         * @return The config representing the set of entities to look for
         * @see Builder#setEntityConfig(EntityConfig)
         */
        @NonNull
        public EntityConfig getEntityConfig() {
            return mEntityConfig;
        }

        /**
         * @return reference time based on which relative dates (e.g. "tomorrow") should be
         *      interpreted. This should be milliseconds from the epoch of
         *      1970-01-01T00:00:00Z(UTC timezone).
         */
        @Nullable
        public Long getReferenceTime() {
            return mReferenceTime;
        }

        /**
         * Returns the extended, vendor specific data.
         *
         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
         * prefer to hold a reference to the returned bundle rather than frequently calling this
         * method. Avoid updating the content of this bundle. On pre-O devices, the values in the
         * Bundle are not deep copied.
         */
        @NonNull
        public Bundle getExtras() {
            return BundleUtils.deepCopy(mExtras);
        }

        /**
         * A builder for building TextLinks requests.
         *
         * @deprecated Use {@link android.view.textclassifier.TextLinks.Builder} instead.
         */
        @Deprecated
        public static final class Builder {

            private final CharSequence mText;

            @Nullable private LocaleListCompat mDefaultLocales;
            @Nullable private EntityConfig mEntityConfig;
            @Nullable private Long mReferenceTime = null;
            @Nullable private Bundle mExtras;

            public Builder(@NonNull CharSequence text) {
                mText = Preconditions.checkNotNull(text);
            }

            /**
             * @param defaultLocales ordered list of locale preferences that may be used to
             *                       disambiguate the provided text. If no locale preferences exist,
             *                       set this to null or an empty locale list.
             * @return this builder
             */
            @NonNull
            public Builder setDefaultLocales(@Nullable LocaleListCompat defaultLocales) {
                mDefaultLocales = defaultLocales;
                return this;
            }

            /**
             * Sets the entity configuration to use. This determines what types of entities the
             * TextClassifier will look for.
             * Set to {@code null} for the default entity config and the TextClassifier will
             * automatically determine what links to generate.
             *
             * @return this builder
             */
            @NonNull
            public Builder setEntityConfig(@Nullable EntityConfig entityConfig) {
                mEntityConfig = entityConfig;
                return this;
            }

            /**
             * @param referenceTime reference time based on which relative dates (e.g. "tomorrow")
             *      should be interpreted. This should usually be the time when the text was
             *      originally composed and should be milliseconds from the epoch of
             *      1970-01-01T00:00:00Z(UTC timezone). For example, if there is a message saying
             *      "see you 10 days later", and the message was composed yesterday, text classifier
             *      will then realize it is indeed means 9 days later from now and generate a link
             *      accordingly. If no reference time or {@code null} is set, now is used.
             *
             * @return this builder
             */
            @NonNull
            public TextLinks.Request.Builder setReferenceTime(
                    @Nullable Long referenceTime) {
                mReferenceTime = referenceTime;
                return this;
            }

            /**
             * Sets the extended, vendor specific data.
             */
            @NonNull
            public Builder setExtras(@Nullable Bundle extras) {
                mExtras = extras;
                return this;
            }

            /**
             * Builds and returns the request object.
             */
            @NonNull
            public Request build() {
                return new Request(mText, mDefaultLocales, mEntityConfig, mReferenceTime,
                        mExtras == null ? Bundle.EMPTY : mExtras);
            }

        }

        /**
         * Adds this Request to a Bundle that can be read back with the same parameters
         * to {@link #createFromBundle(Bundle)}.
         */
        @NonNull
        public Bundle toBundle() {
            final Bundle bundle = new Bundle();
            bundle.putCharSequence(EXTRA_TEXT, mText);
            bundle.putBundle(EXTRA_ENTITY_CONFIG, mEntityConfig.toBundle());
            BundleUtils.putLocaleList(bundle, EXTRA_DEFAULT_LOCALES, mDefaultLocales);
            BundleUtils.putLong(bundle, EXTRA_REFERENCE_TIME, mReferenceTime);
            bundle.putBundle(EXTRA_EXTRAS, mExtras);
            return bundle;
        }

        /**
         * Extracts a Request from a bundle that was added using {@link #toBundle()}.
         */
        @NonNull
        public static Request createFromBundle(@NonNull Bundle bundle) {
            return new Builder(bundle.getCharSequence(EXTRA_TEXT))
                    .setDefaultLocales(BundleUtils.getLocaleList(bundle, EXTRA_DEFAULT_LOCALES))
                    .setEntityConfig(
                            EntityConfig.createFromBundle(bundle.getBundle(EXTRA_ENTITY_CONFIG)))
                    .setReferenceTime(BundleUtils.getLong(bundle, EXTRA_REFERENCE_TIME))
                    .setExtras(bundle.getBundle(EXTRA_EXTRAS))
                    .build();
        }

        /** @hide */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @RequiresApi(28)
        @NonNull
        android.view.textclassifier.TextLinks.Request toPlatform() {
            return new android.view.textclassifier.TextLinks.Request.Builder(getText())
                    .setDefaultLocales(unwrapLocalListCompat(getDefaultLocales()))
                    .setEntityConfig(toPlatformEntityConfig(getEntityConfig()))
                    .build();
        }

        /** @hide */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @RequiresApi(28)
        @NonNull
        static TextLinks.Request fromPlatform(
                @NonNull android.view.textclassifier.TextLinks.Request request) {
            return new TextLinks.Request.Builder(request.getText())
                    .setDefaultLocales(ConvertUtils.wrapLocalList(request.getDefaultLocales()))
                    .setEntityConfig(
                            TextClassifier.EntityConfig.fromPlatform(request.getEntityConfig()))
                    .build();
        }
    }

    /**
     * A factory to create spans from TextLinks.
     *
     * @deprecated Use {@link android.view.textclassifier.TextLinks#apply(Spannable, int, Function)}
     * instead.
     */
    @Deprecated
    public interface SpanFactory {

        /** Creates a span from a text link. */
        TextLinkSpan createSpan(@NonNull TextLinkSpanData textLinkSpanData);
    }

    /**
     * Contains necessary data for {@link TextLinkSpan}.
     * @deprecated Access the data from
     * {@link android.view.textclassifier.TextLinks.TextLinkSpan} directly instead.
     */
    @Deprecated
    public static class TextLinkSpanData {
        @NonNull
        private final TextLink mTextLink;
        @NonNull
        private final TextClassifier mTextClassifier;
        @Nullable
        private final Long mReferenceTime;

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        TextLinkSpanData(
                @NonNull TextLink textLink,
                @NonNull TextClassifier textClassifier,
                @Nullable Long referenceTime) {
            mTextLink = Preconditions.checkNotNull(textLink);
            mTextClassifier = Preconditions.checkNotNull(textClassifier);
            mReferenceTime = referenceTime;
        }

        @NonNull
        public TextLink getTextLink() {
            return mTextLink;
        }

        /**
         * TODO: Make it public once we confirm how should we represent a datetime.
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @Nullable
        public Long getReferenceTime() {
            return mReferenceTime;
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        TextClassifier getTextClassifier() {
            return mTextClassifier;
        }
    }

    /**
     * A ClickableSpan for a TextLink.
     * <p>
     * You can implement the {@link #onClick(View)} function to specify the on click behavior of
     * the span.
     *
     * @deprecated Use {@link android.view.textclassifier.TextLinks.TextLinkSpan} instead.
     */
    @Deprecated
    public abstract static class TextLinkSpan extends ClickableSpan {
        private TextLinkSpanData mTextLinkSpanData;

        public TextLinkSpan(@NonNull TextLinkSpanData textLinkSpanData) {
            mTextLinkSpanData = Preconditions.checkNotNull(textLinkSpanData);
        }

        /**
         * Returns the data that is relevant to this span.
         */
        @NonNull
        public final TextLinkSpanData getTextLinkSpanData() {
            return mTextLinkSpanData;
        }
    }

    /**
     * The default implementation of {@link TextLinkSpan}.
     * <p>
     * When this span is clicked, text classifier will be used to classify the text in the span and
     * suggest possible actions. The suggested actions will be presented to users eventually.
     * You can change the way how the suggested actions are presented to user by overriding
     * {@link #onTextClassificationResult(TextView, TextClassification)}.
     *
     * @deprecated You may create your own custom span by calling
     * {@link android.view.textclassifier.TextLinks#apply(Spannable, int, Function)}.
     */
    @Deprecated
    public static class DefaultTextLinkSpan extends TextLinkSpan {

        /**
         * Constructs a DefaultTextLinkSpan.
         *
         * @param textLinkSpanData The data object that contains data of this span, like the text
         *                         link.
         */
        public DefaultTextLinkSpan(@NonNull TextLinkSpanData textLinkSpanData) {
            super(textLinkSpanData);
        }

        /**
         * Performs the click action associated with this span.
         * <p>
         * Subclass implementations should always call through to the superclass implementation.
         */
        @Override
        @CallSuper
        @SuppressLint("SyntheticAccessor")
        public void onClick(@NonNull View widget) {
            if (!(widget instanceof TextView)) {
                return;
            }

            final TextView textView = (TextView) widget;
            final CharSequence text = textView.getText();

            if (!(text instanceof Spanned)) {
                return;
            }

            final Spanned spanned = (Spanned) text;
            final int start = spanned.getSpanStart(this);
            final int end = spanned.getSpanEnd(this);
            if (start < 0 || start >= end || end > text.length()) {
                Log.d(LOG_TAG, "Cannot show link toolbar. Invalid text indices");
                return;
            }

            final TextClassification.Request request =
                    new TextClassification.Request.Builder(text, start, end)
                            .setReferenceTime(getTextLinkSpanData().getReferenceTime())
                            .setDefaultLocales(getLocales(textView))
                            .build();
            // TODO: Truncate the text.
            sWorkerExecutor.execute(new ClassifyTextRunnable(textView, this, request, spanned));
        }

        private static class ClassifyTextRunnable implements Runnable {
            private WeakReference<TextView> mTextView;
            private DefaultTextLinkSpan mTextLinkSpan;
            private TextClassification.Request mRequest;
            private Spanned mClassifiedSpan;

            private ClassifyTextRunnable(
                    TextView textView,
                    DefaultTextLinkSpan textLinkSpan,
                    TextClassification.Request request, Spanned classifiedSpan) {
                mTextView = new WeakReference<>(textView);
                mTextLinkSpan = textLinkSpan;
                mRequest = request;
                mClassifiedSpan = classifiedSpan;
            }

            @Override
            @SuppressLint("SyntheticAccessor")
            public void run() {
                final TextClassifier classifier =
                        mTextLinkSpan.getTextLinkSpanData().getTextClassifier();
                final TextClassification textClassification = classifier.classifyText(mRequest);
                sMainThreadExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        TextView textView = mTextView.get();
                        if (textView == null) {
                            return;
                        }
                        if (textView.getText() != mClassifiedSpan) {
                            Log.d(LOG_TAG, "Text has changed from the classified text. "
                                    + "Ignoring.");
                            return;
                        }
                        mTextLinkSpan.onTextClassificationResult(textView, textClassification);
                    }
                });
            }
        }

        /**
         * Invoked when the classification of the text of this span is available.
         * <p>
         * When user clicks on this span, text classifier will be used to classify the text of
         * this span. Once text classifier has the result, this callback will be invoked. If the
         * text in the textview is changed during the text classification, this won't be invoked.
         * <p>
         * You can change the way how the suggested actions are presented to user by overriding
         * this function.
         *
         * @param textView the textview that this span is attached to
         * @param textClassification the text classification result of the text in this span.
         */
        @UiThread
        public void onTextClassificationResult(
                @NonNull TextView textView, @NonNull TextClassification textClassification) {
            List<RemoteActionCompat> actions = textClassification.getActions();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                final Spanned spanned = SpannableString.valueOf(textView.getText());
                final int start = spanned.getSpanStart(this);
                final int end = spanned.getSpanEnd(this);
                ToolbarController.getInstance(textView).show(actions, start, end);
                return;
            }

            if (!actions.isEmpty()) {
                try {
                    actions.get(0).getActionIntent().send();
                } catch (PendingIntent.CanceledException e) {
                    Log.e(LOG_TAG, "Error handling TextLinkSpan click", e);
                }
                return;
            }
        }

        private LocaleListCompat getLocales(TextView textView) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                return ConvertUtils.wrapLocalList(textView.getTextLocales());
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                return LocaleListCompat.create(textView.getTextLocale());
            }
            return LocaleListCompat.create(Locale.getDefault());
        }
    }

    /**
     * A builder to construct a TextLinks instance.
     *
     * @deprecated Use {@link android.view.textclassifier.TextLinks.Builder} instead.
     */
    @Deprecated
    public static final class Builder {
        private final CharSequence mFullText;
        private final ArrayList<TextLink> mLinks;
        @Nullable private Bundle mExtras;

        /**
         * Create a new TextLinks.Builder.
         *
         * @param fullText The full text to annotate with links.
         */
        public Builder(@NonNull CharSequence fullText) {
            mFullText = Preconditions.checkNotNull(fullText);
            mLinks = new ArrayList<>();
        }

        /**
         * Adds a TextLink.
         *
         * @return this instance.
         *
         * @throws IllegalArgumentException if entityScores is null or empty.
         */
        @NonNull
        public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores) {
            mLinks.add(new TextLink(start, end, Preconditions.checkNotNull(entityScores)));
            return this;
        }

        /**
         * @hide
         */
        @NonNull
        Builder addLink(TextLink link) {
            mLinks.add(Preconditions.checkNotNull(link));
            return this;
        }

        /**
         * Sets the extended, vendor specific data.
         */
        @NonNull
        public Builder setExtras(@Nullable Bundle extras) {
            mExtras = extras;
            return this;
        }

        /**
         * Removes all {@link TextLink}s.
         */
        // TODO: Hide.
        @NonNull
        public Builder clearTextLinks() {
            mLinks.clear();
            return this;
        }

        /**
         * Constructs a TextLinks instance.
         *
         * @return the constructed TextLinks.
         */
        @NonNull
        public TextLinks build() {
            return new TextLinks(mFullText, mLinks,
                    mExtras == null ? Bundle.EMPTY : BundleUtils.deepCopy(mExtras));
        }
    }

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @RequiresApi(28)
    @NonNull
    // TODO: In Q, we should make getText public and use it here.
    static TextLinks fromPlatform(
            @NonNull android.view.textclassifier.TextLinks textLinks,
            @NonNull CharSequence requestText) {
        Preconditions.checkNotNull(textLinks);
        Preconditions.checkNotNull(requestText);

        Collection<android.view.textclassifier.TextLinks.TextLink> links = textLinks.getLinks();
        TextLinks.Builder builder = new TextLinks.Builder(requestText.toString());
        for (android.view.textclassifier.TextLinks.TextLink link : links) {
            builder.addLink(link.getStart(), link.getEnd(),
                    ConvertUtils.createFloatMapFromTextLinks(link));
        }
        return builder.build();
    }

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @RequiresApi(28)
    @NonNull
    android.view.textclassifier.TextLinks toPlatform() {
        android.view.textclassifier.TextLinks.Builder builder =
                new android.view.textclassifier.TextLinks.Builder((String) getText());
        for (TextLink textLink : getLinks()) {
            builder.addLink(
                    textLink.getStart(),
                    textLink.getEnd(),
                    textLink.getEntityScores().getConfidenceMap());
        }
        return builder.build();
    }
}