summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/service/textclassifier/ITextClassifierService.aidl33
-rw-r--r--core/java/android/service/textclassifier/TextClassifierService.java110
-rw-r--r--core/java/android/text/util/Linkify.java80
-rw-r--r--core/java/android/view/textclassifier/DefaultLogger.java8
-rw-r--r--core/java/android/view/textclassifier/LinksInfo.java42
-rw-r--r--core/java/android/view/textclassifier/Log.java4
-rw-r--r--core/java/android/view/textclassifier/Logger.java27
-rw-r--r--core/java/android/view/textclassifier/SelectionEvent.java55
-rw-r--r--core/java/android/view/textclassifier/SystemTextClassifier.java101
-rw-r--r--core/java/android/view/textclassifier/TextClassification.aidl2
-rw-r--r--core/java/android/view/textclassifier/TextClassification.java214
-rw-r--r--core/java/android/view/textclassifier/TextClassificationContext.aidl19
-rw-r--r--core/java/android/view/textclassifier/TextClassificationContext.java35
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSession.java33
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSessionId.aidl19
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSessionId.java6
-rw-r--r--core/java/android/view/textclassifier/TextClassifier.java215
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java118
-rw-r--r--core/java/android/view/textclassifier/TextLinks.aidl2
-rw-r--r--core/java/android/view/textclassifier/TextLinks.java349
-rw-r--r--core/java/android/view/textclassifier/TextLinksParams.java206
-rw-r--r--core/java/android/view/textclassifier/TextSelection.aidl2
-rw-r--r--core/java/android/view/textclassifier/TextSelection.java217
-rw-r--r--core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java8
-rw-r--r--core/java/android/widget/SelectionActionModeHelper.java93
-rw-r--r--core/java/android/widget/TextView.java19
26 files changed, 1196 insertions, 821 deletions
diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index 25e9d454efb5..7ac72c7c8e83 100644
--- a/core/java/android/service/textclassifier/ITextClassifierService.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl
@@ -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);
}
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 88e29b0d3d11..f1bb72cfacf1 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -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(
+ @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 void onSelectionEvent(@NonNull SelectionEvent event) {}
+ public abstract void onDestroyTextClassificationSession(
+ @NonNull TextClassificationSessionId sessionId);
/**
* Returns a TextClassifier that runs in this service's process.
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 435e32456343..9c6a3f5f8c72 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -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 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,
+ @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 TextLinks.Options options,
+ @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();
diff --git a/core/java/android/view/textclassifier/DefaultLogger.java b/core/java/android/view/textclassifier/DefaultLogger.java
index 46ff44246280..203ca5606818 100644
--- a/core/java/android/view/textclassifier/DefaultLogger.java
+++ b/core/java/android/view/textclassifier/DefaultLogger.java
@@ -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
diff --git a/core/java/android/view/textclassifier/LinksInfo.java b/core/java/android/view/textclassifier/LinksInfo.java
deleted file mode 100644
index 754c9e90ed4b..000000000000
--- a/core/java/android/view/textclassifier/LinksInfo.java
+++ /dev/null
@@ -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);
-}
diff --git a/core/java/android/view/textclassifier/Log.java b/core/java/android/view/textclassifier/Log.java
index 83ca15df92f4..ef19ee56a843 100644
--- a/core/java/android/view/textclassifier/Log.java
+++ b/core/java/android/view/textclassifier/Log.java
@@ -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);
diff --git a/core/java/android/view/textclassifier/Logger.java b/core/java/android/view/textclassifier/Logger.java
index c29d3e64a8b7..f03906a0b5ea 100644
--- a/core/java/android/view/textclassifier/Logger.java
+++ b/core/java/android/view/textclassifier/Logger.java
@@ -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());
}
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 5a4d2cf1d481..1e978ccf5390 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -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];
}
};
-}
+} \ No newline at end of file
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 2a24dd731b4d..45fd6bfb1949 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -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<>();
diff --git a/core/java/android/view/textclassifier/TextClassification.aidl b/core/java/android/view/textclassifier/TextClassification.aidl
index 9fefe5d4176a..bfb143c4d0ac 100644
--- a/core/java/android/view/textclassifier/TextClassification.aidl
+++ b/core/java/android/view/textclassifier/TextClassification.aidl
@@ -17,4 +17,4 @@
package android.view.textclassifier;
parcelable TextClassification;
-parcelable TextClassification.Options; \ No newline at end of file
+parcelable TextClassification.Request; \ No newline at end of file
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index b413d48c6c78..37a5d9a10743 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -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 {
-
- private @Nullable LocaleList mDefaultLocales;
- private @Nullable ZonedDateTime mReferenceTime;
+ public static final class Request implements Parcelable {
+
+ private final CharSequence mText;
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @Nullable private final LocaleList mDefaultLocales;
+ @Nullable private final ZonedDateTime mReferenceTime;
+
+ private Request(
+ CharSequence text,
+ int startIndex,
+ int endIndex,
+ LocaleList defaultLocales,
+ ZonedDateTime referenceTime) {
+ mText = text;
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mDefaultLocales = defaultLocales;
+ mReferenceTime = referenceTime;
+ }
- public Options() {}
+ /**
+ * Returns the text providing context for the text to classify (which is specified
+ * by the sub sequence starting at startIndex and ending at endIndex)
+ */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
/**
- * @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.
+ * Returns start index of the text to classify.
*/
- public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
- mDefaultLocales = defaultLocales;
- return this;
+ @IntRange(from = 0)
+ public int getStartIndex() {
+ return mStartIndex;
}
/**
- * @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 end index of the text to classify.
*/
- public Options setReferenceTime(ZonedDateTime referenceTime) {
- mReferenceTime = referenceTime;
- return this;
+ @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();
}
}
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.aidl b/core/java/android/view/textclassifier/TextClassificationContext.aidl
new file mode 100644
index 000000000000..0d6b03318ab9
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationContext.aidl
@@ -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;
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index a88f2f66d359..a15411f0416e 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -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];
+ }
+ };
}
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index 6938e1ae11c4..e8e300a94de4 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -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 {
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.aidl b/core/java/android/view/textclassifier/TextClassificationSessionId.aidl
new file mode 100644
index 000000000000..1bbf2da09be5
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.aidl
@@ -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;
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java
index 3e4dc1c52b4d..1378bd9c9035 100644
--- a/core/java/android/view/textclassifier/TextClassificationSessionId.java
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java
@@ -22,6 +22,7 @@ import android.os.Parcelable;
import com.android.internal.util.Preconditions;
+import java.util.Locale;
import java.util.UUID;
/**
@@ -77,6 +78,11 @@ public final class TextClassificationSessionId implements Parcelable {
}
@Override
+ public String toString() {
+ return String.format(Locale.US, "TextClassificationSessionId {%s}", mValue);
+ }
+
+ @Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mValue);
}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 2048f2b49182..54261be33607 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -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,36 +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);
- }
-
- /**
- * 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.
- *
- * <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
- *
- * @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();
+ final TextClassification.Request request = new TextClassification.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(defaultLocales)
+ .build();
+ return classifyText(request);
}
/**
@@ -361,25 +273,19 @@ 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 #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
+ * @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, 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");
}
}
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 8d1ed0eb68cb..7e3748ae76cd 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -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;
}
diff --git a/core/java/android/view/textclassifier/TextLinks.aidl b/core/java/android/view/textclassifier/TextLinks.aidl
index 1bbb79845528..5de2c77758c2 100644
--- a/core/java/android/view/textclassifier/TextLinks.aidl
+++ b/core/java/android/view/textclassifier/TextLinks.aidl
@@ -17,4 +17,4 @@
package android.view.textclassifier;
parcelable TextLinks;
-parcelable TextLinks.Options; \ No newline at end of file
+parcelable TextLinks.Request; \ No newline at end of file
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 38a7d9aa1d2b..17c7b13cdc85 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -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;
@@ -99,70 +99,54 @@ public final class TextLinks implements Parcelable {
}
/**
+ * 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
@@ -271,6 +255,13 @@ public final class TextLinks implements Parcelable {
}
@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;
+ mCallingPackageName = callingPackageName;
}
/**
- * Sets the name of the package that requested the links to get generated.
- * @hide
+ * Returns the text to generate links for.
*/
- public Options setCallingPackageName(@Nullable String callingPackageName) {
- mCallingPackageName = callingPackageName;
- return this;
+ @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,42 +468,33 @@ 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.
*
* <p>Applies only to TextViews.
@@ -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.");
- }
- }
}
diff --git a/core/java/android/view/textclassifier/TextLinksParams.java b/core/java/android/view/textclassifier/TextLinksParams.java
new file mode 100644
index 000000000000..be4c3bcdff67
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextLinksParams.java
@@ -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;
+ }
+}
+
diff --git a/core/java/android/view/textclassifier/TextSelection.aidl b/core/java/android/view/textclassifier/TextSelection.aidl
index dab1aefa3532..b2fd9beeafe3 100644
--- a/core/java/android/view/textclassifier/TextSelection.aidl
+++ b/core/java/android/view/textclassifier/TextSelection.aidl
@@ -17,4 +17,4 @@
package android.view.textclassifier;
parcelable TextSelection;
-parcelable TextSelection.Options; \ No newline at end of file
+parcelable TextSelection.Request; \ No newline at end of file
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 1c93be75a3a1..939e71760dcd 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -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,78 +141,86 @@ 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 {
-
- private @Nullable LocaleList mDefaultLocales;
- private boolean mDarkLaunchAllowed;
+ public static final class Request implements Parcelable {
- public Options() {}
+ private final CharSequence mText;
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @Nullable private final LocaleList mDefaultLocales;
+ private final boolean mDarkLaunchAllowed;
+
+ private Request(
+ CharSequence text,
+ int startIndex,
+ int endIndex,
+ LocaleList defaultLocales,
+ boolean darkLaunchAllowed) {
+ mText = text;
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mDefaultLocales = defaultLocales;
+ mDarkLaunchAllowed = darkLaunchAllowed;
+ }
/**
- * @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.
+ * Returns the text providing context for the selected text (which is specified by the
+ * sub sequence starting at startIndex and ending at endIndex).
*/
- public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
- mDefaultLocales = defaultLocales;
- return this;
+ @NonNull
+ public CharSequence getText() {
+ return mText;
}
/**
- * @return ordered list of locale preferences that can be used to disambiguate
- * the provided text.
+ * Returns start index of the selected part of text.
*/
- @Nullable
- public LocaleList getDefaultLocales() {
- return mDefaultLocales;
+ @IntRange(from = 0)
+ public int getStartIndex() {
+ return mStartIndex;
}
/**
- * @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
+ * Returns end index of the selected part of text.
*/
- public void setDarkLaunchAllowed(boolean allowed) {
- mDarkLaunchAllowed = allowed;
+ @IntRange(from = 0)
+ public int getEndIndex() {
+ return mEndIndex;
}
/**
- * Returns true if the TextClassifier should return selection suggestions when
- * "dark launched". Otherwise, returns false.
+ * Returns true if the TextClassifier should return selection suggestions when "dark
+ * launched". Otherwise, returns false.
*
* @hide
*/
@@ -221,6 +228,83 @@ public final class TextSelection implements Parcelable {
return mDarkLaunchAllowed;
}
+ /**
+ * @return ordered list of locale preferences that can be used to disambiguate the
+ * provided text.
+ */
+ @Nullable
+ public LocaleList getDefaultLocales() {
+ return mDefaultLocales;
+ }
+
+ /**
+ * A builder for building TextSelection requests.
+ */
+ public static final class Builder {
+
+ 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
public int describeContents() {
return 0;
@@ -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();
}
}
diff --git a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 157b3d82163b..f7d75cd89537 100644
--- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -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);
}
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 8b49ccbaafdf..b3327a70d579 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -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);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 8bf497e0e90d..11db6b650583 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -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");
}