Home | History | Annotate | Download | only in textclassifier
      1 /*
      2  * Copyright 2018 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.NonNull;
     20 import android.annotation.Nullable;
     21 import android.text.Spannable;
     22 import android.text.style.ClickableSpan;
     23 import android.text.util.Linkify;
     24 import android.text.util.Linkify.LinkifyMask;
     25 import android.view.textclassifier.TextLinks.TextLink;
     26 import android.view.textclassifier.TextLinks.TextLinkSpan;
     27 
     28 import com.android.internal.util.Preconditions;
     29 
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 import java.util.function.Function;
     33 
     34 /**
     35  * Parameters for generating and applying links.
     36  * @hide
     37  */
     38 public final class TextLinksParams {
     39 
     40     /**
     41      * A function to create spans from TextLinks.
     42      */
     43     private static final Function<TextLink, TextLinkSpan> DEFAULT_SPAN_FACTORY =
     44             textLink -> new TextLinkSpan(textLink);
     45 
     46     @TextLinks.ApplyStrategy
     47     private final int mApplyStrategy;
     48     private final Function<TextLink, TextLinkSpan> mSpanFactory;
     49     private final TextClassifier.EntityConfig mEntityConfig;
     50 
     51     private TextLinksParams(
     52             @TextLinks.ApplyStrategy int applyStrategy,
     53             Function<TextLink, TextLinkSpan> spanFactory) {
     54         mApplyStrategy = applyStrategy;
     55         mSpanFactory = spanFactory;
     56         mEntityConfig = TextClassifier.EntityConfig.createWithHints(null);
     57     }
     58 
     59     /**
     60      * Returns a new TextLinksParams object based on the specified link mask.
     61      *
     62      * @param mask the link mask
     63      *      e.g. {@link LinkifyMask#PHONE_NUMBERS} | {@link LinkifyMask#EMAIL_ADDRESSES}
     64      * @hide
     65      */
     66     @NonNull
     67     public static TextLinksParams fromLinkMask(@LinkifyMask int mask) {
     68         final List<String> entitiesToFind = new ArrayList<>();
     69         if ((mask & Linkify.WEB_URLS) != 0) {
     70             entitiesToFind.add(TextClassifier.TYPE_URL);
     71         }
     72         if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
     73             entitiesToFind.add(TextClassifier.TYPE_EMAIL);
     74         }
     75         if ((mask & Linkify.PHONE_NUMBERS) != 0) {
     76             entitiesToFind.add(TextClassifier.TYPE_PHONE);
     77         }
     78         if ((mask & Linkify.MAP_ADDRESSES) != 0) {
     79             entitiesToFind.add(TextClassifier.TYPE_ADDRESS);
     80         }
     81         return new TextLinksParams.Builder().setEntityConfig(
     82                 TextClassifier.EntityConfig.createWithExplicitEntityList(entitiesToFind))
     83                 .build();
     84     }
     85 
     86     /**
     87      * Returns the entity config used to determine what entity types to generate.
     88      */
     89     @NonNull
     90     public TextClassifier.EntityConfig getEntityConfig() {
     91         return mEntityConfig;
     92     }
     93 
     94     /**
     95      * Annotates the given text with the generated links. It will fail if the provided text doesn't
     96      * match the original text used to crete the TextLinks.
     97      *
     98      * @param text the text to apply the links to. Must match the original text
     99      * @param textLinks the links to apply to the text
    100      *
    101      * @return a status code indicating whether or not the links were successfully applied
    102      * @hide
    103      */
    104     @TextLinks.Status
    105     public int apply(@NonNull Spannable text, @NonNull TextLinks textLinks) {
    106         Preconditions.checkNotNull(text);
    107         Preconditions.checkNotNull(textLinks);
    108 
    109         final String textString = text.toString();
    110         if (!textString.startsWith(textLinks.getText())) {
    111             return TextLinks.STATUS_DIFFERENT_TEXT;
    112         }
    113         if (textLinks.getLinks().isEmpty()) {
    114             return TextLinks.STATUS_NO_LINKS_FOUND;
    115         }
    116 
    117         int applyCount = 0;
    118         for (TextLink link : textLinks.getLinks()) {
    119             final TextLinkSpan span = mSpanFactory.apply(link);
    120             if (span != null) {
    121                 final ClickableSpan[] existingSpans = text.getSpans(
    122                         link.getStart(), link.getEnd(), ClickableSpan.class);
    123                 if (existingSpans.length > 0) {
    124                     if (mApplyStrategy == TextLinks.APPLY_STRATEGY_REPLACE) {
    125                         for (ClickableSpan existingSpan : existingSpans) {
    126                             text.removeSpan(existingSpan);
    127                         }
    128                         text.setSpan(span, link.getStart(), link.getEnd(),
    129                                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    130                         applyCount++;
    131                     }
    132                 } else {
    133                     text.setSpan(span, link.getStart(), link.getEnd(),
    134                             Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    135                     applyCount++;
    136                 }
    137             }
    138         }
    139         if (applyCount == 0) {
    140             return TextLinks.STATUS_NO_LINKS_APPLIED;
    141         }
    142         return TextLinks.STATUS_LINKS_APPLIED;
    143     }
    144 
    145     /**
    146      * A builder for building TextLinksParams.
    147      */
    148     public static final class Builder {
    149 
    150         @TextLinks.ApplyStrategy
    151         private int mApplyStrategy = TextLinks.APPLY_STRATEGY_IGNORE;
    152         private Function<TextLink, TextLinkSpan> mSpanFactory = DEFAULT_SPAN_FACTORY;
    153 
    154         /**
    155          * Sets the apply strategy used to determine how to apply links to text.
    156          *      e.g {@link TextLinks#APPLY_STRATEGY_IGNORE}
    157          *
    158          * @return this builder
    159          */
    160         public Builder setApplyStrategy(@TextLinks.ApplyStrategy int applyStrategy) {
    161             mApplyStrategy = checkApplyStrategy(applyStrategy);
    162             return this;
    163         }
    164 
    165         /**
    166          * Sets a custom span factory for converting TextLinks to TextLinkSpans.
    167          * Set to {@code null} to use the default span factory.
    168          *
    169          * @return this builder
    170          */
    171         public Builder setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
    172             mSpanFactory = spanFactory == null ? DEFAULT_SPAN_FACTORY : spanFactory;
    173             return this;
    174         }
    175 
    176         /**
    177          * Sets the entity configuration used to determine what entity types to generate.
    178          * Set to {@code null} for the default entity config which will automatically determine
    179          * what links to generate.
    180          *
    181          * @return this builder
    182          */
    183         public Builder setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
    184             return this;
    185         }
    186 
    187         /**
    188          * Builds and returns a TextLinksParams object.
    189          */
    190         public TextLinksParams build() {
    191             return new TextLinksParams(mApplyStrategy, mSpanFactory);
    192         }
    193     }
    194 
    195     /** @throws IllegalArgumentException if the value is invalid */
    196     @TextLinks.ApplyStrategy
    197     private static int checkApplyStrategy(int applyStrategy) {
    198         if (applyStrategy != TextLinks.APPLY_STRATEGY_IGNORE
    199                 && applyStrategy != TextLinks.APPLY_STRATEGY_REPLACE) {
    200             throw new IllegalArgumentException(
    201                     "Invalid apply strategy. See TextLinksParams.ApplyStrategy for options.");
    202         }
    203         return applyStrategy;
    204     }
    205 }
    206 
    207