diff options
Diffstat (limited to 'core/java/android')
3 files changed, 295 insertions, 2 deletions
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 07455c1d3297..e35bb920deab 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -154,6 +154,21 @@ public interface TextClassifier { } /** + * Returns a {@link TextLinks} that may be applied to the text to annotate it with links + * information. + * + * @param text the text to generate annotations for + * @param options configuration for link generation. If null, defaults will be used. + * + * @throws IllegalArgumentException if text is null + */ + @WorkerThread + default TextLinks generateLinks( + @NonNull CharSequence text, @Nullable TextLinks.Options options) { + return new TextLinks.Builder(text.toString()).build(); + } + + /** * Logs a TextClassifier event. * * @param source the text classifier used to generate this event diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 2799f2bfda8a..9c4fd57be707 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -209,6 +209,32 @@ final class TextClassifierImpl implements TextClassifier { } @Override + public TextLinks generateLinks( + @NonNull CharSequence text, @Nullable TextLinks.Options options) { + Preconditions.checkNotNull(text); + final String textString = text.toString(); + final TextLinks.Builder builder = new TextLinks.Builder(textString); + try { + LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; + final SmartSelection smartSelection = getSmartSelection(defaultLocales); + final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString); + for (SmartSelection.AnnotatedSpan span : annotations) { + final Map<String, Float> entityScores = new HashMap<>(); + final SmartSelection.ClassificationResult[] results = span.getClassification(); + 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)); + } + } catch (Throwable t) { + // Avoid throwing from this method. Log the error. + Log.e(LOG_TAG, "Error getting links info.", t); + } + return builder.build(); + } + + @Override public void logEvent(String source, String event) { if (LOG_TAG.equals(source)) { mMetricsLogger.count(event, 1); @@ -680,8 +706,8 @@ final class TextClassifierImpl implements TextClassifier { intents.add(new Intent(Intent.ACTION_SENDTO) .setData(Uri.parse(String.format("mailto:%s", text)))); intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT) - .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) - .putExtra(ContactsContract.Intents.Insert.EMAIL, text)); + .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) + .putExtra(ContactsContract.Intents.Insert.EMAIL, text)); break; case TextClassifier.TYPE_PHONE: intents.add(new Intent(Intent.ACTION_DIAL) diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java new file mode 100644 index 000000000000..f3cc827f8889 --- /dev/null +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -0,0 +1,252 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; +import android.os.LocaleList; +import android.text.SpannableString; +import android.text.style.ClickableSpan; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +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 { + private final String mFullText; + private final List<TextLink> mLinks; + + private TextLinks(String fullText, Collection<TextLink> links) { + mFullText = fullText; + mLinks = Collections.unmodifiableList(new ArrayList<>(links)); + } + + /** + * Returns an unmodifiable Collection of the links. + */ + public Collection<TextLink> getLinks() { + return mLinks; + } + + /** + * 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 spanFactory a factory to generate spans from TextLinks. Will use a default if null. + * + * @return Success or failure. + */ + public boolean apply( + @NonNull SpannableString text, + @Nullable Function<TextLink, ClickableSpan> spanFactory) { + Preconditions.checkNotNull(text); + if (!mFullText.equals(text.toString())) { + return false; + } + + if (spanFactory == null) { + spanFactory = DEFAULT_SPAN_FACTORY; + } + for (TextLink link : mLinks) { + final ClickableSpan span = spanFactory.apply(link); + if (span != null) { + text.setSpan(span, link.getStart(), link.getEnd(), 0); + } + } + return true; + } + + /** + * A link, identifying a substring of text and possible entity types for it. + */ + public static final class TextLink { + private final EntityConfidence<String> mEntityScores; + private final String mOriginalText; + private final int mStart; + private final int mEnd; + + /** + * Create a new TextLink. + * + * @throws IllegalArgumentException if entityScores is null or empty. + */ + public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) { + Preconditions.checkNotNull(originalText); + Preconditions.checkNotNull(entityScores); + Preconditions.checkArgument(!entityScores.isEmpty()); + Preconditions.checkArgument(start <= end); + mOriginalText = originalText; + mStart = start; + mEnd = end; + mEntityScores = new EntityConfidence<>(); + + for (Map.Entry<String, Float> entry : entityScores.entrySet()) { + mEntityScores.setEntityType(entry.getKey(), entry.getValue()); + } + } + + /** + * 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 @TextClassifier.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( + @TextClassifier.EntityType String entityType) { + return mEntityScores.getConfidenceScore(entityType); + } + } + + /** + * Optional input parameters for generating TextLinks. + */ + public static final class Options { + private final LocaleList mLocaleList; + + private Options(LocaleList localeList) { + this.mLocaleList = localeList; + } + + /** + * Builder to construct Options. + */ + public static final class Builder { + private LocaleList mLocaleList; + + /** + * Sets the LocaleList to use. + * + * @return this Builder. + */ + public Builder setLocaleList(@Nullable LocaleList localeList) { + this.mLocaleList = localeList; + return this; + } + + /** + * Builds the Options object. + */ + public Options build() { + return new Options(mLocaleList); + } + } + public @Nullable LocaleList getDefaultLocales() { + return mLocaleList; + } + }; + + /** + * A function to create spans from TextLinks. + * + * Applies only to TextViews. + * We can hide this until we are convinced we want it to be part of the public API. + * + * @hide + */ + public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY = + new Function<TextLink, ClickableSpan>() { + @Override + public ClickableSpan apply(TextLink textLink) { + // TODO: Implement. + throw new UnsupportedOperationException("Not yet implemented"); + } + }; + + /** + * A builder to construct a TextLinks instance. + */ + public static final class Builder { + private final String mFullText; + private final Collection<TextLink> mLinks; + + /** + * Create a new TextLinks.Builder. + * + * @param fullText The full text that links will be added to. + */ + public Builder(@NonNull String fullText) { + mFullText = Preconditions.checkNotNull(fullText); + mLinks = new ArrayList<>(); + } + + /** + * Adds a TextLink. + * + * @return this instance. + */ + public Builder addLink(TextLink link) { + Preconditions.checkNotNull(link); + mLinks.add(link); + return this; + } + + /** + * Constructs a TextLinks instance. + * + * @return the constructed TextLinks. + */ + public TextLinks build() { + return new TextLinks(mFullText, mLinks); + } + } +} |
