| /* |
| * 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 android.view.textclassifier; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.text.Spannable; |
| import android.text.style.ClickableSpan; |
| import android.text.util.Linkify; |
| import android.text.util.Linkify.LinkifyMask; |
| import android.view.textclassifier.TextLinks.TextLink; |
| import android.view.textclassifier.TextLinks.TextLinkSpan; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.function.Function; |
| |
| /** |
| * Parameters for generating and applying links. |
| * @hide |
| */ |
| public final class TextLinksParams { |
| |
| /** |
| * A function to create spans from TextLinks. |
| */ |
| private static final Function<TextLink, TextLinkSpan> DEFAULT_SPAN_FACTORY = |
| textLink -> new TextLinkSpan(textLink); |
| |
| @TextLinks.ApplyStrategy |
| private final int mApplyStrategy; |
| private final Function<TextLink, TextLinkSpan> mSpanFactory; |
| private final TextClassifier.EntityConfig mEntityConfig; |
| |
| private TextLinksParams( |
| @TextLinks.ApplyStrategy int applyStrategy, |
| Function<TextLink, TextLinkSpan> spanFactory) { |
| mApplyStrategy = applyStrategy; |
| mSpanFactory = spanFactory; |
| mEntityConfig = TextClassifier.EntityConfig.createWithHints(null); |
| } |
| |
| /** |
| * Returns a new TextLinksParams object based on the specified link mask. |
| * |
| * @param mask the link mask |
| * e.g. {@link LinkifyMask#PHONE_NUMBERS} | {@link LinkifyMask#EMAIL_ADDRESSES} |
| * @hide |
| */ |
| @NonNull |
| public static TextLinksParams fromLinkMask(@LinkifyMask int mask) { |
| final List<String> entitiesToFind = new ArrayList<>(); |
| if ((mask & Linkify.WEB_URLS) != 0) { |
| entitiesToFind.add(TextClassifier.TYPE_URL); |
| } |
| if ((mask & Linkify.EMAIL_ADDRESSES) != 0) { |
| entitiesToFind.add(TextClassifier.TYPE_EMAIL); |
| } |
| if ((mask & Linkify.PHONE_NUMBERS) != 0) { |
| entitiesToFind.add(TextClassifier.TYPE_PHONE); |
| } |
| if ((mask & Linkify.MAP_ADDRESSES) != 0) { |
| entitiesToFind.add(TextClassifier.TYPE_ADDRESS); |
| } |
| return new TextLinksParams.Builder().setEntityConfig( |
| TextClassifier.EntityConfig.createWithExplicitEntityList(entitiesToFind)) |
| .build(); |
| } |
| |
| /** |
| * Returns the entity config used to determine what entity types to generate. |
| */ |
| @NonNull |
| public TextClassifier.EntityConfig getEntityConfig() { |
| return mEntityConfig; |
| } |
| |
| /** |
| * Annotates the given text with the generated links. It will fail if the provided text doesn't |
| * match the original text used to crete the TextLinks. |
| * |
| * @param text the text to apply the links to. Must match the original text |
| * @param textLinks the links to apply to the text |
| * |
| * @return a status code indicating whether or not the links were successfully applied |
| * @hide |
| */ |
| @TextLinks.Status |
| public int apply(@NonNull Spannable text, @NonNull TextLinks textLinks) { |
| Preconditions.checkNotNull(text); |
| Preconditions.checkNotNull(textLinks); |
| |
| final String textString = text.toString(); |
| |
| if (Linkify.containsUnsupportedCharacters(textString)) { |
| // Do not apply links to text containing unsupported characters. |
| android.util.EventLog.writeEvent(0x534e4554, "116321860", -1, ""); |
| return TextLinks.STATUS_NO_LINKS_APPLIED; |
| } |
| |
| if (!textString.startsWith(textLinks.getText())) { |
| return TextLinks.STATUS_DIFFERENT_TEXT; |
| } |
| if (textLinks.getLinks().isEmpty()) { |
| return TextLinks.STATUS_NO_LINKS_FOUND; |
| } |
| |
| int applyCount = 0; |
| for (TextLink link : textLinks.getLinks()) { |
| final TextLinkSpan span = mSpanFactory.apply(link); |
| if (span != null) { |
| final ClickableSpan[] existingSpans = text.getSpans( |
| link.getStart(), link.getEnd(), ClickableSpan.class); |
| if (existingSpans.length > 0) { |
| if (mApplyStrategy == TextLinks.APPLY_STRATEGY_REPLACE) { |
| for (ClickableSpan existingSpan : existingSpans) { |
| text.removeSpan(existingSpan); |
| } |
| text.setSpan(span, link.getStart(), link.getEnd(), |
| Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| applyCount++; |
| } |
| } else { |
| text.setSpan(span, link.getStart(), link.getEnd(), |
| Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| applyCount++; |
| } |
| } |
| } |
| if (applyCount == 0) { |
| return TextLinks.STATUS_NO_LINKS_APPLIED; |
| } |
| return TextLinks.STATUS_LINKS_APPLIED; |
| } |
| |
| /** |
| * A builder for building TextLinksParams. |
| */ |
| public static final class Builder { |
| |
| @TextLinks.ApplyStrategy |
| private int mApplyStrategy = TextLinks.APPLY_STRATEGY_IGNORE; |
| private Function<TextLink, TextLinkSpan> mSpanFactory = DEFAULT_SPAN_FACTORY; |
| |
| /** |
| * Sets the apply strategy used to determine how to apply links to text. |
| * e.g {@link TextLinks#APPLY_STRATEGY_IGNORE} |
| * |
| * @return this builder |
| */ |
| public Builder setApplyStrategy(@TextLinks.ApplyStrategy int applyStrategy) { |
| mApplyStrategy = checkApplyStrategy(applyStrategy); |
| return this; |
| } |
| |
| /** |
| * Sets a custom span factory for converting TextLinks to TextLinkSpans. |
| * Set to {@code null} to use the default span factory. |
| * |
| * @return this builder |
| */ |
| public Builder setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) { |
| mSpanFactory = spanFactory == null ? DEFAULT_SPAN_FACTORY : spanFactory; |
| return this; |
| } |
| |
| /** |
| * Sets the entity configuration used to determine what entity types to generate. |
| * Set to {@code null} for the default entity config which will automatically determine |
| * what links to generate. |
| * |
| * @return this builder |
| */ |
| public Builder setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) { |
| return this; |
| } |
| |
| /** |
| * Builds and returns a TextLinksParams object. |
| */ |
| public TextLinksParams build() { |
| return new TextLinksParams(mApplyStrategy, mSpanFactory); |
| } |
| } |
| |
| /** @throws IllegalArgumentException if the value is invalid */ |
| @TextLinks.ApplyStrategy |
| private static int checkApplyStrategy(int applyStrategy) { |
| if (applyStrategy != TextLinks.APPLY_STRATEGY_IGNORE |
| && applyStrategy != TextLinks.APPLY_STRATEGY_REPLACE) { |
| throw new IllegalArgumentException( |
| "Invalid apply strategy. See TextLinksParams.ApplyStrategy for options."); |
| } |
| return applyStrategy; |
| } |
| } |
| |