Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0172104c authored by Abodunrinwa Toki's avatar Abodunrinwa Toki Committed by Android (Google) Code Review
Browse files

Merge "Smart Linkify API"

parents cb6854eb fe20cdd9
Loading
Loading
Loading
Loading
+24 −3
Original line number Diff line number Diff line
@@ -44342,6 +44342,11 @@ package android.text.util {
    method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String);
    method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
    method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
    method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.widget.TextView, android.view.textclassifier.TextLinks.Options);
    method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.widget.TextView, android.view.textclassifier.TextLinks.Options, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>);
    method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, android.view.textclassifier.TextLinks.Options);
    method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, int);
    method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, android.view.textclassifier.TextLinks.Options, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>);
    field public static final int ALL = 15; // 0xf
    field public static final int EMAIL_ADDRESSES = 2; // 0x2
    field public static final int MAP_ADDRESSES = 8; // 0x8
@@ -50125,32 +50130,42 @@ package android.view.textclassifier {
  }
  public final class TextLinks implements android.os.Parcelable {
    method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>);
    method public int describeContents();
    method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final int APPLY_STRATEGY_IGNORE = 0; // 0x0
    field public static final int APPLY_STRATEGY_REPLACE = 1; // 0x1
    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks> CREATOR;
    field public static final int STATUS_DIFFERENT_TEXT = 3; // 0x3
    field public static final int STATUS_LINKS_APPLIED = 0; // 0x0
    field public static final int STATUS_NO_LINKS_APPLIED = 2; // 0x2
    field public static final int STATUS_NO_LINKS_FOUND = 1; // 0x1
  }
  public static final class TextLinks.Builder {
    ctor public TextLinks.Builder(java.lang.String);
    method public android.view.textclassifier.TextLinks.Builder addLink(android.view.textclassifier.TextLinks.TextLink);
    method public android.view.textclassifier.TextLinks.Builder addLink(int, int, java.util.Map<java.lang.String, java.lang.Float>);
    method public android.view.textclassifier.TextLinks build();
    method public android.view.textclassifier.TextLinks.Builder clearTextLinks();
  }
  public static final class TextLinks.Options implements android.os.Parcelable {
    ctor public TextLinks.Options();
    method public int describeContents();
    method public static android.view.textclassifier.TextLinks.Options fromLinkMask(int);
    method public int getApplyStrategy();
    method public android.os.LocaleList getDefaultLocales();
    method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig();
    method public java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.view.textclassifier.TextLinks.TextLinkSpan> getSpanFactory();
    method public android.view.textclassifier.TextLinks.Options setApplyStrategy(int);
    method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList);
    method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig);
    method public android.view.textclassifier.TextLinks.Options setSpanFactory(java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.view.textclassifier.TextLinks.TextLinkSpan>);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.Options> CREATOR;
  }
  public static final class TextLinks.TextLink implements android.os.Parcelable {
    ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map<java.lang.String, java.lang.Float>);
    method public int describeContents();
    method public float getConfidenceScore(java.lang.String);
    method public int getEnd();
@@ -50161,6 +50176,12 @@ package android.view.textclassifier {
    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.TextLink> CREATOR;
  }
  public static class TextLinks.TextLinkSpan extends android.text.style.ClickableSpan {
    ctor public TextLinks.TextLinkSpan(android.view.textclassifier.TextLinks.TextLink);
    method public final android.view.textclassifier.TextLinks.TextLink getTextLink();
    method public void onClick(android.view.View);
  }
  public final class TextSelection implements android.os.Parcelable {
    method public int describeContents();
    method public float getConfidenceScore(java.lang.String);
+200 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.text.util;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.Context;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
@@ -29,12 +30,16 @@ import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.URLSpan;
import android.util.Patterns;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
import android.view.textclassifier.TextLinks.TextLinkSpan;
import android.webkit.WebView;
import android.widget.TextView;

import com.android.i18n.phonenumbers.PhoneNumberMatch;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;
import com.android.internal.util.Preconditions;

import libcore.util.EmptyArray;

@@ -46,6 +51,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@@ -479,6 +490,195 @@ public class Linkify {
        return hasMatches;
    }

    /**
     * Scans the text of the provided TextView and turns all occurrences of the entity types
     * specified by {@code options} into clickable links. If links are found, this method
     * removes any pre-existing {@link TextLinkSpan} attached to the text (to avoid
     * problems if you call it repeatedly on the same text) and sets the movement method for the
     * TextView to LinkMovementMethod.
     *
     * <p><strong>Note:</strong> This method returns immediately but generates the links with
     * the specified classifier on a background thread. The generated links are applied on the
     * calling thread.
     *
     * @param textView TextView whose text is to be marked-up with links
     * @param options optional parameters to specify how to generate the links
     *
     * @return a future that may be used to interrupt or query the background task
     */
    @UiThread
    public static Future<Void> addLinksAsync(
            @NonNull TextView textView,
            @Nullable TextLinks.Options options) {
        return addLinksAsync(textView, options, null /* executor */, null /* callback */);
    }

    /**
     * Scans the text of the provided TextView and turns all occurrences of the entity types
     * specified by {@code options} into clickable links. If links are found, this method
     * removes any pre-existing {@link TextLinkSpan} attached to the text (to avoid
     * problems if you call it repeatedly on the same text) and sets the movement method for the
     * TextView to LinkMovementMethod.
     *
     * <p><strong>Note:</strong> This method returns immediately but generates the links with
     * the specified classifier on a background thread. The generated links are applied on the
     * calling thread.
     *
     * @param textView TextView whose text is to be marked-up with links
     * @param options optional parameters to specify how to generate the links
     * @param executor Executor that runs the background task
     * @param callback Callback that receives the final status of the background task execution
     *
     * @return a future that may be used to interrupt or query the background task
     */
    @UiThread
    public static Future<Void> addLinksAsync(
            @NonNull TextView textView,
            @Nullable TextLinks.Options options,
            @Nullable Executor executor,
            @Nullable Consumer<Integer> callback) {
        Preconditions.checkNotNull(textView);
        final CharSequence text = textView.getText();
        final Spannable spannable = (text instanceof Spannable)
                ? (Spannable) text : SpannableString.valueOf(text);
        final Runnable modifyTextView = () -> {
            addLinkMovementMethod(textView);
            if (spannable != text) {
                textView.setText(spannable);
            }
        };
        return addLinksAsync(spannable, textView.getTextClassifier(),
                options, executor, callback, modifyTextView);
    }

    /**
     * Scans the text of the provided TextView and turns all occurrences of the entity types
     * specified by {@code options} into clickable links. If links are found, this method
     * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid
     * problems if you call it repeatedly on the same text.
     *
     * <p><strong>Note:</strong> This method returns immediately but generates the links with
     * the specified classifier on a background thread. The generated links are applied on the
     * calling thread.
     *
     * <p><strong>Note:</strong> If the text is currently attached to a TextView, this method
     * should be called on the UI thread.
     *
     * @param text Spannable whose text is to be marked-up with links
     * @param classifier the TextClassifier to use to generate the links
     * @param options optional parameters to specify how to generate the links
     *
     * @return a future that may be used to interrupt or query the background task
     */
    public static Future<Void> addLinksAsync(
            @NonNull Spannable text,
            @NonNull TextClassifier classifier,
            @Nullable TextLinks.Options options) {
        return addLinksAsync(text, classifier, options, null /* executor */, null /* callback */);
    }

    /**
     * Scans the text of the provided TextView and turns all occurrences of the entity types
     * specified by the link {@code mask} into clickable links. If links are found, this method
     * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid
     * problems if you call it repeatedly on the same text.
     *
     * <p><strong>Note:</strong> This method returns immediately but generates the links with
     * the specified classifier on a background thread. The generated links are applied on the
     * calling thread.
     *
     * <p><strong>Note:</strong> If the text is currently attached to a TextView, this method
     * should be called on the UI thread.
     *
     * @param text Spannable whose text is to be marked-up with links
     * @param classifier the TextClassifier to use to generate the links
     * @param mask mask to define which kinds of links will be generated
     *
     * @return a future that may be used to interrupt or query the background task
     */
    public static Future<Void> addLinksAsync(
            @NonNull Spannable text,
            @NonNull TextClassifier classifier,
            @LinkifyMask int mask) {
        return addLinksAsync(text, classifier, TextLinks.Options.fromLinkMask(mask),
                null /* executor */, null /* callback */);
    }

    /**
     * Scans the text of the provided TextView and turns all occurrences of the entity types
     * specified by {@code options} into clickable links. If links are found, this method
     * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid
     * problems if you call it repeatedly on the same text.
     *
     * <p><strong>Note:</strong> This method returns immediately but generates the links with
     * the specified classifier on a background thread. The generated links are applied on the
     * calling thread.
     *
     * <p><strong>Note:</strong> If the text is currently attached to a TextView, this method
     * should be called on the UI thread.
     *
     * @param text Spannable whose text is to be marked-up with links
     * @param classifier the TextClassifier to use to generate the links
     * @param options optional parameters to specify how to generate the links
     * @param executor Executor that runs the background task
     * @param callback Callback that receives the final status of the background task execution
     *
     * @return a future that may be used to interrupt or query the background task
     */
    public static Future<Void> addLinksAsync(
            @NonNull Spannable text,
            @NonNull TextClassifier classifier,
            @Nullable TextLinks.Options options,
            @Nullable Executor executor,
            @Nullable Consumer<Integer> callback) {
        return addLinksAsync(text, classifier, options, executor, callback,
                null /* modifyTextView */);
    }

    private static Future<Void> addLinksAsync(
            @NonNull Spannable text,
            @NonNull TextClassifier classifier,
            @Nullable TextLinks.Options options,
            @Nullable Executor executor,
            @Nullable Consumer<Integer> callback,
            @Nullable Runnable modifyTextView) {
        Preconditions.checkNotNull(text);
        Preconditions.checkNotNull(classifier);
        final Supplier<TextLinks> supplier = () -> classifier.generateLinks(text, options);
        final Consumer<TextLinks> consumer = links -> {
            if (links.getLinks().isEmpty()) {
                if (callback != null) {
                    callback.accept(TextLinks.STATUS_NO_LINKS_FOUND);
                }
                return;
            }

            final TextLinkSpan[] old = text.getSpans(0, text.length(), TextLinkSpan.class);
            for (int i = old.length - 1; i >= 0; i--) {
                text.removeSpan(old[i]);
            }

            final Function<TextLinks.TextLink, TextLinkSpan> spanFactory = (options == null)
                    ? null : options.getSpanFactory();
            final @TextLinks.ApplyStrategy int applyStrategy = (options == null)
                    ? TextLinks.APPLY_STRATEGY_IGNORE : options.getApplyStrategy();
            final @TextLinks.Status int result =  links.apply(text, applyStrategy, spanFactory);
            if (result == TextLinks.STATUS_LINKS_APPLIED) {
                if (modifyTextView != null) {
                    modifyTextView.run();
                }
            }
            if (callback != null) {
                callback.accept(result);
            }
        };
        if (executor == null) {
            return CompletableFuture.supplyAsync(supplier).thenAccept(consumer);
        } else {
            return CompletableFuture.supplyAsync(supplier, executor).thenAccept(consumer);
        }
    }

    private static final void applyLink(String url, int start, int end, Spannable text) {
        URLSpan span = new URLSpan(url);

+1 −2
Original line number Diff line number Diff line
@@ -230,8 +230,7 @@ public final class TextClassifierImpl implements TextClassifier {
                for (int i = 0; i < results.length; i++) {
                    entityScores.put(results[i].mCollection, results[i].mScore);
                }
                builder.addLink(new TextLinks.TextLink(
                        textString, span.getStartIndex(), span.getEndIndex(), entityScores));
                builder.addLink(span.getStartIndex(), span.getEndIndex(), entityScores);
            }
        } catch (Throwable t) {
            // Avoid throwing from this method. Log the error.
+218 −42

File changed.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -68,8 +68,8 @@ public class TextLinksTest {
    public void testParcel() {
        final String fullText = "this is just a test";
        final TextLinks reference = new TextLinks.Builder(fullText)
                .addLink(new TextLinks.TextLink(fullText, 0, 4, getEntityScores(0.f, 0.f, 1.f)))
                .addLink(new TextLinks.TextLink(fullText, 5, 12, getEntityScores(.8f, .1f, .5f)))
                .addLink(0, 4, getEntityScores(0.f, 0.f, 1.f))
                .addLink(5, 12, getEntityScores(.8f, .1f, .5f))
                .build();

        // Parcel and unparcel.
Loading