All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.view.textclassifier.TextLinks Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright 2017 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 android.view.textclassifier;

import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Spannable;
import android.text.method.MovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.View;
import android.view.textclassifier.TextClassifier.EntityConfig;
import android.view.textclassifier.TextClassifier.EntityType;
import android.widget.TextView;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.ZonedDateTime;
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.Objects;
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.
 */
public final class TextLinks implements Parcelable {

    /**
     * Return status of an attempt to apply TextLinks to text.
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({STATUS_LINKS_APPLIED, STATUS_NO_LINKS_FOUND, STATUS_NO_LINKS_APPLIED,
            STATUS_DIFFERENT_TEXT, STATUS_UNSUPPORTED_CHARACTER})
    public @interface Status {}

    /** 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;

    /** The specified text contains unsupported characters. */
    public static final int STATUS_UNSUPPORTED_CHARACTER = 4;

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

    /**
     * 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;

    private final String mFullText;
    private final List mLinks;
    private final Bundle mExtras;

    private TextLinks(String fullText, ArrayList 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 getLinks() {
        return mLinks;
    }

    /**
     * Returns the extended data.
     *
     * 

NOTE: Do not modify this bundle. */ @NonNull public Bundle getExtras() { return mExtras; } /** * Annotates the given text with the generated links. It will fail if the provided text doesn't * match the original text used to create the TextLinks. * *

NOTE: It may be necessary to set a LinkMovementMethod on the TextView * widget to properly handle links. See {@link TextView#setMovementMethod(MovementMethod)} * * @param text the text to apply the links to. Must match the original text * @param applyStrategy the apply strategy used to determine how to apply links to text. * e.g {@link TextLinks#APPLY_STRATEGY_IGNORE} * @param spanFactory a custom span factory for converting TextLinks to TextLinkSpans. * Set to {@code null} to use the default span factory. * * @return a status code indicating whether or not the links were successfully applied * e.g. {@link #STATUS_LINKS_APPLIED} */ @Status public int apply( @NonNull Spannable text, @ApplyStrategy int applyStrategy, @Nullable Function spanFactory) { Objects.requireNonNull(text); return new TextLinksParams.Builder() .setApplyStrategy(applyStrategy) .setSpanFactory(spanFactory) .build() .apply(text, this); } @Override public String toString() { return String.format(Locale.US, "TextLinks{fullText=%s, links=%s}", mFullText, mLinks); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mFullText); dest.writeTypedList(mLinks); dest.writeBundle(mExtras); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public TextLinks createFromParcel(Parcel in) { return new TextLinks(in); } @Override public TextLinks[] newArray(int size) { return new TextLinks[size]; } }; private TextLinks(Parcel in) { mFullText = in.readString(); mLinks = in.createTypedArrayList(TextLink.CREATOR); mExtras = in.readBundle(); } /** * A link, identifying a substring of text and possible entity types for it. */ public static final class TextLink implements Parcelable { private final EntityConfidence mEntityScores; private final int mStart; private final int mEnd; private final Bundle mExtras; @Nullable private final URLSpan mUrlSpan; /** * Create a new TextLink. * * @param start The start index of the identified subsequence * @param end The end index of the identified subsequence * @param entityConfidence A mapping of entity type to confidence score * @param extras A bundle containing custom data related to this TextLink * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled * * @throws IllegalArgumentException if {@code entityConfidence} is null or empty * @throws IllegalArgumentException if {@code start} is greater than {@code end} */ private TextLink(int start, int end, @NonNull EntityConfidence entityConfidence, @NonNull Bundle extras, @Nullable URLSpan urlSpan) { Objects.requireNonNull(entityConfidence); Preconditions.checkArgument(!entityConfidence.getEntities().isEmpty()); Preconditions.checkArgument(start <= end); Objects.requireNonNull(extras); mStart = start; mEnd = end; mEntityScores = entityConfidence; mUrlSpan = urlSpan; mExtras = extras; } /** * 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 count */ public int getEntityCount() { 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 getEntity(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); } /** * Returns a bundle containing custom data related to this TextLink. */ @NonNull public Bundle getExtras() { return mExtras; } @Override public String toString() { return String.format(Locale.US, "TextLink{start=%s, end=%s, entityScores=%s, urlSpan=%s}", mStart, mEnd, mEntityScores, mUrlSpan); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { mEntityScores.writeToParcel(dest, flags); dest.writeInt(mStart); dest.writeInt(mEnd); dest.writeBundle(mExtras); } private static TextLink readFromParcel(Parcel in) { final EntityConfidence entityConfidence = EntityConfidence.CREATOR.createFromParcel(in); final int start = in.readInt(); final int end = in.readInt(); final Bundle extras = in.readBundle(); return new TextLink(start, end, entityConfidence, extras, null /* urlSpan */); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public TextLink createFromParcel(Parcel in) { return readFromParcel(in); } @Override public TextLink[] newArray(int size) { return new TextLink[size]; } }; } /** * A request object for generating TextLinks. */ public static final class Request implements Parcelable { private final CharSequence mText; @Nullable private final LocaleList mDefaultLocales; @Nullable private final EntityConfig mEntityConfig; private final boolean mLegacyFallback; private final Bundle mExtras; @Nullable private final ZonedDateTime mReferenceTime; @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; private Request( CharSequence text, LocaleList defaultLocales, EntityConfig entityConfig, boolean legacyFallback, ZonedDateTime referenceTime, Bundle extras) { mText = text; mDefaultLocales = defaultLocales; mEntityConfig = entityConfig; mLegacyFallback = legacyFallback; mReferenceTime = referenceTime; mExtras = extras; } /** * Returns the text to generate links for. */ @NonNull public CharSequence getText() { return mText; } /** * Returns an ordered list of locale preferences that can be used to disambiguate the * provided text. */ @Nullable public LocaleList getDefaultLocales() { return mDefaultLocales; } /** * Returns the config representing the set of entities to look for * * @see Builder#setEntityConfig(EntityConfig) */ @Nullable public EntityConfig getEntityConfig() { return mEntityConfig; } /** * Returns whether the TextClassifier can fallback to legacy links if smart linkify is * disabled. * Note: This is not parcelled. * @hide */ public boolean isLegacyFallback() { return mLegacyFallback; } /** * Returns reference time based on which relative dates (e.g. "tomorrow") should be * interpreted. */ @Nullable public ZonedDateTime getReferenceTime() { return mReferenceTime; } /** * Returns the name of the package that sent this request. * This returns {@code null} if no calling package name is set. */ @Nullable public String getCallingPackageName() { return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; } /** * Sets the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void setSystemTextClassifierMetadata( @Nullable SystemTextClassifierMetadata systemTcMetadata) { mSystemTcMetadata = systemTcMetadata; } /** * Returns the information about the {@link SystemTextClassifier} that sent this request. * * @hide */ @Nullable public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { return mSystemTcMetadata; } /** * Returns the extended data. * *

NOTE: Do not modify this bundle. */ @NonNull public Bundle getExtras() { return mExtras; } /** * A builder for building TextLinks requests. */ public static final class Builder { private final CharSequence mText; @Nullable private LocaleList mDefaultLocales; @Nullable private EntityConfig mEntityConfig; private boolean mLegacyFallback = true; // Use legacy fall back by default. @Nullable private Bundle mExtras; @Nullable private ZonedDateTime mReferenceTime; public Builder(@NonNull CharSequence text) { mText = Objects.requireNonNull(text); } /** * Sets ordered list of locale preferences that may be used to disambiguate the * provided 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 LocaleList 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 teh TextClassifier will * automatically determine what links to generate. * * @return this builder */ @NonNull public Builder setEntityConfig(@Nullable EntityConfig entityConfig) { mEntityConfig = entityConfig; return this; } /** * Sets whether the TextClassifier can fallback to legacy links if smart linkify is * disabled. * *

Note: This is not parcelled. * * @return this builder * @hide */ @NonNull public Builder setLegacyFallback(boolean legacyFallback) { mLegacyFallback = legacyFallback; return this; } /** * Sets the extended data. * * @return this builder */ public Builder setExtras(@Nullable Bundle extras) { mExtras = extras; return this; } /** * Sets the reference time based on which relative dates (e.g. * "tomorrow") should be interpreted. * * @param referenceTime reference time based on which relative dates. This should * usually be the time when the text was originally composed. * * @return this builder */ @NonNull public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { mReferenceTime = referenceTime; return this; } /** * Builds and returns the request object. */ @NonNull public Request build() { return new Request( mText, mDefaultLocales, mEntityConfig, mLegacyFallback, mReferenceTime, mExtras == null ? Bundle.EMPTY : mExtras); } } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mText.toString()); dest.writeParcelable(mDefaultLocales, flags); dest.writeParcelable(mEntityConfig, flags); dest.writeBundle(mExtras); dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); dest.writeParcelable(mSystemTcMetadata, flags); } private static Request readFromParcel(Parcel in) { final String text = in.readString(); final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class); final EntityConfig entityConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class); final Bundle extras = in.readBundle(); final String referenceTimeString = in.readString(); final ZonedDateTime referenceTime = referenceTimeString == null ? null : ZonedDateTime.parse(referenceTimeString); final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); final Request request = new Request(text, defaultLocales, entityConfig, /* legacyFallback= */ true, referenceTime, extras); request.setSystemTextClassifierMetadata(systemTcMetadata); return request; } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public Request createFromParcel(Parcel in) { return readFromParcel(in); } @Override public Request[] newArray(int size) { return new Request[size]; } }; } /** * A ClickableSpan for a TextLink. * *

Applies only to TextViews. */ public static class TextLinkSpan extends ClickableSpan { /** * How the clickspan is triggered. * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({INVOCATION_METHOD_UNSPECIFIED, INVOCATION_METHOD_TOUCH, INVOCATION_METHOD_KEYBOARD}) public @interface InvocationMethod {} /** @hide */ public static final int INVOCATION_METHOD_UNSPECIFIED = -1; /** @hide */ public static final int INVOCATION_METHOD_TOUCH = 0; /** @hide */ public static final int INVOCATION_METHOD_KEYBOARD = 1; private final TextLink mTextLink; public TextLinkSpan(@NonNull TextLink textLink) { mTextLink = textLink; } @Override public void onClick(View widget) { onClick(widget, INVOCATION_METHOD_UNSPECIFIED); } /** @hide */ public final void onClick(View widget, @InvocationMethod int invocationMethod) { if (widget instanceof TextView) { final TextView textView = (TextView) widget; final Context context = textView.getContext(); if (TextClassificationManager.getSettings(context).isSmartLinkifyEnabled()) { switch (invocationMethod) { case INVOCATION_METHOD_TOUCH: textView.requestActionMode(this); break; case INVOCATION_METHOD_KEYBOARD:// fall though case INVOCATION_METHOD_UNSPECIFIED: // fall through default: textView.handleClick(this); break; } } else { if (mTextLink.mUrlSpan != null) { mTextLink.mUrlSpan.onClick(textView); } else { textView.handleClick(this); } } } } public final TextLink getTextLink() { return mTextLink; } /** @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) @Nullable public final String getUrl() { if (mTextLink.mUrlSpan != null) { return mTextLink.mUrlSpan.getURL(); } return null; } } /** * A builder to construct a TextLinks instance. */ public static final class Builder { private final String mFullText; private final ArrayList mLinks; private Bundle mExtras; /** * Create a new TextLinks.Builder. * * @param fullText The full text to annotate with links */ public Builder(@NonNull String fullText) { mFullText = Objects.requireNonNull(fullText); mLinks = new ArrayList<>(); } /** * Adds a TextLink. * * @param start The start index of the identified subsequence * @param end The end index of the identified subsequence * @param entityScores A mapping of entity type to confidence score * * @throws IllegalArgumentException if entityScores is null or empty. */ @NonNull public Builder addLink(int start, int end, @NonNull Map entityScores) { return addLink(start, end, entityScores, Bundle.EMPTY, null); } /** * Adds a TextLink. * * @see #addLink(int, int, Map) * @param extras An optional bundle containing custom data related to this TextLink */ @NonNull public Builder addLink(int start, int end, @NonNull Map entityScores, @NonNull Bundle extras) { return addLink(start, end, entityScores, extras, null); } /** * Adds a TextLink. * * @see #addLink(int, int, Map) * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled. */ @NonNull Builder addLink(int start, int end, @NonNull Map entityScores, @Nullable URLSpan urlSpan) { return addLink(start, end, entityScores, Bundle.EMPTY, urlSpan); } private Builder addLink(int start, int end, @NonNull Map entityScores, @NonNull Bundle extras, @Nullable URLSpan urlSpan) { mLinks.add(new TextLink( start, end, new EntityConfidence(entityScores), extras, urlSpan)); return this; } /** * Removes all {@link TextLink}s. */ @NonNull public Builder clearTextLinks() { mLinks.clear(); return this; } /** * Sets the extended data. * * @return this builder */ @NonNull public Builder setExtras(@Nullable Bundle extras) { mExtras = extras; return this; } /** * Constructs a TextLinks instance. * * @return the constructed TextLinks */ @NonNull public TextLinks build() { return new TextLinks(mFullText, mLinks, mExtras == null ? Bundle.EMPTY : mExtras); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy