TextClassifier API updates.
1. Wraps TC queries in Request objects 2. Adds create/destroyTextClassificationSession system APIs 3. Adds the session Ids to system API calls 4. Change setSignature() to setId() on result objects 5. Plumbing to make the API updates work as things currently work 6. Hide Linkify.addLinksAsync APIs Bug: 74461129 Test: bit FrameworksCoreTests:android.view.textclassifier.TextClassificationManagerTest Test: bit CtsViewTestCases:android.view.textclassifier.cts.TextClassificationManagerTest Test: bit CtsWidgetTestCases:android.widget.cts.TextViewTest Test: bit FrameworksCoreTests:android.widget.TextViewActivityTest Test: bit FrameworksCoreTests:android.view.textclassifier.TextClassificationTest Test: bit FrameworksCoreTests:android.view.textclassifier.TextSelectionTest Test: bit FrameworksCoreTests:android.view.textclassifier.TextLinksTest Change-Id: I933ada8b37ef9893331a265e3b4fc08e043f1029
This commit is contained in:
@@ -21,30 +21,41 @@ import android.service.textclassifier.ITextLinksCallback;
|
||||
import android.service.textclassifier.ITextSelectionCallback;
|
||||
import android.view.textclassifier.SelectionEvent;
|
||||
import android.view.textclassifier.TextClassification;
|
||||
import android.view.textclassifier.TextClassificationContext;
|
||||
import android.view.textclassifier.TextClassificationSessionId;
|
||||
import android.view.textclassifier.TextLinks;
|
||||
import android.view.textclassifier.TextSelection;
|
||||
|
||||
/**
|
||||
* TextClassifierService binder interface.
|
||||
* See TextClassifier (and TextClassifier.Logger) for interface documentation.
|
||||
* See TextClassifier for interface documentation.
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface ITextClassifierService {
|
||||
|
||||
void onSuggestSelection(
|
||||
in CharSequence text, int selectionStartIndex, int selectionEndIndex,
|
||||
in TextSelection.Options options,
|
||||
in ITextSelectionCallback c);
|
||||
in TextClassificationSessionId sessionId,
|
||||
in TextSelection.Request request,
|
||||
in ITextSelectionCallback callback);
|
||||
|
||||
void onClassifyText(
|
||||
in CharSequence text, int startIndex, int endIndex,
|
||||
in TextClassification.Options options,
|
||||
in ITextClassificationCallback c);
|
||||
in TextClassificationSessionId sessionId,
|
||||
in TextClassification.Request request,
|
||||
in ITextClassificationCallback callback);
|
||||
|
||||
void onGenerateLinks(
|
||||
in CharSequence text,
|
||||
in TextLinks.Options options,
|
||||
in ITextLinksCallback c);
|
||||
in TextClassificationSessionId sessionId,
|
||||
in TextLinks.Request request,
|
||||
in ITextLinksCallback callback);
|
||||
|
||||
void onSelectionEvent(in SelectionEvent event);
|
||||
void onSelectionEvent(
|
||||
in TextClassificationSessionId sessionId,
|
||||
in SelectionEvent event);
|
||||
|
||||
void onCreateTextClassificationSession(
|
||||
in TextClassificationContext context,
|
||||
in TextClassificationSessionId sessionId);
|
||||
|
||||
void onDestroyTextClassificationSession(
|
||||
in TextClassificationSessionId sessionId);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package android.service.textclassifier;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
@@ -35,11 +34,15 @@ import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.textclassifier.SelectionEvent;
|
||||
import android.view.textclassifier.TextClassification;
|
||||
import android.view.textclassifier.TextClassificationContext;
|
||||
import android.view.textclassifier.TextClassificationManager;
|
||||
import android.view.textclassifier.TextClassificationSessionId;
|
||||
import android.view.textclassifier.TextClassifier;
|
||||
import android.view.textclassifier.TextLinks;
|
||||
import android.view.textclassifier.TextSelection;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Abstract base class for the TextClassifier service.
|
||||
*
|
||||
@@ -88,11 +91,13 @@ public abstract class TextClassifierService extends Service {
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void onSuggestSelection(
|
||||
CharSequence text, int selectionStartIndex, int selectionEndIndex,
|
||||
TextSelection.Options options, ITextSelectionCallback callback)
|
||||
TextClassificationSessionId sessionId,
|
||||
TextSelection.Request request, ITextSelectionCallback callback)
|
||||
throws RemoteException {
|
||||
Preconditions.checkNotNull(request);
|
||||
Preconditions.checkNotNull(callback);
|
||||
TextClassifierService.this.onSuggestSelection(
|
||||
text, selectionStartIndex, selectionEndIndex, options, mCancellationSignal,
|
||||
sessionId, request, mCancellationSignal,
|
||||
new Callback<TextSelection>() {
|
||||
@Override
|
||||
public void onSuccess(TextSelection result) {
|
||||
@@ -119,11 +124,13 @@ public abstract class TextClassifierService extends Service {
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void onClassifyText(
|
||||
CharSequence text, int startIndex, int endIndex,
|
||||
TextClassification.Options options, ITextClassificationCallback callback)
|
||||
TextClassificationSessionId sessionId,
|
||||
TextClassification.Request request, ITextClassificationCallback callback)
|
||||
throws RemoteException {
|
||||
Preconditions.checkNotNull(request);
|
||||
Preconditions.checkNotNull(callback);
|
||||
TextClassifierService.this.onClassifyText(
|
||||
text, startIndex, endIndex, options, mCancellationSignal,
|
||||
sessionId, request, mCancellationSignal,
|
||||
new Callback<TextClassification>() {
|
||||
@Override
|
||||
public void onSuccess(TextClassification result) {
|
||||
@@ -148,10 +155,13 @@ public abstract class TextClassifierService extends Service {
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void onGenerateLinks(
|
||||
CharSequence text, TextLinks.Options options, ITextLinksCallback callback)
|
||||
TextClassificationSessionId sessionId,
|
||||
TextLinks.Request request, ITextLinksCallback callback)
|
||||
throws RemoteException {
|
||||
Preconditions.checkNotNull(request);
|
||||
Preconditions.checkNotNull(callback);
|
||||
TextClassifierService.this.onGenerateLinks(
|
||||
text, options, mCancellationSignal,
|
||||
sessionId, request, mCancellationSignal,
|
||||
new Callback<TextLinks>() {
|
||||
@Override
|
||||
public void onSuccess(TextLinks result) {
|
||||
@@ -175,8 +185,28 @@ public abstract class TextClassifierService extends Service {
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void onSelectionEvent(SelectionEvent event) throws RemoteException {
|
||||
TextClassifierService.this.onSelectionEvent(event);
|
||||
public void onSelectionEvent(
|
||||
TextClassificationSessionId sessionId,
|
||||
SelectionEvent event) throws RemoteException {
|
||||
Preconditions.checkNotNull(event);
|
||||
TextClassifierService.this.onSelectionEvent(sessionId, event);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void onCreateTextClassificationSession(
|
||||
TextClassificationContext context, TextClassificationSessionId sessionId)
|
||||
throws RemoteException {
|
||||
Preconditions.checkNotNull(context);
|
||||
Preconditions.checkNotNull(sessionId);
|
||||
TextClassifierService.this.onCreateTextClassificationSession(context, sessionId);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId)
|
||||
throws RemoteException {
|
||||
TextClassifierService.this.onDestroyTextClassificationSession(sessionId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -193,19 +223,14 @@ public abstract class TextClassifierService extends Service {
|
||||
* Returns suggested text selection start and end indices, recognized entity types, and their
|
||||
* associated confidence scores. The entity types are ordered from highest to lowest scoring.
|
||||
*
|
||||
* @param text text providing context for the selected text (which is specified
|
||||
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||
* @param selectionStartIndex start index of the selected part of text
|
||||
* @param selectionEndIndex end index of the selected part of text
|
||||
* @param options optional input parameters
|
||||
* @param sessionId the session id
|
||||
* @param request the text selection request
|
||||
* @param cancellationSignal object to watch for canceling the current operation
|
||||
* @param callback the callback to return the result to
|
||||
*/
|
||||
public abstract void onSuggestSelection(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int selectionStartIndex,
|
||||
@IntRange(from = 0) int selectionEndIndex,
|
||||
@Nullable TextSelection.Options options,
|
||||
@Nullable TextClassificationSessionId sessionId,
|
||||
@NonNull TextSelection.Request request,
|
||||
@NonNull CancellationSignal cancellationSignal,
|
||||
@NonNull Callback<TextSelection> callback);
|
||||
|
||||
@@ -213,19 +238,14 @@ public abstract class TextClassifierService extends Service {
|
||||
* Classifies the specified text and returns a {@link TextClassification} object that can be
|
||||
* used to generate a widget for handling the classified text.
|
||||
*
|
||||
* @param text text providing context for the text to classify (which is specified
|
||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||
* @param startIndex start index of the text to classify
|
||||
* @param endIndex end index of the text to classify
|
||||
* @param options optional input parameters
|
||||
* @param sessionId the session id
|
||||
* @param request the text classification request
|
||||
* @param cancellationSignal object to watch for canceling the current operation
|
||||
* @param callback the callback to return the result to
|
||||
*/
|
||||
public abstract void onClassifyText(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int startIndex,
|
||||
@IntRange(from = 0) int endIndex,
|
||||
@Nullable TextClassification.Options options,
|
||||
@Nullable TextClassificationSessionId sessionId,
|
||||
@NonNull TextClassification.Request request,
|
||||
@NonNull CancellationSignal cancellationSignal,
|
||||
@NonNull Callback<TextClassification> callback);
|
||||
|
||||
@@ -233,14 +253,14 @@ public abstract class TextClassifierService extends Service {
|
||||
* Generates and 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
|
||||
* @param sessionId the session id
|
||||
* @param request the text classification request
|
||||
* @param cancellationSignal object to watch for canceling the current operation
|
||||
* @param callback the callback to return the result to
|
||||
*/
|
||||
public abstract void onGenerateLinks(
|
||||
@NonNull CharSequence text,
|
||||
@Nullable TextLinks.Options options,
|
||||
@Nullable TextClassificationSessionId sessionId,
|
||||
@NonNull TextLinks.Request request,
|
||||
@NonNull CancellationSignal cancellationSignal,
|
||||
@NonNull Callback<TextLinks> callback);
|
||||
|
||||
@@ -250,8 +270,30 @@ public abstract class TextClassifierService extends Service {
|
||||
* happened.
|
||||
*
|
||||
* <p>The default implementation ignores the event.
|
||||
*
|
||||
* @param sessionId the session id
|
||||
* @param event the selection event
|
||||
*/
|
||||
public void onSelectionEvent(@NonNull SelectionEvent event) {}
|
||||
public void onSelectionEvent(
|
||||
@Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}
|
||||
|
||||
/**
|
||||
* Creates a new text classification session for the specified context.
|
||||
*
|
||||
* @param context the text classification context
|
||||
* @param sessionId the session's Id
|
||||
*/
|
||||
public abstract void onCreateTextClassificationSession(
|
||||
@NonNull TextClassificationContext context,
|
||||
@NonNull TextClassificationSessionId sessionId);
|
||||
|
||||
/**
|
||||
* Destroys the text classification session identified by the specified sessionId.
|
||||
*
|
||||
* @param sessionId the id of the session to destroy
|
||||
*/
|
||||
public abstract void onDestroyTextClassificationSession(
|
||||
@NonNull TextClassificationSessionId sessionId);
|
||||
|
||||
/**
|
||||
* Returns a TextClassifier that runs in this service's process.
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.util.Patterns;
|
||||
import android.view.textclassifier.TextClassifier;
|
||||
import android.view.textclassifier.TextLinks;
|
||||
import android.view.textclassifier.TextLinks.TextLinkSpan;
|
||||
import android.view.textclassifier.TextLinksParams;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -55,7 +56,6 @@ 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;
|
||||
@@ -502,15 +502,16 @@ public class Linkify {
|
||||
* 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 params optional parameters to specify how to generate the links
|
||||
*
|
||||
* @return a future that may be used to interrupt or query the background task
|
||||
* @hide
|
||||
*/
|
||||
@UiThread
|
||||
public static Future<Void> addLinksAsync(
|
||||
@NonNull TextView textView,
|
||||
@Nullable TextLinks.Options options) {
|
||||
return addLinksAsync(textView, options, null /* executor */, null /* callback */);
|
||||
@Nullable TextLinksParams params) {
|
||||
return addLinksAsync(textView, params, null /* executor */, null /* callback */);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,16 +526,42 @@ public class Linkify {
|
||||
* 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
|
||||
* @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
|
||||
* @hide
|
||||
*/
|
||||
@UiThread
|
||||
public static Future<Void> addLinksAsync(
|
||||
@NonNull TextView textView,
|
||||
@Nullable TextLinks.Options options,
|
||||
@LinkifyMask int mask) {
|
||||
return addLinksAsync(textView, TextLinksParams.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) 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 params 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
|
||||
* @hide
|
||||
*/
|
||||
@UiThread
|
||||
public static Future<Void> addLinksAsync(
|
||||
@NonNull TextView textView,
|
||||
@Nullable TextLinksParams params,
|
||||
@Nullable Executor executor,
|
||||
@Nullable Consumer<Integer> callback) {
|
||||
Preconditions.checkNotNull(textView);
|
||||
@@ -548,7 +575,7 @@ public class Linkify {
|
||||
}
|
||||
};
|
||||
return addLinksAsync(spannable, textView.getTextClassifier(),
|
||||
options, executor, callback, modifyTextView);
|
||||
params, executor, callback, modifyTextView);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,15 +593,16 @@ public class Linkify {
|
||||
*
|
||||
* @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 params optional parameters to specify how to generate the links
|
||||
*
|
||||
* @return a future that may be used to interrupt or query the background task
|
||||
* @hide
|
||||
*/
|
||||
public static Future<Void> addLinksAsync(
|
||||
@NonNull Spannable text,
|
||||
@NonNull TextClassifier classifier,
|
||||
@Nullable TextLinks.Options options) {
|
||||
return addLinksAsync(text, classifier, options, null /* executor */, null /* callback */);
|
||||
@Nullable TextLinksParams params) {
|
||||
return addLinksAsync(text, classifier, params, null /* executor */, null /* callback */);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -595,12 +623,13 @@ public class Linkify {
|
||||
* @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
|
||||
* @hide
|
||||
*/
|
||||
public static Future<Void> addLinksAsync(
|
||||
@NonNull Spannable text,
|
||||
@NonNull TextClassifier classifier,
|
||||
@LinkifyMask int mask) {
|
||||
return addLinksAsync(text, classifier, TextLinks.Options.fromLinkMask(mask),
|
||||
return addLinksAsync(text, classifier, TextLinksParams.fromLinkMask(mask),
|
||||
null /* executor */, null /* callback */);
|
||||
}
|
||||
|
||||
@@ -619,39 +648,46 @@ public class Linkify {
|
||||
*
|
||||
* @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 params 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
|
||||
* @hide
|
||||
*/
|
||||
public static Future<Void> addLinksAsync(
|
||||
@NonNull Spannable text,
|
||||
@NonNull TextClassifier classifier,
|
||||
@Nullable TextLinks.Options options,
|
||||
@Nullable TextLinksParams params,
|
||||
@Nullable Executor executor,
|
||||
@Nullable Consumer<Integer> callback) {
|
||||
return addLinksAsync(text, classifier, options, executor, callback,
|
||||
return addLinksAsync(text, classifier, params, executor, callback,
|
||||
null /* modifyTextView */);
|
||||
}
|
||||
|
||||
private static Future<Void> addLinksAsync(
|
||||
@NonNull Spannable text,
|
||||
@NonNull TextClassifier classifier,
|
||||
@Nullable TextLinks.Options options,
|
||||
@Nullable TextLinksParams params,
|
||||
@Nullable Executor executor,
|
||||
@Nullable Consumer<Integer> callback,
|
||||
@Nullable Runnable modifyTextView) {
|
||||
Preconditions.checkNotNull(text);
|
||||
Preconditions.checkNotNull(classifier);
|
||||
|
||||
// TODO: This is a bug. We shouldnot call getMaxGenerateLinksTextLength() on the UI thread.
|
||||
// The input text may exceed the maximum length the text classifier can handle. In such
|
||||
// cases, we process the text up to the maximum length.
|
||||
final CharSequence truncatedText = text.subSequence(
|
||||
0, Math.min(text.length(), classifier.getMaxGenerateLinksTextLength()));
|
||||
|
||||
final Supplier<TextLinks> supplier = () ->
|
||||
classifier.generateLinks(truncatedText, options.setLegacyFallback(true));
|
||||
final TextClassifier.EntityConfig entityConfig = (params == null)
|
||||
? null : params.getEntityConfig();
|
||||
final TextLinks.Request request = new TextLinks.Request.Builder(truncatedText)
|
||||
.setLegacyFallback(true)
|
||||
.setEntityConfig(entityConfig)
|
||||
.build();
|
||||
final Supplier<TextLinks> supplier = () -> classifier.generateLinks(request);
|
||||
final Consumer<TextLinks> consumer = links -> {
|
||||
if (links.getLinks().isEmpty()) {
|
||||
if (callback != null) {
|
||||
@@ -661,17 +697,13 @@ public class Linkify {
|
||||
}
|
||||
|
||||
// Remove spans only for the part of the text we generated links for.
|
||||
final TextLinkSpan[] old = text.getSpans(0, truncatedText.length(), TextLinkSpan.class);
|
||||
final TextLinkSpan[] old =
|
||||
text.getSpans(0, truncatedText.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,
|
||||
true /*allowPrefix*/);
|
||||
final @TextLinks.Status int result = params.apply(text, links);
|
||||
if (result == TextLinks.STATUS_LINKS_APPLIED) {
|
||||
if (modifyTextView != null) {
|
||||
modifyTextView.run();
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class DefaultLogger extends Logger {
|
||||
.addTaggedData(INDEX, event.getEventIndex())
|
||||
.addTaggedData(WIDGET_TYPE, event.getWidgetType())
|
||||
.addTaggedData(WIDGET_VERSION, event.getWidgetVersion())
|
||||
.addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getSignature()))
|
||||
.addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getResultId()))
|
||||
.addTaggedData(ENTITY_TYPE, event.getEntityType())
|
||||
.addTaggedData(SMART_START, event.getSmartStart())
|
||||
.addTaggedData(SMART_END, event.getSmartEnd())
|
||||
@@ -231,9 +231,9 @@ public final class DefaultLogger extends Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a signature string that may be used to tag TextClassifier results.
|
||||
* Creates a string id that may be used to identify a TextClassifier result.
|
||||
*/
|
||||
public static String createSignature(
|
||||
public static String createId(
|
||||
String text, int start, int end, Context context, int modelVersion,
|
||||
List<Locale> locales) {
|
||||
Preconditions.checkNotNull(text);
|
||||
@@ -250,7 +250,7 @@ public final class DefaultLogger extends Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for creating and parsing signature strings for
|
||||
* Helper for creating and parsing string ids for
|
||||
* {@link android.view.textclassifier.TextClassifierImpl}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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.NonNull;
|
||||
|
||||
/**
|
||||
* Link information that can be applied to text. See: {@link #apply(CharSequence)}.
|
||||
* Typical implementations of this interface will annotate spannable text with e.g
|
||||
* {@link android.text.style.ClickableSpan}s or other annotations.
|
||||
* @hide
|
||||
*/
|
||||
public interface LinksInfo {
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
LinksInfo NO_OP = text -> false;
|
||||
|
||||
/**
|
||||
* Applies link annotations to the specified text.
|
||||
* These annotations are not guaranteed to be applied. For example, the annotations may not be
|
||||
* applied if the text has changed from what it was when the link spec was generated for it.
|
||||
*
|
||||
* @return Whether or not the link annotations were successfully applied.
|
||||
*/
|
||||
boolean apply(@NonNull CharSequence text);
|
||||
}
|
||||
@@ -35,6 +35,10 @@ final class Log {
|
||||
Slog.d(tag, msg);
|
||||
}
|
||||
|
||||
public static void w(String tag, String msg) {
|
||||
Slog.w(tag, msg);
|
||||
}
|
||||
|
||||
public static void e(String tag, String msg, Throwable tr) {
|
||||
if (ENABLE_FULL_LOGGING) {
|
||||
Slog.e(tag, msg, tr);
|
||||
|
||||
@@ -35,8 +35,6 @@ public abstract class Logger {
|
||||
private static final String LOG_TAG = "Logger";
|
||||
/* package */ static final boolean DEBUG_LOG_ENABLED = true;
|
||||
|
||||
private static final String NO_SIGNATURE = "";
|
||||
|
||||
private @SelectionEvent.InvocationMethod int mInvocationMethod;
|
||||
private SelectionEvent mPrevEvent;
|
||||
private SelectionEvent mSmartEvent;
|
||||
@@ -68,12 +66,12 @@ public abstract class Logger {
|
||||
public abstract void writeEvent(@NonNull SelectionEvent event);
|
||||
|
||||
/**
|
||||
* Returns true if the signature matches that of a smart selection event (i.e.
|
||||
* Returns true if the resultId matches that of a smart selection event (i.e.
|
||||
* {@link SelectionEvent#EVENT_SMART_SELECTION_SINGLE} or
|
||||
* {@link SelectionEvent#EVENT_SMART_SELECTION_MULTI}).
|
||||
* Returns false otherwise.
|
||||
*/
|
||||
public boolean isSmartSelection(@NonNull String signature) {
|
||||
public boolean isSmartSelection(@NonNull String resultId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -99,7 +97,7 @@ public abstract class Logger {
|
||||
mInvocationMethod = invocationMethod;
|
||||
logEvent(new SelectionEvent(
|
||||
start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED,
|
||||
TextClassifier.TYPE_UNKNOWN, mInvocationMethod, NO_SIGNATURE, mConfig));
|
||||
TextClassifier.TYPE_UNKNOWN, mInvocationMethod, null, mConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +116,7 @@ public abstract class Logger {
|
||||
|
||||
logEvent(new SelectionEvent(
|
||||
start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
|
||||
TextClassifier.TYPE_UNKNOWN, mInvocationMethod, NO_SIGNATURE, mConfig));
|
||||
TextClassifier.TYPE_UNKNOWN, mInvocationMethod, null, mConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,10 +140,9 @@ public abstract class Logger {
|
||||
final String entityType = classification.getEntityCount() > 0
|
||||
? classification.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
final String signature = classification.getSignature();
|
||||
logEvent(new SelectionEvent(
|
||||
start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
|
||||
entityType, mInvocationMethod, signature, mConfig));
|
||||
entityType, mInvocationMethod, classification.getId(), mConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +164,7 @@ public abstract class Logger {
|
||||
}
|
||||
|
||||
final int eventType;
|
||||
if (isSmartSelection(selection.getSignature())) {
|
||||
if (isSmartSelection(selection.getId())) {
|
||||
eventType = end - start > 1
|
||||
? SelectionEvent.EVENT_SMART_SELECTION_MULTI
|
||||
: SelectionEvent.EVENT_SMART_SELECTION_SINGLE;
|
||||
@@ -178,9 +175,8 @@ public abstract class Logger {
|
||||
final String entityType = selection.getEntityCount() > 0
|
||||
? selection.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
final String signature = selection.getSignature();
|
||||
logEvent(new SelectionEvent(start, end, eventType, entityType, mInvocationMethod, signature,
|
||||
mConfig));
|
||||
logEvent(new SelectionEvent(start, end, eventType, entityType, mInvocationMethod,
|
||||
selection.getId(), mConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +198,7 @@ public abstract class Logger {
|
||||
|
||||
logEvent(new SelectionEvent(
|
||||
start, end, actionType, TextClassifier.TYPE_UNKNOWN, mInvocationMethod,
|
||||
NO_SIGNATURE, mConfig));
|
||||
null, mConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,9 +228,8 @@ public abstract class Logger {
|
||||
final String entityType = classification.getEntityCount() > 0
|
||||
? classification.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
final String signature = classification.getSignature();
|
||||
logEvent(new SelectionEvent(start, end, actionType, entityType, mInvocationMethod,
|
||||
signature, mConfig));
|
||||
classification.getId(), mConfig));
|
||||
}
|
||||
|
||||
private void logEvent(@NonNull SelectionEvent event) {
|
||||
@@ -280,7 +275,7 @@ public abstract class Logger {
|
||||
.setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
|
||||
}
|
||||
if (mSmartEvent != null) {
|
||||
event.setSignature(mSmartEvent.getSignature())
|
||||
event.setResultId(mSmartEvent.getResultId())
|
||||
.setSmartStart(mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
|
||||
.setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN;
|
||||
private @InvocationMethod int mInvocationMethod;
|
||||
@Nullable private String mWidgetVersion;
|
||||
private String mSignature; // TODO: Rename to resultId.
|
||||
@Nullable private String mResultId;
|
||||
private long mEventTime;
|
||||
private long mDurationSinceSessionStart;
|
||||
private long mDurationSincePreviousEvent;
|
||||
@@ -140,21 +140,22 @@ public final class SelectionEvent implements Parcelable {
|
||||
SelectionEvent(
|
||||
int start, int end,
|
||||
@EventType int eventType, @EntityType String entityType,
|
||||
@InvocationMethod int invocationMethod, String signature) {
|
||||
@InvocationMethod int invocationMethod, @Nullable String resultId) {
|
||||
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||
mAbsoluteStart = start;
|
||||
mAbsoluteEnd = end;
|
||||
mEventType = eventType;
|
||||
mEntityType = Preconditions.checkNotNull(entityType);
|
||||
mSignature = Preconditions.checkNotNull(signature);
|
||||
mResultId = resultId;
|
||||
mInvocationMethod = invocationMethod;
|
||||
}
|
||||
|
||||
SelectionEvent(
|
||||
int start, int end,
|
||||
@EventType int eventType, @EntityType String entityType,
|
||||
@InvocationMethod int invocationMethod, String signature, Logger.Config config) {
|
||||
this(start, end, eventType, entityType, invocationMethod, signature);
|
||||
@InvocationMethod int invocationMethod, @Nullable String resultId,
|
||||
Logger.Config config) {
|
||||
this(start, end, eventType, entityType, invocationMethod, resultId);
|
||||
Preconditions.checkNotNull(config);
|
||||
setTextClassificationSessionContext(
|
||||
new TextClassificationContext.Builder(
|
||||
@@ -172,7 +173,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
mPackageName = in.readString();
|
||||
mWidgetType = in.readString();
|
||||
mInvocationMethod = in.readInt();
|
||||
mSignature = in.readString();
|
||||
mResultId = in.readString();
|
||||
mEventTime = in.readLong();
|
||||
mDurationSinceSessionStart = in.readLong();
|
||||
mDurationSincePreviousEvent = in.readLong();
|
||||
@@ -198,7 +199,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
dest.writeString(mPackageName);
|
||||
dest.writeString(mWidgetType);
|
||||
dest.writeInt(mInvocationMethod);
|
||||
dest.writeString(mSignature);
|
||||
dest.writeString(mResultId);
|
||||
dest.writeLong(mEventTime);
|
||||
dest.writeLong(mDurationSinceSessionStart);
|
||||
dest.writeLong(mDurationSincePreviousEvent);
|
||||
@@ -270,7 +271,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
return new SelectionEvent(
|
||||
start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
|
||||
entityType, INVOCATION_UNKNOWN, classification.getSignature());
|
||||
entityType, INVOCATION_UNKNOWN, classification.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +295,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
return new SelectionEvent(
|
||||
start, end, SelectionEvent.EVENT_AUTO_SELECTION,
|
||||
entityType, INVOCATION_UNKNOWN, selection.getSignature());
|
||||
entityType, INVOCATION_UNKNOWN, selection.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,7 +343,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
? classification.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN,
|
||||
classification.getSignature());
|
||||
classification.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,14 +451,15 @@ public final class SelectionEvent implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature of the text classifier result associated with this event.
|
||||
* Returns the id of the text classifier result associated with this event.
|
||||
*/
|
||||
public String getSignature() {
|
||||
return mSignature;
|
||||
@Nullable
|
||||
public String getResultId() {
|
||||
return mResultId;
|
||||
}
|
||||
|
||||
SelectionEvent setSignature(String signature) {
|
||||
mSignature = Preconditions.checkNotNull(signature);
|
||||
SelectionEvent setResultId(@Nullable String resultId) {
|
||||
mResultId = resultId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -604,7 +606,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
|
||||
mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mSignature,
|
||||
mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId,
|
||||
mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
|
||||
mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
|
||||
}
|
||||
@@ -627,7 +629,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
&& Objects.equals(mPackageName, other.mPackageName)
|
||||
&& Objects.equals(mWidgetType, other.mWidgetType)
|
||||
&& mInvocationMethod == other.mInvocationMethod
|
||||
&& Objects.equals(mSignature, other.mSignature)
|
||||
&& Objects.equals(mResultId, other.mResultId)
|
||||
&& mEventTime == other.mEventTime
|
||||
&& mDurationSinceSessionStart == other.mDurationSinceSessionStart
|
||||
&& mDurationSincePreviousEvent == other.mDurationSincePreviousEvent
|
||||
@@ -642,15 +644,16 @@ public final class SelectionEvent implements Parcelable {
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US,
|
||||
"SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, "
|
||||
+ "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
|
||||
+ "signature=%s, eventTime=%d, durationSinceSessionStart=%d, "
|
||||
+ "durationSincePreviousEvent=%d, eventIndex=%d, sessionId=%s, start=%d, end=%d, "
|
||||
+ "smartStart=%d, smartEnd=%d}",
|
||||
"SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, "
|
||||
+ "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
|
||||
+ "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
|
||||
+ "durationSincePreviousEvent=%d, eventIndex=%d,"
|
||||
+ "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}",
|
||||
mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
|
||||
mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mSignature,
|
||||
mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
|
||||
mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
|
||||
mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod,
|
||||
mResultId, mEventTime, mDurationSinceSessionStart,
|
||||
mDurationSincePreviousEvent, mEventIndex,
|
||||
mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
|
||||
}
|
||||
|
||||
public static final Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() {
|
||||
@@ -664,4 +667,4 @@ public final class SelectionEvent implements Parcelable {
|
||||
return new SelectionEvent[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package android.view.textclassifier;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.WorkerThread;
|
||||
@@ -56,6 +55,8 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
private Logger.Config mLoggerConfig;
|
||||
@GuardedBy("mLoggerLock")
|
||||
private Logger mLogger;
|
||||
@GuardedBy("mLoggerLock")
|
||||
private TextClassificationSessionId mSessionId;
|
||||
|
||||
public SystemTextClassifier(Context context, TextClassificationConstants settings)
|
||||
throws ServiceManager.ServiceNotFoundException {
|
||||
@@ -71,26 +72,20 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
*/
|
||||
@Override
|
||||
@WorkerThread
|
||||
public TextSelection suggestSelection(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int selectionStartIndex,
|
||||
@IntRange(from = 0) int selectionEndIndex,
|
||||
@Nullable TextSelection.Options options) {
|
||||
Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */);
|
||||
public TextSelection suggestSelection(TextSelection.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
try {
|
||||
final TextSelectionCallback callback = new TextSelectionCallback();
|
||||
mManagerService.onSuggestSelection(
|
||||
text, selectionStartIndex, selectionEndIndex, options, callback);
|
||||
mManagerService.onSuggestSelection(mSessionId, request, callback);
|
||||
final TextSelection selection = callback.mReceiver.get();
|
||||
if (selection != null) {
|
||||
return selection;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(LOG_TAG, e.getMessage());
|
||||
} catch (RemoteException | InterruptedException e) {
|
||||
Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e);
|
||||
}
|
||||
return mFallback.suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
|
||||
return mFallback.suggestSelection(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,25 +93,20 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
*/
|
||||
@Override
|
||||
@WorkerThread
|
||||
public TextClassification classifyText(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int startIndex,
|
||||
@IntRange(from = 0) int endIndex,
|
||||
@Nullable TextClassification.Options options) {
|
||||
Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */);
|
||||
public TextClassification classifyText(TextClassification.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
try {
|
||||
final TextClassificationCallback callback = new TextClassificationCallback();
|
||||
mManagerService.onClassifyText(text, startIndex, endIndex, options, callback);
|
||||
mManagerService.onClassifyText(mSessionId, request, callback);
|
||||
final TextClassification classification = callback.mReceiver.get();
|
||||
if (classification != null) {
|
||||
return classification;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(LOG_TAG, e.getMessage());
|
||||
} catch (RemoteException | InterruptedException e) {
|
||||
Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
|
||||
}
|
||||
return mFallback.classifyText(text, startIndex, endIndex, options);
|
||||
return mFallback.classifyText(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,33 +114,26 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
*/
|
||||
@Override
|
||||
@WorkerThread
|
||||
public TextLinks generateLinks(
|
||||
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
|
||||
Utils.validate(text, false /* allowInMainThread */);
|
||||
public TextLinks generateLinks(@NonNull TextLinks.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
|
||||
final boolean legacyFallback = options != null && options.isLegacyFallback();
|
||||
if (!mSettings.isSmartLinkifyEnabled() && legacyFallback) {
|
||||
return Utils.generateLegacyLinks(text, options);
|
||||
if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
|
||||
return Utils.generateLegacyLinks(request);
|
||||
}
|
||||
|
||||
try {
|
||||
if (options == null) {
|
||||
options = new TextLinks.Options().setCallingPackageName(mPackageName);
|
||||
} else if (!mPackageName.equals(options.getCallingPackageName())) {
|
||||
options.setCallingPackageName(mPackageName);
|
||||
}
|
||||
request.setCallingPackageName(mPackageName);
|
||||
final TextLinksCallback callback = new TextLinksCallback();
|
||||
mManagerService.onGenerateLinks(text, options, callback);
|
||||
mManagerService.onGenerateLinks(mSessionId, request, callback);
|
||||
final TextLinks links = callback.mReceiver.get();
|
||||
if (links != null) {
|
||||
return links;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(LOG_TAG, e.getMessage());
|
||||
} catch (RemoteException | InterruptedException e) {
|
||||
Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
|
||||
}
|
||||
return mFallback.generateLinks(text, options);
|
||||
return mFallback.generateLinks(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,9 +156,9 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
@Override
|
||||
public void writeEvent(SelectionEvent event) {
|
||||
try {
|
||||
mManagerService.onSelectionEvent(event);
|
||||
mManagerService.onSelectionEvent(mSessionId, event);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
Log.e(LOG_TAG, "Error reporting selection event.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -184,6 +167,34 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
return mLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
try {
|
||||
if (mSessionId != null) {
|
||||
mManagerService.onDestroyTextClassificationSession(mSessionId);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(LOG_TAG, "Error destroying classification session.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to initialize a new classification session.
|
||||
*
|
||||
* @param classificationContext the classification context
|
||||
* @param sessionId the session's id
|
||||
*/
|
||||
void initializeRemoteSession(
|
||||
@NonNull TextClassificationContext classificationContext,
|
||||
@NonNull TextClassificationSessionId sessionId) {
|
||||
mSessionId = Preconditions.checkNotNull(sessionId);
|
||||
try {
|
||||
mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(LOG_TAG, "Error starting a new classification session.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
|
||||
|
||||
final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>();
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
package android.view.textclassifier;
|
||||
|
||||
parcelable TextClassification;
|
||||
parcelable TextClassification.Options;
|
||||
parcelable TextClassification.Request;
|
||||
@@ -38,6 +38,7 @@ import android.os.Parcelable;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.textclassifier.TextClassifier.EntityType;
|
||||
import android.view.textclassifier.TextClassifier.Utils;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -123,7 +124,7 @@ public final class TextClassification implements Parcelable {
|
||||
@Nullable private final OnClickListener mLegacyOnClickListener;
|
||||
@NonNull private final List<RemoteAction> mActions;
|
||||
@NonNull private final EntityConfidence mEntityConfidence;
|
||||
@NonNull private final String mSignature;
|
||||
@Nullable private final String mId;
|
||||
|
||||
private TextClassification(
|
||||
@Nullable String text,
|
||||
@@ -133,7 +134,7 @@ public final class TextClassification implements Parcelable {
|
||||
@Nullable OnClickListener legacyOnClickListener,
|
||||
@NonNull List<RemoteAction> actions,
|
||||
@NonNull Map<String, Float> entityConfidence,
|
||||
@NonNull String signature) {
|
||||
@Nullable String id) {
|
||||
mText = text;
|
||||
mLegacyIcon = legacyIcon;
|
||||
mLegacyLabel = legacyLabel;
|
||||
@@ -141,7 +142,7 @@ public final class TextClassification implements Parcelable {
|
||||
mLegacyOnClickListener = legacyOnClickListener;
|
||||
mActions = Collections.unmodifiableList(actions);
|
||||
mEntityConfidence = new EntityConfidence(entityConfidence);
|
||||
mSignature = signature;
|
||||
mId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,20 +238,18 @@ public final class TextClassification implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature for this object.
|
||||
* The TextClassifier that generates this object may use it as a way to internally identify
|
||||
* this object.
|
||||
* Returns the id, if one exists, for this object.
|
||||
*/
|
||||
@NonNull
|
||||
public String getSignature() {
|
||||
return mSignature;
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US,
|
||||
"TextClassification {text=%s, entities=%s, actions=%s, signature=%s}",
|
||||
mText, mEntityConfidence, mActions, mSignature);
|
||||
"TextClassification {text=%s, entities=%s, actions=%s, id=%s}",
|
||||
mText, mEntityConfidence, mActions, mId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +263,7 @@ public final class TextClassification implements Parcelable {
|
||||
try {
|
||||
intent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(LOG_TAG, "Error creating OnClickListener from PendingIntent", e);
|
||||
Log.e(LOG_TAG, "Error sending PendingIntent", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -289,25 +288,6 @@ public final class TextClassification implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the specified intent.
|
||||
*
|
||||
* @throws IllegalArgumentException if context or intent is null
|
||||
* @hide
|
||||
*/
|
||||
public static void fireIntent(@NonNull final Context context, @NonNull final Intent intent) {
|
||||
switch (getIntentType(intent, context)) {
|
||||
case IntentType.ACTIVITY:
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
case IntentType.SERVICE:
|
||||
context.startService(intent);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@IntentType
|
||||
private static int getIntentType(@NonNull Intent intent, @NonNull Context context) {
|
||||
Preconditions.checkArgument(context != null);
|
||||
@@ -402,11 +382,12 @@ public final class TextClassification implements Parcelable {
|
||||
@Nullable String mLegacyLabel;
|
||||
@Nullable Intent mLegacyIntent;
|
||||
@Nullable OnClickListener mLegacyOnClickListener;
|
||||
@NonNull private String mSignature = "";
|
||||
@Nullable private String mId;
|
||||
|
||||
/**
|
||||
* Sets the classified text.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setText(@Nullable String text) {
|
||||
mText = text;
|
||||
return this;
|
||||
@@ -421,6 +402,7 @@ public final class TextClassification implements Parcelable {
|
||||
* 0 implies the entity does not exist for the classified text.
|
||||
* Values greater than 1 are clamped to 1.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setEntityType(
|
||||
@NonNull @EntityType String type,
|
||||
@FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
|
||||
@@ -433,6 +415,7 @@ public final class TextClassification implements Parcelable {
|
||||
* order of likelihood that the user will use them, with the most likely action being added
|
||||
* first.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addAction(@NonNull RemoteAction action) {
|
||||
Preconditions.checkArgument(action != null);
|
||||
mActions.add(action);
|
||||
@@ -446,6 +429,7 @@ public final class TextClassification implements Parcelable {
|
||||
* @deprecated Use {@link #addAction(RemoteAction)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder setIcon(@Nullable Drawable icon) {
|
||||
mLegacyIcon = icon;
|
||||
return this;
|
||||
@@ -458,6 +442,7 @@ public final class TextClassification implements Parcelable {
|
||||
* @deprecated Use {@link #addAction(RemoteAction)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder setLabel(@Nullable String label) {
|
||||
mLegacyLabel = label;
|
||||
return this;
|
||||
@@ -470,6 +455,7 @@ public final class TextClassification implements Parcelable {
|
||||
* @deprecated Use {@link #addAction(RemoteAction)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder setIntent(@Nullable Intent intent) {
|
||||
mLegacyIntent = intent;
|
||||
return this;
|
||||
@@ -482,58 +468,79 @@ public final class TextClassification implements Parcelable {
|
||||
*
|
||||
* @deprecated Use {@link #addAction(RemoteAction)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
|
||||
mLegacyOnClickListener = onClickListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a signature for the TextClassification object.
|
||||
* The TextClassifier that generates the TextClassification object may use it as a way to
|
||||
* internally identify the TextClassification object.
|
||||
* Sets an id for the TextClassification object.
|
||||
*/
|
||||
public Builder setSignature(@NonNull String signature) {
|
||||
mSignature = Preconditions.checkNotNull(signature);
|
||||
@NonNull
|
||||
public Builder setId(@Nullable String id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a {@link TextClassification} object.
|
||||
*/
|
||||
@NonNull
|
||||
public TextClassification build() {
|
||||
return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
|
||||
mLegacyOnClickListener, mActions, mEntityConfidence, mSignature);
|
||||
mLegacyOnClickListener, mActions, mEntityConfidence, mId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional input parameters for generating TextClassification.
|
||||
* A request object for generating TextClassification.
|
||||
*/
|
||||
public static final class Options implements Parcelable {
|
||||
public static final class Request implements Parcelable {
|
||||
|
||||
private @Nullable LocaleList mDefaultLocales;
|
||||
private @Nullable ZonedDateTime mReferenceTime;
|
||||
private final CharSequence mText;
|
||||
private final int mStartIndex;
|
||||
private final int mEndIndex;
|
||||
@Nullable private final LocaleList mDefaultLocales;
|
||||
@Nullable private final ZonedDateTime mReferenceTime;
|
||||
|
||||
public Options() {}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
|
||||
private Request(
|
||||
CharSequence text,
|
||||
int startIndex,
|
||||
int endIndex,
|
||||
LocaleList defaultLocales,
|
||||
ZonedDateTime referenceTime) {
|
||||
mText = text;
|
||||
mStartIndex = startIndex;
|
||||
mEndIndex = endIndex;
|
||||
mDefaultLocales = defaultLocales;
|
||||
return this;
|
||||
mReferenceTime = referenceTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param referenceTime reference time based on which relative dates (e.g. "tomorrow" should
|
||||
* be interpreted. This should usually be the time when the text was originally
|
||||
* composed. If no reference time is set, now is used.
|
||||
* Returns the text providing context for the text to classify (which is specified
|
||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||
*/
|
||||
public Options setReferenceTime(ZonedDateTime referenceTime) {
|
||||
mReferenceTime = referenceTime;
|
||||
return this;
|
||||
@NonNull
|
||||
public CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns start index of the text to classify.
|
||||
*/
|
||||
@IntRange(from = 0)
|
||||
public int getStartIndex() {
|
||||
return mStartIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns end index of the text to classify.
|
||||
*/
|
||||
@IntRange(from = 0)
|
||||
public int getEndIndex() {
|
||||
return mEndIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,6 +561,69 @@ public final class TextClassification implements Parcelable {
|
||||
return mReferenceTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for building TextClassification requests.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final CharSequence mText;
|
||||
private final int mStartIndex;
|
||||
private final int mEndIndex;
|
||||
|
||||
@Nullable private LocaleList mDefaultLocales;
|
||||
@Nullable private ZonedDateTime mReferenceTime;
|
||||
|
||||
/**
|
||||
* @param text text providing context for the text to classify (which is specified
|
||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||
* @param startIndex start index of the text to classify
|
||||
* @param endIndex end index of the text to classify
|
||||
*/
|
||||
public Builder(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int startIndex,
|
||||
@IntRange(from = 0) int endIndex) {
|
||||
Utils.checkArgument(text, startIndex, endIndex);
|
||||
mText = text;
|
||||
mStartIndex = startIndex;
|
||||
mEndIndex = endIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param referenceTime reference time based on which relative dates (e.g. "tomorrow"
|
||||
* should be interpreted. This should usually be the time when the text was
|
||||
* originally composed. If no reference time is set, now is used.
|
||||
*
|
||||
* @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, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -561,6 +631,9 @@ public final class TextClassification implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mText.toString());
|
||||
dest.writeInt(mStartIndex);
|
||||
dest.writeInt(mEndIndex);
|
||||
dest.writeInt(mDefaultLocales != null ? 1 : 0);
|
||||
if (mDefaultLocales != null) {
|
||||
mDefaultLocales.writeToParcel(dest, flags);
|
||||
@@ -571,26 +644,25 @@ public final class TextClassification implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Options> CREATOR =
|
||||
new Parcelable.Creator<Options>() {
|
||||
public static final Parcelable.Creator<Request> CREATOR =
|
||||
new Parcelable.Creator<Request>() {
|
||||
@Override
|
||||
public Options createFromParcel(Parcel in) {
|
||||
return new Options(in);
|
||||
public Request createFromParcel(Parcel in) {
|
||||
return new Request(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options[] newArray(int size) {
|
||||
return new Options[size];
|
||||
public Request[] newArray(int size) {
|
||||
return new Request[size];
|
||||
}
|
||||
};
|
||||
|
||||
private Options(Parcel in) {
|
||||
if (in.readInt() > 0) {
|
||||
mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
|
||||
}
|
||||
if (in.readInt() > 0) {
|
||||
mReferenceTime = ZonedDateTime.parse(in.readString());
|
||||
}
|
||||
private Request(Parcel in) {
|
||||
mText = in.readString();
|
||||
mStartIndex = in.readInt();
|
||||
mEndIndex = in.readInt();
|
||||
mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
|
||||
mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse(in.readString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,7 +687,7 @@ public final class TextClassification implements Parcelable {
|
||||
// mOnClickListener is not parcelable.
|
||||
dest.writeTypedList(mActions);
|
||||
mEntityConfidence.writeToParcel(dest, flags);
|
||||
dest.writeString(mSignature);
|
||||
dest.writeString(mId);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<TextClassification> CREATOR =
|
||||
@@ -647,6 +719,6 @@ public final class TextClassification implements Parcelable {
|
||||
mLegacyOnClickListener = null; // not parcelable
|
||||
mActions = in.createTypedArrayList(RemoteAction.CREATOR);
|
||||
mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
|
||||
mSignature = in.readString();
|
||||
mId = in.readString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 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;
|
||||
|
||||
parcelable TextClassificationContext;
|
||||
@@ -18,6 +18,8 @@ package android.view.textclassifier;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.textclassifier.TextClassifier.WidgetType;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
@@ -28,7 +30,7 @@ import java.util.Locale;
|
||||
* A representation of the context in which text classification would be performed.
|
||||
* @see TextClassificationManager#createTextClassificationSession(TextClassificationContext)
|
||||
*/
|
||||
public final class TextClassificationContext {
|
||||
public final class TextClassificationContext implements Parcelable {
|
||||
|
||||
private final String mPackageName;
|
||||
private final String mWidgetType;
|
||||
@@ -120,4 +122,35 @@ public final class TextClassificationContext {
|
||||
return new TextClassificationContext(mPackageName, mWidgetType, mWidgetVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(mPackageName);
|
||||
parcel.writeString(mWidgetType);
|
||||
parcel.writeString(mWidgetVersion);
|
||||
}
|
||||
|
||||
private TextClassificationContext(Parcel in) {
|
||||
mPackageName = in.readString();
|
||||
mWidgetType = in.readString();
|
||||
mWidgetVersion = in.readString();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<TextClassificationContext> CREATOR =
|
||||
new Parcelable.Creator<TextClassificationContext>() {
|
||||
@Override
|
||||
public TextClassificationContext createFromParcel(Parcel parcel) {
|
||||
return new TextClassificationContext(parcel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextClassificationContext[] newArray(int size) {
|
||||
return new TextClassificationContext[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,32 +33,42 @@ final class TextClassificationSession implements TextClassifier {
|
||||
|
||||
private final TextClassifier mDelegate;
|
||||
private final SelectionEventHelper mEventHelper;
|
||||
private final TextClassificationSessionId mSessionId;
|
||||
private final TextClassificationContext mClassificationContext;
|
||||
|
||||
private boolean mDestroyed;
|
||||
|
||||
TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
|
||||
mClassificationContext = Preconditions.checkNotNull(context);
|
||||
mDelegate = Preconditions.checkNotNull(delegate);
|
||||
mEventHelper = new SelectionEventHelper(new TextClassificationSessionId(), context);
|
||||
mSessionId = new TextClassificationSessionId();
|
||||
mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext);
|
||||
initializeRemoteSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextSelection suggestSelection(CharSequence text, int selectionStartIndex,
|
||||
int selectionEndIndex, TextSelection.Options options) {
|
||||
public TextSelection suggestSelection(TextSelection.Request request) {
|
||||
checkDestroyed();
|
||||
return mDelegate.suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
|
||||
return mDelegate.suggestSelection(request);
|
||||
}
|
||||
|
||||
private void initializeRemoteSession() {
|
||||
if (mDelegate instanceof SystemTextClassifier) {
|
||||
((SystemTextClassifier) mDelegate).initializeRemoteSession(
|
||||
mClassificationContext, mSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextClassification classifyText(CharSequence text, int startIndex, int endIndex,
|
||||
TextClassification.Options options) {
|
||||
public TextClassification classifyText(TextClassification.Request request) {
|
||||
checkDestroyed();
|
||||
return mDelegate.classifyText(text, startIndex, endIndex, options);
|
||||
return mDelegate.classifyText(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextLinks generateLinks(CharSequence text, TextLinks.Options options) {
|
||||
public TextLinks generateLinks(TextLinks.Request request) {
|
||||
checkDestroyed();
|
||||
return mDelegate.generateLinks(text, options);
|
||||
return mDelegate.generateLinks(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,6 +83,7 @@ final class TextClassificationSession implements TextClassifier {
|
||||
@Override
|
||||
public void destroy() {
|
||||
mEventHelper.endSession();
|
||||
mDelegate.destroy();
|
||||
mDestroyed = true;
|
||||
}
|
||||
|
||||
@@ -162,7 +173,7 @@ final class TextClassificationSession implements TextClassifier {
|
||||
.setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
|
||||
}
|
||||
if (mSmartEvent != null) {
|
||||
event.setSignature(mSmartEvent.getSignature())
|
||||
event.setResultId(mSmartEvent.getResultId())
|
||||
.setSmartStart(
|
||||
mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
|
||||
.setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
|
||||
@@ -195,7 +206,7 @@ final class TextClassificationSession implements TextClassifier {
|
||||
case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through
|
||||
case SelectionEvent.EVENT_SMART_SELECTION_MULTI: // fall through
|
||||
case SelectionEvent.EVENT_AUTO_SELECTION:
|
||||
if (isPlatformLocalTextClassifierSmartSelection(event.getSignature())) {
|
||||
if (isPlatformLocalTextClassifierSmartSelection(event.getResultId())) {
|
||||
if (event.getAbsoluteEnd() - event.getAbsoluteStart() > 1) {
|
||||
event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 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;
|
||||
|
||||
parcelable TextClassificationSessionId;
|
||||
@@ -22,6 +22,7 @@ import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -76,6 +77,11 @@ public final class TextClassificationSessionId implements Parcelable {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "TextClassificationSessionId {%s}", mValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(mValue);
|
||||
|
||||
@@ -33,7 +33,6 @@ import android.text.util.Linkify;
|
||||
import android.text.util.Linkify.LinkifyMask;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -156,76 +155,44 @@ public interface TextClassifier {
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text text providing context for the selected text (which is specified
|
||||
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||
* @param selectionStartIndex start index of the selected part of text
|
||||
* @param selectionEndIndex end index of the selected part of text
|
||||
* @param options optional input parameters
|
||||
*
|
||||
* @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
|
||||
* selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
|
||||
*
|
||||
* @see #suggestSelection(CharSequence, int, int)
|
||||
* @param request the text selection request
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextSelection suggestSelection(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int selectionStartIndex,
|
||||
@IntRange(from = 0) int selectionEndIndex,
|
||||
@Nullable TextSelection.Options options) {
|
||||
Utils.validate(text, selectionStartIndex, selectionEndIndex, false);
|
||||
return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
|
||||
default TextSelection suggestSelection(@NonNull TextSelection.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suggested text selection start and end indices, recognized entity types, and their
|
||||
* associated confidence scores. The entity types are ordered from highest to lowest scoring.
|
||||
*
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
|
||||
* calls this method, a stack overflow error will happen.
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #suggestSelection(TextSelection.Request)}. If that method calls this method,
|
||||
* a stack overflow error will happen.
|
||||
*
|
||||
* @param text text providing context for the selected text (which is specified
|
||||
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||
* @param selectionStartIndex start index of the selected part of text
|
||||
* @param selectionEndIndex end index of the selected part of 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.
|
||||
*
|
||||
* @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
|
||||
* selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
|
||||
*
|
||||
* @see #suggestSelection(CharSequence, int, int, TextSelection.Options)
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextSelection suggestSelection(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int selectionStartIndex,
|
||||
@IntRange(from = 0) int selectionEndIndex) {
|
||||
return suggestSelection(text, selectionStartIndex, selectionEndIndex,
|
||||
(TextSelection.Options) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #suggestSelection(CharSequence, int, int)} or
|
||||
* {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}.
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
|
||||
* calls this method, a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
* @see #suggestSelection(TextSelection.Request)
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
@@ -234,10 +201,11 @@ public interface TextClassifier {
|
||||
@IntRange(from = 0) int selectionStartIndex,
|
||||
@IntRange(from = 0) int selectionEndIndex,
|
||||
@Nullable LocaleList defaultLocales) {
|
||||
final TextSelection.Options options = (defaultLocales != null)
|
||||
? new TextSelection.Options().setDefaultLocales(defaultLocales)
|
||||
: null;
|
||||
return suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
|
||||
final TextSelection.Request request = new TextSelection.Request.Builder(
|
||||
text, selectionStartIndex, selectionEndIndex)
|
||||
.setDefaultLocales(defaultLocales)
|
||||
.build();
|
||||
return suggestSelection(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,25 +217,13 @@ public interface TextClassifier {
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text text providing context for the text to classify (which is specified
|
||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||
* @param startIndex start index of the text to classify
|
||||
* @param endIndex end index of the text to classify
|
||||
* @param options optional input parameters
|
||||
*
|
||||
* @throws IllegalArgumentException if text is null; startIndex is negative;
|
||||
* endIndex is greater than text.length() or not greater than startIndex
|
||||
*
|
||||
* @see #classifyText(CharSequence, int, int)
|
||||
* @param request the text classification request
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextClassification classifyText(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int startIndex,
|
||||
@IntRange(from = 0) int endIndex,
|
||||
@Nullable TextClassification.Options options) {
|
||||
Utils.validate(text, startIndex, endIndex, false);
|
||||
default TextClassification classifyText(@NonNull TextClassification.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
return TextClassification.EMPTY;
|
||||
}
|
||||
|
||||
@@ -278,8 +234,8 @@ public interface TextClassifier {
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
|
||||
* calls this method, a stack overflow error will happen.
|
||||
* {@link #classifyText(TextClassification.Request)}. If that method calls this method,
|
||||
* a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
@@ -288,33 +244,14 @@ public interface TextClassifier {
|
||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||
* @param startIndex start index of the text to classify
|
||||
* @param endIndex end index of the text to classify
|
||||
* @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.
|
||||
*
|
||||
* @throws IllegalArgumentException if text is null; startIndex is negative;
|
||||
* endIndex is greater than text.length() or not greater than startIndex
|
||||
*
|
||||
* @see #classifyText(CharSequence, int, int, TextClassification.Options)
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextClassification classifyText(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int startIndex,
|
||||
@IntRange(from = 0) int endIndex) {
|
||||
return classifyText(text, startIndex, endIndex, (TextClassification.Options) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #classifyText(CharSequence, int, int, TextClassification.Options)} or
|
||||
* {@link #classifyText(CharSequence, int, int)}.
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
|
||||
* calls this method, a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
* @see #classifyText(TextClassification.Request)
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
@@ -323,10 +260,11 @@ public interface TextClassifier {
|
||||
@IntRange(from = 0) int startIndex,
|
||||
@IntRange(from = 0) int endIndex,
|
||||
@Nullable LocaleList defaultLocales) {
|
||||
final TextClassification.Options options = (defaultLocales != null)
|
||||
? new TextClassification.Options().setDefaultLocales(defaultLocales)
|
||||
: null;
|
||||
return classifyText(text, startIndex, endIndex, options);
|
||||
final TextClassification.Request request = new TextClassification.Request.Builder(
|
||||
text, startIndex, endIndex)
|
||||
.setDefaultLocales(defaultLocales)
|
||||
.build();
|
||||
return classifyText(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,48 +276,16 @@ public interface TextClassifier {
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text the text to generate annotations for
|
||||
* @param options configuration for link generation
|
||||
* @param request the text links request
|
||||
*
|
||||
* @throws IllegalArgumentException if text is null or the text is too long for the
|
||||
* TextClassifier implementation.
|
||||
*
|
||||
* @see #generateLinks(CharSequence)
|
||||
* @see #getMaxGenerateLinksTextLength()
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextLinks generateLinks(
|
||||
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
|
||||
Utils.validate(text, false);
|
||||
return new TextLinks.Builder(text.toString()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
|
||||
* links information.
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method,
|
||||
* a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text the text to generate annotations for
|
||||
*
|
||||
* @throws IllegalArgumentException if text is null or the text is too long for the
|
||||
* TextClassifier implementation.
|
||||
*
|
||||
* @see #generateLinks(CharSequence, TextLinks.Options)
|
||||
* @see #getMaxGenerateLinksTextLength()
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextLinks generateLinks(@NonNull CharSequence text) {
|
||||
return generateLinks(text, null);
|
||||
default TextLinks generateLinks(@NonNull TextLinks.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
return new TextLinks.Builder(request.getText().toString()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,8 +294,7 @@ public interface TextClassifier {
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @see #generateLinks(CharSequence)
|
||||
* @see #generateLinks(CharSequence, TextLinks.Options)
|
||||
* @see #generateLinks(TextLinks.Request)
|
||||
*/
|
||||
@WorkerThread
|
||||
default int getMaxGenerateLinksTextLength() {
|
||||
@@ -467,7 +372,7 @@ public interface TextClassifier {
|
||||
*
|
||||
* @param hints Hints for the TextClassifier to determine what types of entities to find.
|
||||
*/
|
||||
public static EntityConfig create(@Nullable Collection<String> hints) {
|
||||
public static EntityConfig createWithHints(@Nullable Collection<String> hints) {
|
||||
return new EntityConfig(/* useHints */ true, hints,
|
||||
/* includedEntityTypes */null, /* excludedEntityTypes */ null);
|
||||
}
|
||||
@@ -495,7 +400,8 @@ public interface TextClassifier {
|
||||
* @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find.
|
||||
*
|
||||
*/
|
||||
public static EntityConfig createWithEntityList(@Nullable Collection<String> entityTypes) {
|
||||
public static EntityConfig createWithExplicitEntityList(
|
||||
@Nullable Collection<String> entityTypes) {
|
||||
return new EntityConfig(/* useHints */ false, /* hints */ null,
|
||||
/* includedEntityTypes */ entityTypes, /* excludedEntityTypes */ null);
|
||||
}
|
||||
@@ -584,42 +490,25 @@ public interface TextClassifier {
|
||||
* endIndex is greater than text.length() or is not greater than startIndex;
|
||||
* options is null
|
||||
*/
|
||||
public static void validate(
|
||||
@NonNull CharSequence text, int startIndex, int endIndex,
|
||||
boolean allowInMainThread) {
|
||||
static void checkArgument(@NonNull CharSequence text, int startIndex, int endIndex) {
|
||||
Preconditions.checkArgument(text != null);
|
||||
Preconditions.checkArgument(startIndex >= 0);
|
||||
Preconditions.checkArgument(endIndex <= text.length());
|
||||
Preconditions.checkArgument(endIndex > startIndex);
|
||||
checkMainThread(allowInMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if text is null or options is null
|
||||
*/
|
||||
public static void validate(@NonNull CharSequence text, boolean allowInMainThread) {
|
||||
Preconditions.checkArgument(text != null);
|
||||
checkMainThread(allowInMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if text is null; the text is too long or options is null
|
||||
*/
|
||||
public static void validate(@NonNull CharSequence text, int maxLength,
|
||||
boolean allowInMainThread) {
|
||||
validate(text, allowInMainThread);
|
||||
static void checkTextLength(CharSequence text, int maxLength) {
|
||||
Preconditions.checkArgumentInRange(text.length(), 0, maxLength, "text.length()");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates links using legacy {@link Linkify}.
|
||||
*/
|
||||
public static TextLinks generateLegacyLinks(
|
||||
@NonNull CharSequence text, @NonNull TextLinks.Options options) {
|
||||
final String string = Preconditions.checkNotNull(text).toString();
|
||||
public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) {
|
||||
final String string = request.getText().toString();
|
||||
final TextLinks.Builder links = new TextLinks.Builder(string);
|
||||
|
||||
final List<String> entities = Preconditions.checkNotNull(options).getEntityConfig()
|
||||
final List<String> entities = request.getEntityConfig()
|
||||
.resolveEntityListModifications(Collections.emptyList());
|
||||
if (entities.contains(TextClassifier.TYPE_URL)) {
|
||||
addLinks(links, string, TextClassifier.TYPE_URL);
|
||||
@@ -670,9 +559,9 @@ public interface TextClassifier {
|
||||
return scores;
|
||||
}
|
||||
|
||||
private static void checkMainThread(boolean allowInMainThread) {
|
||||
if (!allowInMainThread && Looper.myLooper() == Looper.getMainLooper()) {
|
||||
Slog.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread");
|
||||
static void checkMainThread() {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
Log.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,35 +112,32 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
/** @inheritDoc */
|
||||
@Override
|
||||
@WorkerThread
|
||||
public TextSelection suggestSelection(
|
||||
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
|
||||
@Nullable TextSelection.Options options) {
|
||||
Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */);
|
||||
public TextSelection suggestSelection(TextSelection.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
try {
|
||||
final int rangeLength = selectionEndIndex - selectionStartIndex;
|
||||
if (text.length() > 0
|
||||
final int rangeLength = request.getEndIndex() - request.getStartIndex();
|
||||
final String string = request.getText().toString();
|
||||
if (string.length() > 0
|
||||
&& rangeLength <= mSettings.getSuggestSelectionMaxRangeLength()) {
|
||||
final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
|
||||
final String localesString = concatenateLocales(locales);
|
||||
final String localesString = concatenateLocales(request.getDefaultLocales());
|
||||
final ZonedDateTime refTime = ZonedDateTime.now();
|
||||
final boolean darkLaunchAllowed = options != null && options.isDarkLaunchAllowed();
|
||||
final TextClassifierImplNative nativeImpl = getNative(locales);
|
||||
final String string = text.toString();
|
||||
final TextClassifierImplNative nativeImpl = getNative(request.getDefaultLocales());
|
||||
final int start;
|
||||
final int end;
|
||||
if (mSettings.isModelDarkLaunchEnabled() && !darkLaunchAllowed) {
|
||||
start = selectionStartIndex;
|
||||
end = selectionEndIndex;
|
||||
if (mSettings.isModelDarkLaunchEnabled() && !request.isDarkLaunchAllowed()) {
|
||||
start = request.getStartIndex();
|
||||
end = request.getEndIndex();
|
||||
} else {
|
||||
final int[] startEnd = nativeImpl.suggestSelection(
|
||||
string, selectionStartIndex, selectionEndIndex,
|
||||
string, request.getStartIndex(), request.getEndIndex(),
|
||||
new TextClassifierImplNative.SelectionOptions(localesString));
|
||||
start = startEnd[0];
|
||||
end = startEnd[1];
|
||||
}
|
||||
if (start < end
|
||||
&& start >= 0 && end <= string.length()
|
||||
&& start <= selectionStartIndex && end >= selectionEndIndex) {
|
||||
&& start <= request.getStartIndex() && end >= request.getEndIndex()) {
|
||||
final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
|
||||
final TextClassifierImplNative.ClassificationResult[] results =
|
||||
nativeImpl.classifyText(
|
||||
@@ -153,9 +150,8 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
for (int i = 0; i < size; i++) {
|
||||
tsBuilder.setEntityType(results[i].getCollection(), results[i].getScore());
|
||||
}
|
||||
return tsBuilder
|
||||
.setSignature(
|
||||
getSignature(string, selectionStartIndex, selectionEndIndex))
|
||||
return tsBuilder.setId(createId(
|
||||
string, request.getStartIndex(), request.getEndIndex()))
|
||||
.build();
|
||||
} else {
|
||||
// We can not trust the result. Log the issue and ignore the result.
|
||||
@@ -169,37 +165,34 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
t);
|
||||
}
|
||||
// Getting here means something went wrong, return a NO_OP result.
|
||||
return mFallback.suggestSelection(
|
||||
text, selectionStartIndex, selectionEndIndex, options);
|
||||
return mFallback.suggestSelection(request);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
@Override
|
||||
@WorkerThread
|
||||
public TextClassification classifyText(
|
||||
@NonNull CharSequence text, int startIndex, int endIndex,
|
||||
@Nullable TextClassification.Options options) {
|
||||
Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */);
|
||||
public TextClassification classifyText(TextClassification.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkMainThread();
|
||||
try {
|
||||
final int rangeLength = endIndex - startIndex;
|
||||
if (text.length() > 0 && rangeLength <= mSettings.getClassifyTextMaxRangeLength()) {
|
||||
final String string = text.toString();
|
||||
final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
|
||||
final String localesString = concatenateLocales(locales);
|
||||
final ZonedDateTime refTime =
|
||||
(options != null && options.getReferenceTime() != null)
|
||||
? options.getReferenceTime() : ZonedDateTime.now();
|
||||
|
||||
final int rangeLength = request.getEndIndex() - request.getStartIndex();
|
||||
final String string = request.getText().toString();
|
||||
if (string.length() > 0 && rangeLength <= mSettings.getClassifyTextMaxRangeLength()) {
|
||||
final String localesString = concatenateLocales(request.getDefaultLocales());
|
||||
final ZonedDateTime refTime = request.getReferenceTime() != null
|
||||
? request.getReferenceTime() : ZonedDateTime.now();
|
||||
final TextClassifierImplNative.ClassificationResult[] results =
|
||||
getNative(locales)
|
||||
.classifyText(string, startIndex, endIndex,
|
||||
getNative(request.getDefaultLocales())
|
||||
.classifyText(
|
||||
string, request.getStartIndex(), request.getEndIndex(),
|
||||
new TextClassifierImplNative.ClassificationOptions(
|
||||
refTime.toInstant().toEpochMilli(),
|
||||
refTime.getZone().getId(),
|
||||
localesString));
|
||||
if (results.length > 0) {
|
||||
return createClassificationResult(
|
||||
results, string, startIndex, endIndex, refTime.toInstant());
|
||||
results, string,
|
||||
request.getStartIndex(), request.getEndIndex(), refTime.toInstant());
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
@@ -207,42 +200,40 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
Log.e(LOG_TAG, "Error getting text classification info.", t);
|
||||
}
|
||||
// Getting here means something went wrong, return a NO_OP result.
|
||||
return mFallback.classifyText(text, startIndex, endIndex, options);
|
||||
return mFallback.classifyText(request);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
@Override
|
||||
@WorkerThread
|
||||
public TextLinks generateLinks(
|
||||
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
|
||||
Utils.validate(text, getMaxGenerateLinksTextLength(), false /* allowInMainThread */);
|
||||
public TextLinks generateLinks(@NonNull TextLinks.Request request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength());
|
||||
Utils.checkMainThread();
|
||||
|
||||
final boolean legacyFallback = options != null && options.isLegacyFallback();
|
||||
if (!mSettings.isSmartLinkifyEnabled() && legacyFallback) {
|
||||
return Utils.generateLegacyLinks(text, options);
|
||||
if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
|
||||
return Utils.generateLegacyLinks(request);
|
||||
}
|
||||
|
||||
final String textString = text.toString();
|
||||
final String textString = request.getText().toString();
|
||||
final TextLinks.Builder builder = new TextLinks.Builder(textString);
|
||||
|
||||
try {
|
||||
final long startTimeMs = System.currentTimeMillis();
|
||||
final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
|
||||
final ZonedDateTime refTime = ZonedDateTime.now();
|
||||
final Collection<String> entitiesToIdentify =
|
||||
options != null && options.getEntityConfig() != null
|
||||
? options.getEntityConfig().resolveEntityListModifications(
|
||||
getEntitiesForHints(options.getEntityConfig().getHints()))
|
||||
: mSettings.getEntityListDefault();
|
||||
final Collection<String> entitiesToIdentify = request.getEntityConfig() != null
|
||||
? request.getEntityConfig().resolveEntityListModifications(
|
||||
getEntitiesForHints(request.getEntityConfig().getHints()))
|
||||
: mSettings.getEntityListDefault();
|
||||
final TextClassifierImplNative nativeImpl =
|
||||
getNative(defaultLocales);
|
||||
getNative(request.getDefaultLocales());
|
||||
final TextClassifierImplNative.AnnotatedSpan[] annotations =
|
||||
nativeImpl.annotate(
|
||||
textString,
|
||||
new TextClassifierImplNative.AnnotationOptions(
|
||||
refTime.toInstant().toEpochMilli(),
|
||||
refTime.getZone().getId(),
|
||||
concatenateLocales(defaultLocales)));
|
||||
refTime.getZone().getId(),
|
||||
concatenateLocales(request.getDefaultLocales())));
|
||||
for (TextClassifierImplNative.AnnotatedSpan span : annotations) {
|
||||
final TextClassifierImplNative.ClassificationResult[] results =
|
||||
span.getClassification();
|
||||
@@ -258,18 +249,17 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
}
|
||||
final TextLinks links = builder.build();
|
||||
final long endTimeMs = System.currentTimeMillis();
|
||||
final String callingPackageName =
|
||||
options == null || options.getCallingPackageName() == null
|
||||
? mContext.getPackageName() // local (in process) TC.
|
||||
: options.getCallingPackageName();
|
||||
final String callingPackageName = request.getCallingPackageName() == null
|
||||
? mContext.getPackageName() // local (in process) TC.
|
||||
: request.getCallingPackageName();
|
||||
mGenerateLinksLogger.logGenerateLinks(
|
||||
text, links, callingPackageName, endTimeMs - startTimeMs);
|
||||
request.getText(), links, callingPackageName, endTimeMs - startTimeMs);
|
||||
return links;
|
||||
} catch (Throwable t) {
|
||||
// Avoid throwing from this method. Log the error.
|
||||
Log.e(LOG_TAG, "Error getting links info.", t);
|
||||
}
|
||||
return mFallback.generateLinks(text, options);
|
||||
return mFallback.generateLinks(request);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
@@ -339,9 +329,9 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
private String getSignature(String text, int start, int end) {
|
||||
private String createId(String text, int start, int end) {
|
||||
synchronized (mLock) {
|
||||
return DefaultLogger.createSignature(text, start, end, mContext, mModel.getVersion(),
|
||||
return DefaultLogger.createId(text, start, end, mContext, mModel.getVersion(),
|
||||
mModel.getSupportedLocales());
|
||||
}
|
||||
}
|
||||
@@ -455,7 +445,7 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
builder.addAction(action);
|
||||
}
|
||||
|
||||
return builder.setSignature(getSignature(text, start, end)).build();
|
||||
return builder.setId(createId(text, start, end)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -512,7 +502,7 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
return mPath;
|
||||
}
|
||||
|
||||
/** A name to use for signature generation. Effectively the name of the model file. */
|
||||
/** A name to use for id generation. Effectively the name of the model file. */
|
||||
String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
package android.view.textclassifier;
|
||||
|
||||
parcelable TextLinks;
|
||||
parcelable TextLinks.Options;
|
||||
parcelable TextLinks.Request;
|
||||
@@ -25,10 +25,9 @@ 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.text.util.Linkify;
|
||||
import android.text.util.Linkify.LinkifyMask;
|
||||
import android.view.View;
|
||||
import android.view.textclassifier.TextClassifier.EntityType;
|
||||
import android.widget.TextView;
|
||||
@@ -43,6 +42,7 @@ 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.function.Function;
|
||||
|
||||
@@ -79,15 +79,15 @@ public final class TextLinks implements Parcelable {
|
||||
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.
|
||||
*/
|
||||
* 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.
|
||||
*/
|
||||
* 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;
|
||||
@@ -98,71 +98,55 @@ public final class TextLinks implements Parcelable {
|
||||
mLinks = Collections.unmodifiableList(links);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text that was used to generate these links.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public String getText() {
|
||||
return mFullText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable Collection of the links.
|
||||
*/
|
||||
@NonNull
|
||||
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.
|
||||
* match the original text used to create the TextLinks.
|
||||
*
|
||||
* <p><strong>NOTE: </strong>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 strategy for resolving link conflicts
|
||||
* @param spanFactory a factory to generate spans from TextLinks. Will use a default if null
|
||||
* @param allowPrefix whether to allow applying links only to a prefix of the 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
|
||||
*
|
||||
* @hide
|
||||
* e.g. {@link #STATUS_LINKS_APPLIED}
|
||||
*/
|
||||
@Status
|
||||
public int apply(
|
||||
@NonNull Spannable text,
|
||||
@ApplyStrategy int applyStrategy,
|
||||
@Nullable Function<TextLink, TextLinkSpan> spanFactory,
|
||||
boolean allowPrefix) {
|
||||
@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
|
||||
Preconditions.checkNotNull(text);
|
||||
checkValidApplyStrategy(applyStrategy);
|
||||
final String textString = text.toString();
|
||||
if (!mFullText.equals(textString) && !(allowPrefix && textString.startsWith(mFullText))) {
|
||||
return STATUS_DIFFERENT_TEXT;
|
||||
}
|
||||
if (mLinks.isEmpty()) {
|
||||
return STATUS_NO_LINKS_FOUND;
|
||||
}
|
||||
return new TextLinksParams.Builder()
|
||||
.setApplyStrategy(applyStrategy)
|
||||
.setSpanFactory(spanFactory)
|
||||
.build()
|
||||
.apply(text, this);
|
||||
}
|
||||
|
||||
if (spanFactory == null) {
|
||||
spanFactory = DEFAULT_SPAN_FACTORY;
|
||||
}
|
||||
int applyCount = 0;
|
||||
for (TextLink link : mLinks) {
|
||||
final TextLinkSpan span = spanFactory.apply(link);
|
||||
if (span != null) {
|
||||
final ClickableSpan[] existingSpans = text.getSpans(
|
||||
link.getStart(), link.getEnd(), ClickableSpan.class);
|
||||
if (existingSpans.length > 0) {
|
||||
if (applyStrategy == 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 STATUS_NO_LINKS_APPLIED;
|
||||
}
|
||||
return STATUS_LINKS_APPLIED;
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "TextLinks{fullText=%s, links=%s}", mFullText, mLinks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -270,6 +254,13 @@ public final class TextLinks implements Parcelable {
|
||||
return mEntityScores.getConfidenceScore(entityType);
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -304,108 +295,35 @@ public final class TextLinks implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional input parameters for generating TextLinks.
|
||||
* A request object for generating TextLinks.
|
||||
*/
|
||||
public static final class Options implements Parcelable {
|
||||
|
||||
private LocaleList mDefaultLocales;
|
||||
private TextClassifier.EntityConfig mEntityConfig;
|
||||
private boolean mLegacyFallback;
|
||||
|
||||
private @ApplyStrategy int mApplyStrategy;
|
||||
private Function<TextLink, TextLinkSpan> mSpanFactory;
|
||||
public static final class Request implements Parcelable {
|
||||
|
||||
private final CharSequence mText;
|
||||
@Nullable private final LocaleList mDefaultLocales;
|
||||
@Nullable private final TextClassifier.EntityConfig mEntityConfig;
|
||||
private final boolean mLegacyFallback;
|
||||
private String mCallingPackageName;
|
||||
|
||||
/**
|
||||
* Returns a new options object based on the specified link mask.
|
||||
*/
|
||||
public static Options 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 Options().setEntityConfig(
|
||||
TextClassifier.EntityConfig.createWithEntityList(entitiesToFind));
|
||||
}
|
||||
|
||||
public Options() {}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
|
||||
private Request(
|
||||
CharSequence text,
|
||||
LocaleList defaultLocales,
|
||||
TextClassifier.EntityConfig entityConfig,
|
||||
boolean legacyFallback,
|
||||
String callingPackageName) {
|
||||
mText = text;
|
||||
mDefaultLocales = defaultLocales;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity configuration to use. This determines what types of entities the
|
||||
* TextClassifier will look for.
|
||||
*
|
||||
* @param entityConfig EntityConfig to use
|
||||
*/
|
||||
public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
|
||||
mEntityConfig = entityConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the TextClassifier can fallback to legacy links if smart linkify is
|
||||
* disabled.
|
||||
* <strong>Note: </strong>This is not parcelled.
|
||||
* @hide
|
||||
*/
|
||||
public Options setLegacyFallback(boolean legacyFallback) {
|
||||
mLegacyFallback = legacyFallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a strategy for resolving conflicts when applying generated links to text that
|
||||
* already have links.
|
||||
*
|
||||
* @throws IllegalArgumentException if applyStrategy is not valid
|
||||
*
|
||||
* @see #APPLY_STRATEGY_IGNORE
|
||||
* @see #APPLY_STRATEGY_REPLACE
|
||||
*/
|
||||
public Options setApplyStrategy(@ApplyStrategy int applyStrategy) {
|
||||
checkValidApplyStrategy(applyStrategy);
|
||||
mApplyStrategy = applyStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a factory for converting a TextLink to a TextLinkSpan.
|
||||
*
|
||||
* <p><strong>Note: </strong>This is not parceled over IPC.
|
||||
*/
|
||||
public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
|
||||
mSpanFactory = spanFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the package that requested the links to get generated.
|
||||
* @hide
|
||||
*/
|
||||
public Options setCallingPackageName(@Nullable String callingPackageName) {
|
||||
mCallingPackageName = callingPackageName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text to generate links for.
|
||||
*/
|
||||
@NonNull
|
||||
public CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -437,26 +355,91 @@ public final class TextLinks implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the strategy for resolving conflictswhen applying generated links to text that
|
||||
* already have links
|
||||
*
|
||||
* @see #APPLY_STRATEGY_IGNORE
|
||||
* @see #APPLY_STRATEGY_REPLACE
|
||||
* Sets the name of the package that requested the links to get generated.
|
||||
*/
|
||||
@ApplyStrategy
|
||||
public int getApplyStrategy() {
|
||||
return mApplyStrategy;
|
||||
void setCallingPackageName(@Nullable String callingPackageName) {
|
||||
mCallingPackageName = callingPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a factory for converting a TextLink to a TextLinkSpan.
|
||||
*
|
||||
* <p><strong>Note: </strong>This is not parcelable and will always return null if read
|
||||
* from a parcel
|
||||
* A builder for building TextLinks requests.
|
||||
*/
|
||||
@Nullable
|
||||
public Function<TextLink, TextLinkSpan> getSpanFactory() {
|
||||
return mSpanFactory;
|
||||
public static final class Builder {
|
||||
|
||||
private final CharSequence mText;
|
||||
|
||||
@Nullable private LocaleList mDefaultLocales;
|
||||
@Nullable private TextClassifier.EntityConfig mEntityConfig;
|
||||
private boolean mLegacyFallback = true; // Use legacy fall back by default.
|
||||
private String mCallingPackageName;
|
||||
|
||||
public Builder(@NonNull CharSequence text) {
|
||||
mText = Preconditions.checkNotNull(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 TextClassifier.EntityConfig entityConfig) {
|
||||
mEntityConfig = entityConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the TextClassifier can fallback to legacy links if smart linkify is
|
||||
* disabled.
|
||||
*
|
||||
* <p><strong>Note: </strong>This is not parcelled.
|
||||
*
|
||||
* @return this builder
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setLegacyFallback(boolean legacyFallback) {
|
||||
mLegacyFallback = legacyFallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the package that requested the links to get generated.
|
||||
*
|
||||
* @return this builder
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setCallingPackageName(@Nullable String callingPackageName) {
|
||||
mCallingPackageName = callingPackageName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the request object.
|
||||
*/
|
||||
@NonNull
|
||||
public Request build() {
|
||||
return new Request(
|
||||
mText, mDefaultLocales, mEntityConfig,
|
||||
mLegacyFallback, mCallingPackageName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,6 +459,7 @@ public final class TextLinks implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mText.toString());
|
||||
dest.writeInt(mDefaultLocales != null ? 1 : 0);
|
||||
if (mDefaultLocales != null) {
|
||||
mDefaultLocales.writeToParcel(dest, flags);
|
||||
@@ -484,41 +468,32 @@ public final class TextLinks implements Parcelable {
|
||||
if (mEntityConfig != null) {
|
||||
mEntityConfig.writeToParcel(dest, flags);
|
||||
}
|
||||
dest.writeInt(mApplyStrategy);
|
||||
dest.writeString(mCallingPackageName);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Options> CREATOR =
|
||||
new Parcelable.Creator<Options>() {
|
||||
public static final Parcelable.Creator<Request> CREATOR =
|
||||
new Parcelable.Creator<Request>() {
|
||||
@Override
|
||||
public Options createFromParcel(Parcel in) {
|
||||
return new Options(in);
|
||||
public Request createFromParcel(Parcel in) {
|
||||
return new Request(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options[] newArray(int size) {
|
||||
return new Options[size];
|
||||
public Request[] newArray(int size) {
|
||||
return new Request[size];
|
||||
}
|
||||
};
|
||||
|
||||
private Options(Parcel in) {
|
||||
if (in.readInt() > 0) {
|
||||
mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
|
||||
}
|
||||
if (in.readInt() > 0) {
|
||||
mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
|
||||
}
|
||||
mApplyStrategy = in.readInt();
|
||||
private Request(Parcel in) {
|
||||
mText = in.readString();
|
||||
mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
|
||||
mEntityConfig = in.readInt() == 0
|
||||
? null : TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
|
||||
mLegacyFallback = true;
|
||||
mCallingPackageName = in.readString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to create spans from TextLinks.
|
||||
*/
|
||||
private static final Function<TextLink, TextLinkSpan> DEFAULT_SPAN_FACTORY =
|
||||
textLink -> new TextLinkSpan(textLink);
|
||||
|
||||
/**
|
||||
* A ClickableSpan for a TextLink.
|
||||
*
|
||||
@@ -596,6 +571,7 @@ public final class TextLinks implements Parcelable {
|
||||
*
|
||||
* @throws IllegalArgumentException if entityScores is null or empty.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addLink(int start, int end, Map<String, Float> entityScores) {
|
||||
mLinks.add(new TextLink(start, end, entityScores, null));
|
||||
return this;
|
||||
@@ -605,6 +581,7 @@ public final class TextLinks implements Parcelable {
|
||||
* @see #addLink(int, int, Map)
|
||||
* @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled.
|
||||
*/
|
||||
@NonNull
|
||||
Builder addLink(int start, int end, Map<String, Float> entityScores,
|
||||
@Nullable URLSpan urlSpan) {
|
||||
mLinks.add(new TextLink(start, end, entityScores, urlSpan));
|
||||
@@ -614,6 +591,7 @@ public final class TextLinks implements Parcelable {
|
||||
/**
|
||||
* Removes all {@link TextLink}s.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder clearTextLinks() {
|
||||
mLinks.clear();
|
||||
return this;
|
||||
@@ -624,18 +602,9 @@ public final class TextLinks implements Parcelable {
|
||||
*
|
||||
* @return the constructed TextLinks
|
||||
*/
|
||||
@NonNull
|
||||
public TextLinks build() {
|
||||
return new TextLinks(mFullText, mLinks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the value is invalid
|
||||
*/
|
||||
private static void checkValidApplyStrategy(int applyStrategy) {
|
||||
if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid apply strategy. See TextLinks.ApplyStrategy for options.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
206
core/java/android/view/textclassifier/TextLinksParams.java
Normal file
206
core/java/android/view/textclassifier/TextLinksParams.java
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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 (!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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
package android.view.textclassifier;
|
||||
|
||||
parcelable TextSelection;
|
||||
parcelable TextSelection.Options;
|
||||
parcelable TextSelection.Request;
|
||||
@@ -25,6 +25,7 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.textclassifier.TextClassifier.EntityType;
|
||||
import android.view.textclassifier.TextClassifier.Utils;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -38,16 +39,15 @@ public final class TextSelection implements Parcelable {
|
||||
|
||||
private final int mStartIndex;
|
||||
private final int mEndIndex;
|
||||
@NonNull private final EntityConfidence mEntityConfidence;
|
||||
@NonNull private final String mSignature;
|
||||
private final EntityConfidence mEntityConfidence;
|
||||
@Nullable private final String mId;
|
||||
|
||||
private TextSelection(
|
||||
int startIndex, int endIndex, @NonNull Map<String, Float> entityConfidence,
|
||||
@NonNull String signature) {
|
||||
int startIndex, int endIndex, Map<String, Float> entityConfidence, String id) {
|
||||
mStartIndex = startIndex;
|
||||
mEndIndex = endIndex;
|
||||
mEntityConfidence = new EntityConfidence(entityConfidence);
|
||||
mSignature = signature;
|
||||
mId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +80,8 @@ public final class TextSelection implements Parcelable {
|
||||
* @see #getEntityCount() for the number of entities available.
|
||||
*/
|
||||
@NonNull
|
||||
public @EntityType String getEntity(int index) {
|
||||
@EntityType
|
||||
public String getEntity(int index) {
|
||||
return mEntityConfidence.getEntities().get(index);
|
||||
}
|
||||
|
||||
@@ -95,21 +96,19 @@ public final class TextSelection implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature for this object.
|
||||
* The TextClassifier that generates this object may use it as a way to internally identify
|
||||
* this object.
|
||||
* Returns the id, if one exists, for this object.
|
||||
*/
|
||||
@NonNull
|
||||
public String getSignature() {
|
||||
return mSignature;
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
Locale.US,
|
||||
"TextSelection {startIndex=%d, endIndex=%d, entities=%s, signature=%s}",
|
||||
mStartIndex, mEndIndex, mEntityConfidence, mSignature);
|
||||
"TextSelection {id=%s, startIndex=%d, endIndex=%d, entities=%s}",
|
||||
mId, mStartIndex, mEndIndex, mEntityConfidence);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,8 +118,8 @@ public final class TextSelection implements Parcelable {
|
||||
|
||||
private final int mStartIndex;
|
||||
private final int mEndIndex;
|
||||
@NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
|
||||
@NonNull private String mSignature = "";
|
||||
private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
|
||||
@Nullable private String mId;
|
||||
|
||||
/**
|
||||
* Creates a builder used to build {@link TextSelection} objects.
|
||||
@@ -142,56 +141,96 @@ public final class TextSelection implements Parcelable {
|
||||
* 0 implies the entity does not exist for the classified text.
|
||||
* Values greater than 1 are clamped to 1.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setEntityType(
|
||||
@NonNull @EntityType String type,
|
||||
@FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
|
||||
Preconditions.checkNotNull(type);
|
||||
mEntityConfidence.put(type, confidenceScore);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a signature for the TextSelection object.
|
||||
*
|
||||
* The TextClassifier that generates the TextSelection object may use it as a way to
|
||||
* internally identify the TextSelection object.
|
||||
* Sets an id for the TextSelection object.
|
||||
*/
|
||||
public Builder setSignature(@NonNull String signature) {
|
||||
mSignature = Preconditions.checkNotNull(signature);
|
||||
@NonNull
|
||||
public Builder setId(@NonNull String id) {
|
||||
mId = Preconditions.checkNotNull(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns {@link TextSelection} object.
|
||||
*/
|
||||
@NonNull
|
||||
public TextSelection build() {
|
||||
return new TextSelection(
|
||||
mStartIndex, mEndIndex, mEntityConfidence, mSignature);
|
||||
mStartIndex, mEndIndex, mEntityConfidence, mId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional input parameters for generating TextSelection.
|
||||
* A request object for generating TextSelection.
|
||||
*/
|
||||
public static final class Options implements Parcelable {
|
||||
public static final class Request implements Parcelable {
|
||||
|
||||
private @Nullable LocaleList mDefaultLocales;
|
||||
private boolean mDarkLaunchAllowed;
|
||||
private final CharSequence mText;
|
||||
private final int mStartIndex;
|
||||
private final int mEndIndex;
|
||||
@Nullable private final LocaleList mDefaultLocales;
|
||||
private final boolean mDarkLaunchAllowed;
|
||||
|
||||
public Options() {}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
|
||||
private Request(
|
||||
CharSequence text,
|
||||
int startIndex,
|
||||
int endIndex,
|
||||
LocaleList defaultLocales,
|
||||
boolean darkLaunchAllowed) {
|
||||
mText = text;
|
||||
mStartIndex = startIndex;
|
||||
mEndIndex = endIndex;
|
||||
mDefaultLocales = defaultLocales;
|
||||
return this;
|
||||
mDarkLaunchAllowed = darkLaunchAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ordered list of locale preferences that can be used to disambiguate
|
||||
* the provided text.
|
||||
* Returns the text providing context for the selected text (which is specified by the
|
||||
* sub sequence starting at startIndex and ending at endIndex).
|
||||
*/
|
||||
@NonNull
|
||||
public CharSequence getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns start index of the selected part of text.
|
||||
*/
|
||||
@IntRange(from = 0)
|
||||
public int getStartIndex() {
|
||||
return mStartIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns end index of the selected part of text.
|
||||
*/
|
||||
@IntRange(from = 0)
|
||||
public int getEndIndex() {
|
||||
return mEndIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the TextClassifier should return selection suggestions when "dark
|
||||
* launched". Otherwise, returns false.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean isDarkLaunchAllowed() {
|
||||
return mDarkLaunchAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ordered list of locale preferences that can be used to disambiguate the
|
||||
* provided text.
|
||||
*/
|
||||
@Nullable
|
||||
public LocaleList getDefaultLocales() {
|
||||
@@ -199,26 +238,71 @@ public final class TextSelection implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param allowed whether or not the TextClassifier should return selection suggestions
|
||||
* when "dark launched". When a TextClassifier is dark launched, it can suggest
|
||||
* selection changes that should not be used to actually change the user's selection.
|
||||
* Instead, the suggested selection is logged, compared with the user's selection
|
||||
* interaction, and used to generate quality metrics for the TextClassifier.
|
||||
*
|
||||
* @hide
|
||||
* A builder for building TextSelection requests.
|
||||
*/
|
||||
public void setDarkLaunchAllowed(boolean allowed) {
|
||||
mDarkLaunchAllowed = allowed;
|
||||
}
|
||||
public static final class Builder {
|
||||
|
||||
/**
|
||||
* Returns true if the TextClassifier should return selection suggestions when
|
||||
* "dark launched". Otherwise, returns false.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean isDarkLaunchAllowed() {
|
||||
return mDarkLaunchAllowed;
|
||||
private final CharSequence mText;
|
||||
private final int mStartIndex;
|
||||
private final int mEndIndex;
|
||||
|
||||
@Nullable private LocaleList mDefaultLocales;
|
||||
private boolean mDarkLaunchAllowed;
|
||||
|
||||
/**
|
||||
* @param text text providing context for the selected text (which is specified by the
|
||||
* sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||
* @param startIndex start index of the selected part of text
|
||||
* @param endIndex end index of the selected part of text
|
||||
*/
|
||||
public Builder(
|
||||
@NonNull CharSequence text,
|
||||
@IntRange(from = 0) int startIndex,
|
||||
@IntRange(from = 0) int endIndex) {
|
||||
Utils.checkArgument(text, startIndex, endIndex);
|
||||
mText = text;
|
||||
mStartIndex = startIndex;
|
||||
mEndIndex = endIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param allowed whether or not the TextClassifier should return selection suggestions
|
||||
* when "dark launched". When a TextClassifier is dark launched, it can suggest
|
||||
* selection changes that should not be used to actually change the user's
|
||||
* selection. Instead, the suggested selection is logged, compared with the user's
|
||||
* selection interaction, and used to generate quality metrics for the
|
||||
* TextClassifier. Not parceled.
|
||||
*
|
||||
* @return this builder.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setDarkLaunchAllowed(boolean allowed) {
|
||||
mDarkLaunchAllowed = allowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the request object.
|
||||
*/
|
||||
@NonNull
|
||||
public Request build() {
|
||||
return new Request(mText, mStartIndex, mEndIndex,
|
||||
mDefaultLocales, mDarkLaunchAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -228,31 +312,34 @@ public final class TextSelection implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mText.toString());
|
||||
dest.writeInt(mStartIndex);
|
||||
dest.writeInt(mEndIndex);
|
||||
dest.writeInt(mDefaultLocales != null ? 1 : 0);
|
||||
if (mDefaultLocales != null) {
|
||||
mDefaultLocales.writeToParcel(dest, flags);
|
||||
}
|
||||
dest.writeInt(mDarkLaunchAllowed ? 1 : 0);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Options> CREATOR =
|
||||
new Parcelable.Creator<Options>() {
|
||||
public static final Parcelable.Creator<Request> CREATOR =
|
||||
new Parcelable.Creator<Request>() {
|
||||
@Override
|
||||
public Options createFromParcel(Parcel in) {
|
||||
return new Options(in);
|
||||
public Request createFromParcel(Parcel in) {
|
||||
return new Request(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options[] newArray(int size) {
|
||||
return new Options[size];
|
||||
public Request[] newArray(int size) {
|
||||
return new Request[size];
|
||||
}
|
||||
};
|
||||
|
||||
private Options(Parcel in) {
|
||||
if (in.readInt() > 0) {
|
||||
mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
|
||||
}
|
||||
mDarkLaunchAllowed = in.readInt() != 0;
|
||||
private Request(Parcel in) {
|
||||
mText = in.readString();
|
||||
mStartIndex = in.readInt();
|
||||
mEndIndex = in.readInt();
|
||||
mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
|
||||
mDarkLaunchAllowed = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +353,7 @@ public final class TextSelection implements Parcelable {
|
||||
dest.writeInt(mStartIndex);
|
||||
dest.writeInt(mEndIndex);
|
||||
mEntityConfidence.writeToParcel(dest, flags);
|
||||
dest.writeString(mSignature);
|
||||
dest.writeString(mId);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<TextSelection> CREATOR =
|
||||
@@ -286,6 +373,6 @@ public final class TextSelection implements Parcelable {
|
||||
mStartIndex = in.readInt();
|
||||
mEndIndex = in.readInt();
|
||||
mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
|
||||
mSignature = in.readString();
|
||||
mId = in.readString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ public final class SmartSelectionEventTracker {
|
||||
final String entityType = classification.getEntityCount() > 0
|
||||
? classification.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
final String versionTag = getVersionInfo(classification.getSignature());
|
||||
final String versionTag = getVersionInfo(classification.getId());
|
||||
return new SelectionEvent(
|
||||
start, end, EventType.SELECTION_MODIFIED, entityType, versionTag);
|
||||
}
|
||||
@@ -489,7 +489,7 @@ public final class SmartSelectionEventTracker {
|
||||
*/
|
||||
public static SelectionEvent selectionModified(
|
||||
int start, int end, @NonNull TextSelection selection) {
|
||||
final boolean smartSelection = getSourceClassifier(selection.getSignature())
|
||||
final boolean smartSelection = getSourceClassifier(selection.getId())
|
||||
.equals(TextClassifier.DEFAULT_LOG_TAG);
|
||||
final int eventType;
|
||||
if (smartSelection) {
|
||||
@@ -503,7 +503,7 @@ public final class SmartSelectionEventTracker {
|
||||
final String entityType = selection.getEntityCount() > 0
|
||||
? selection.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
final String versionTag = getVersionInfo(selection.getSignature());
|
||||
final String versionTag = getVersionInfo(selection.getId());
|
||||
return new SelectionEvent(start, end, eventType, entityType, versionTag);
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ public final class SmartSelectionEventTracker {
|
||||
final String entityType = classification.getEntityCount() > 0
|
||||
? classification.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
final String versionTag = getVersionInfo(classification.getSignature());
|
||||
final String versionTag = getVersionInfo(classification.getId());
|
||||
return new SelectionEvent(start, end, actionType, entityType, versionTag);
|
||||
}
|
||||
|
||||
|
||||
@@ -499,7 +499,8 @@ public final class SelectionActionModeHelper {
|
||||
mOriginalEnd = mSelectionEnd = selectionEnd;
|
||||
mAllowReset = false;
|
||||
maybeInvalidateLogger();
|
||||
mLogger.logSelectionStarted(text, selectionStart,
|
||||
mLogger.logSelectionStarted(mTextView.getTextClassificationSession(),
|
||||
text, selectionStart,
|
||||
isLink ? SelectionEvent.INVOCATION_LINK : SelectionEvent.INVOCATION_MANUAL);
|
||||
}
|
||||
|
||||
@@ -633,6 +634,7 @@ public final class SelectionActionModeHelper {
|
||||
mSelectionStart, mSelectionEnd,
|
||||
SelectionEvent.ACTION_ABANDON, null /* classification */);
|
||||
mSelectionStart = mSelectionEnd = -1;
|
||||
mTextView.getTextClassificationSession().destroy();
|
||||
mIsPending = false;
|
||||
}
|
||||
}
|
||||
@@ -661,16 +663,16 @@ public final class SelectionActionModeHelper {
|
||||
private static final String LOG_TAG = "SelectionMetricsLogger";
|
||||
private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+");
|
||||
|
||||
private final Supplier<TextClassifier> mTextClassificationSession;
|
||||
private final Logger mLogger;
|
||||
private final boolean mEditTextLogger;
|
||||
private final BreakIterator mTokenIterator;
|
||||
|
||||
@Nullable private TextClassifier mClassificationSession;
|
||||
private int mStartIndex;
|
||||
private String mText;
|
||||
|
||||
SelectionMetricsLogger(TextView textView) {
|
||||
Preconditions.checkNotNull(textView);
|
||||
mTextClassificationSession = textView::getTextClassificationSession;
|
||||
mLogger = textView.getTextClassifier().getLogger(
|
||||
new Logger.Config(textView.getContext(), getWidetType(textView), null));
|
||||
mEditTextLogger = textView.isTextEditable();
|
||||
@@ -689,6 +691,7 @@ public final class SelectionActionModeHelper {
|
||||
}
|
||||
|
||||
public void logSelectionStarted(
|
||||
TextClassifier classificationSession,
|
||||
CharSequence text, int index,
|
||||
@InvocationMethod int invocationMethod) {
|
||||
try {
|
||||
@@ -701,7 +704,8 @@ public final class SelectionActionModeHelper {
|
||||
mStartIndex = index;
|
||||
mLogger.logSelectionStartedEvent(invocationMethod, 0);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
mClassificationSession = classificationSession;
|
||||
mClassificationSession.onSelectionEvent(
|
||||
SelectionEvent.createSelectionStartedEvent(invocationMethod, 0));
|
||||
} catch (Exception e) {
|
||||
// Avoid crashes due to logging.
|
||||
@@ -719,23 +723,29 @@ public final class SelectionActionModeHelper {
|
||||
mLogger.logSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], selection);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], selection));
|
||||
if (mClassificationSession != null) {
|
||||
mClassificationSession.onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], selection));
|
||||
}
|
||||
} else if (classification != null) {
|
||||
mLogger.logSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], classification);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], classification));
|
||||
if (mClassificationSession != null) {
|
||||
mClassificationSession.onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], classification));
|
||||
}
|
||||
} else {
|
||||
mLogger.logSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1]);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1]));
|
||||
if (mClassificationSession != null) {
|
||||
mClassificationSession.onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1]));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Avoid crashes due to logging.
|
||||
@@ -755,24 +765,24 @@ public final class SelectionActionModeHelper {
|
||||
mLogger.logSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action, classification);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action, classification));
|
||||
if (mClassificationSession != null) {
|
||||
mClassificationSession.onSelectionEvent(
|
||||
SelectionEvent.createSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action, classification));
|
||||
}
|
||||
} else {
|
||||
mLogger.logSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action));
|
||||
if (mClassificationSession != null) {
|
||||
mClassificationSession.onSelectionEvent(
|
||||
SelectionEvent.createSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Avoid crashes due to logging.
|
||||
Log.e(LOG_TAG, "" + e.getMessage(), e);
|
||||
} finally {
|
||||
if (SelectionEvent.isTerminal(action)) {
|
||||
mTextClassificationSession.get().destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,9 +936,8 @@ public final class SelectionActionModeHelper {
|
||||
/** End index relative to mText. */
|
||||
private int mSelectionEnd;
|
||||
|
||||
private final TextSelection.Options mSelectionOptions = new TextSelection.Options();
|
||||
private final TextClassification.Options mClassificationOptions =
|
||||
new TextClassification.Options();
|
||||
@Nullable
|
||||
private LocaleList mDefaultLocales;
|
||||
|
||||
/** Trimmed text starting from mTrimStart in mText. */
|
||||
private CharSequence mTrimmedText;
|
||||
@@ -966,9 +975,7 @@ public final class SelectionActionModeHelper {
|
||||
Preconditions.checkArgument(selectionEnd > selectionStart);
|
||||
mSelectionStart = selectionStart;
|
||||
mSelectionEnd = selectionEnd;
|
||||
mClassificationOptions.setDefaultLocales(locales);
|
||||
mSelectionOptions.setDefaultLocales(locales)
|
||||
.setDarkLaunchAllowed(true);
|
||||
mDefaultLocales = locales;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -983,13 +990,16 @@ public final class SelectionActionModeHelper {
|
||||
trimText();
|
||||
final TextSelection selection;
|
||||
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
||||
selection = mTextClassifier.get().suggestSelection(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
|
||||
final TextSelection.Request request = new TextSelection.Request.Builder(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd)
|
||||
.setDefaultLocales(mDefaultLocales)
|
||||
.setDarkLaunchAllowed(true)
|
||||
.build();
|
||||
selection = mTextClassifier.get().suggestSelection(request);
|
||||
} else {
|
||||
// Use old APIs.
|
||||
selection = mTextClassifier.get().suggestSelection(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd,
|
||||
mSelectionOptions.getDefaultLocales());
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd, mDefaultLocales);
|
||||
}
|
||||
// Do not classify new selection boundaries if TextClassifier should be dark launched.
|
||||
if (!mDarkLaunchEnabled) {
|
||||
@@ -1024,25 +1034,26 @@ public final class SelectionActionModeHelper {
|
||||
if (!Objects.equals(mText, mLastClassificationText)
|
||||
|| mSelectionStart != mLastClassificationSelectionStart
|
||||
|| mSelectionEnd != mLastClassificationSelectionEnd
|
||||
|| !Objects.equals(
|
||||
mClassificationOptions.getDefaultLocales(),
|
||||
mLastClassificationLocales)) {
|
||||
|| !Objects.equals(mDefaultLocales, mLastClassificationLocales)) {
|
||||
|
||||
mLastClassificationText = mText;
|
||||
mLastClassificationSelectionStart = mSelectionStart;
|
||||
mLastClassificationSelectionEnd = mSelectionEnd;
|
||||
mLastClassificationLocales = mClassificationOptions.getDefaultLocales();
|
||||
mLastClassificationLocales = mDefaultLocales;
|
||||
|
||||
trimText();
|
||||
final TextClassification classification;
|
||||
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
||||
classification = mTextClassifier.get().classifyText(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
|
||||
final TextClassification.Request request =
|
||||
new TextClassification.Request.Builder(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd)
|
||||
.setDefaultLocales(mDefaultLocales)
|
||||
.build();
|
||||
classification = mTextClassifier.get().classifyText(request);
|
||||
} else {
|
||||
// Use old APIs.
|
||||
classification = mTextClassifier.get().classifyText(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd,
|
||||
mClassificationOptions.getDefaultLocales());
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd, mDefaultLocales);
|
||||
}
|
||||
mLastClassificationResult = new SelectionResult(
|
||||
mSelectionStart, mSelectionEnd, classification, selection);
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.annotation.StringRes;
|
||||
import android.annotation.StyleRes;
|
||||
import android.annotation.XmlRes;
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
@@ -11541,6 +11542,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
|
||||
/**
|
||||
* Returns a session-aware text classifier.
|
||||
* This method creates one if none already exists or the current one is destroyed.
|
||||
*/
|
||||
@NonNull
|
||||
TextClassifier getTextClassificationSession() {
|
||||
@@ -11623,15 +11625,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
final int start = spanned.getSpanStart(clickedSpan);
|
||||
final int end = spanned.getSpanEnd(clickedSpan);
|
||||
if (start >= 0 && end <= mText.length() && start < end) {
|
||||
final TextClassification.Options options = new TextClassification.Options()
|
||||
.setDefaultLocales(getTextLocales());
|
||||
final TextClassification.Request request = new TextClassification.Request.Builder(
|
||||
mText, start, end)
|
||||
.setDefaultLocales(getTextLocales())
|
||||
.build();
|
||||
final Supplier<TextClassification> supplier = () ->
|
||||
getTextClassifier().classifyText(mText, start, end, options);
|
||||
getTextClassifier().classifyText(request);
|
||||
final Consumer<TextClassification> consumer = classification -> {
|
||||
if (classification != null) {
|
||||
final Intent intent = classification.getIntent();
|
||||
if (intent != null) {
|
||||
TextClassification.fireIntent(mContext, intent);
|
||||
if (!classification.getActions().isEmpty()) {
|
||||
try {
|
||||
classification.getActions().get(0).getActionIntent().send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(LOG_TAG, "Error sending PendingIntent", e);
|
||||
}
|
||||
} else {
|
||||
Log.d(LOG_TAG, "No link action to perform");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user