TextClassifier API updates.

1. Wraps TC queries in Request objects
2. Adds create/destroyTextClassificationSession system APIs
3. Adds the session Ids to system API calls
4. Change setSignature() to setId() on result objects
5. Plumbing to make the API updates work as things currently work
6. Hide Linkify.addLinksAsync APIs

Bug: 74461129

Test: bit FrameworksCoreTests:android.view.textclassifier.TextClassificationManagerTest
Test: bit CtsViewTestCases:android.view.textclassifier.cts.TextClassificationManagerTest
Test: bit CtsWidgetTestCases:android.widget.cts.TextViewTest
Test: bit FrameworksCoreTests:android.widget.TextViewActivityTest
Test: bit FrameworksCoreTests:android.view.textclassifier.TextClassificationTest
Test: bit FrameworksCoreTests:android.view.textclassifier.TextSelectionTest
Test: bit FrameworksCoreTests:android.view.textclassifier.TextLinksTest

Change-Id: I933ada8b37ef9893331a265e3b4fc08e043f1029
diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index 25e9d45..7ac72c7 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.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 88e29b0..f1bb72c 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.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 @@
         /** {@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 @@
         /** {@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 @@
         /** {@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 @@
 
         /** {@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 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * happened.
      *
      * <p>The default implementation ignores the event.
+     *
+     * @param sessionId the session id
+     * @param event the selection event
      */
-    public void onSelectionEvent(@NonNull SelectionEvent event) {}
+    public void onSelectionEvent(
+            @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}
+
+    /**
+     * Creates a new text classification session for the specified context.
+     *
+     * @param context the text classification context
+     * @param sessionId the session's Id
+     */
+    public abstract void onCreateTextClassificationSession(
+            @NonNull TextClassificationContext context,
+            @NonNull TextClassificationSessionId sessionId);
+
+    /**
+     * Destroys the text classification session identified by the specified sessionId.
+     *
+     * @param sessionId the id of the session to destroy
+     */
+    public abstract void onDestroyTextClassificationSession(
+            @NonNull TextClassificationSessionId sessionId);
 
     /**
      * Returns a TextClassifier that runs in this service's process.
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 435e324..9c6a3f5 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -33,6 +33,7 @@
 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.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 @@
      * 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 @@
      * calling thread.
      *
      * @param textView TextView whose text is to be marked-up with links
-     * @param options optional parameters to specify how to generate the links
-     * @param executor Executor that runs the background task
-     * @param callback Callback that receives the final status of the background task execution
+     * @param mask mask to define which kinds of links will be generated
      *
      * @return a future that may be used to interrupt or query the background task
+     * @hide
      */
     @UiThread
     public static Future<Void> addLinksAsync(
             @NonNull TextView textView,
-            @Nullable TextLinks.Options options,
+            @LinkifyMask int mask) {
+        return addLinksAsync(textView, TextLinksParams.fromLinkMask(mask),
+                null /* executor */, null /* callback */);
+    }
+
+    /**
+     * Scans the text of the provided TextView and turns all occurrences of the entity types
+     * specified by {@code options} into clickable links. If links are found, this method
+     * removes any pre-existing {@link TextLinkSpan} attached to the text (to avoid
+     * problems if you call it repeatedly on the same text) and sets the movement method for the
+     * TextView to LinkMovementMethod.
+     *
+     * <p><strong>Note:</strong> This method returns immediately but generates the links with
+     * the specified classifier on a background thread. The generated links are applied on the
+     * calling thread.
+     *
+     * @param textView TextView whose text is to be marked-up with links
+     * @param params optional parameters to specify how to generate the links
+     * @param executor Executor that runs the background task
+     * @param callback Callback that receives the final status of the background task execution
+     *
+     * @return a future that may be used to interrupt or query the background task
+     * @hide
+     */
+    @UiThread
+    public static Future<Void> addLinksAsync(
+            @NonNull TextView textView,
+            @Nullable TextLinksParams params,
             @Nullable Executor executor,
             @Nullable Consumer<Integer> callback) {
         Preconditions.checkNotNull(textView);
@@ -548,7 +575,7 @@
             }
         };
         return addLinksAsync(spannable, textView.getTextClassifier(),
-                options, executor, callback, modifyTextView);
+                params, executor, callback, modifyTextView);
     }
 
     /**
@@ -566,15 +593,16 @@
      *
      * @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 @@
      * @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 @@
      *
      * @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 @@
             }
 
             // 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 46ff442..203ca56 100644
--- a/core/java/android/view/textclassifier/DefaultLogger.java
+++ b/core/java/android/view/textclassifier/DefaultLogger.java
@@ -87,7 +87,7 @@
                 .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 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
-     * 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 754c9e9..0000000
--- 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 83ca15d..ef19ee5 100644
--- a/core/java/android/view/textclassifier/Log.java
+++ b/core/java/android/view/textclassifier/Log.java
@@ -35,6 +35,10 @@
         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 c29d3e6..f03906a 100644
--- a/core/java/android/view/textclassifier/Logger.java
+++ b/core/java/android/view/textclassifier/Logger.java
@@ -35,8 +35,6 @@
     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 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 @@
         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 @@
 
         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 @@
         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 @@
         }
 
         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 @@
         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 @@
 
         logEvent(new SelectionEvent(
                 start, end, actionType, TextClassifier.TYPE_UNKNOWN, mInvocationMethod,
-                NO_SIGNATURE, mConfig));
+                null, mConfig));
     }
 
     /**
@@ -232,9 +228,8 @@
         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 @@
                     .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 5a4d2cf..1e978cc 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -126,7 +126,7 @@
     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 @@
     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 @@
         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 @@
         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 @@
                 : 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 @@
                 : 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 @@
                 ? classification.getEntity(0)
                 : TextClassifier.TYPE_UNKNOWN;
         return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN,
-                classification.getSignature());
+                classification.getId());
     }
 
     /**
@@ -450,14 +451,15 @@
     }
 
     /**
-     * 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 @@
     @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 @@
                 && 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 @@
     @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 @@
             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 2a24dd7..45fd6bf 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 @@
     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 @@
      */
     @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 @@
      */
     @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 @@
      */
     @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 @@
                     @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 @@
         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 9fefe5d..bfb143c 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 b413d48..37a5d9a 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -38,6 +38,7 @@
 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 @@
     @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 @@
             @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 @@
         mLegacyOnClickListener = legacyOnClickListener;
         mActions = Collections.unmodifiableList(actions);
         mEntityConfidence = new EntityConfidence(entityConfidence);
-        mSignature = signature;
+        mId = id;
     }
 
     /**
@@ -237,20 +238,18 @@
     }
 
     /**
-     * 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 @@
             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 @@
         }
     }
 
-    /**
-     * 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 @@
         @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 @@
          *      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 @@
          * 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 @@
          * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
         @Deprecated
+        @NonNull
         public Builder setIcon(@Nullable Drawable icon) {
             mLegacyIcon = icon;
             return this;
@@ -458,6 +442,7 @@
          * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
         @Deprecated
+        @NonNull
         public Builder setLabel(@Nullable String label) {
             mLegacyLabel = label;
             return this;
@@ -470,6 +455,7 @@
          * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
         @Deprecated
+        @NonNull
         public Builder setIntent(@Nullable Intent intent) {
             mLegacyIntent = intent;
             return this;
@@ -482,58 +468,79 @@
          *
          * @deprecated Use {@link #addAction(RemoteAction)} instead.
          */
+        @Deprecated
+        @NonNull
         public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
             mLegacyOnClickListener = onClickListener;
             return this;
         }
 
         /**
-         * Sets a signature for the TextClassification object.
-         * The TextClassifier that generates the TextClassification object may use it as a way to
-         * internally identify the TextClassification object.
+         * Sets an id for the TextClassification object.
          */
-        public Builder setSignature(@NonNull String signature) {
-            mSignature = Preconditions.checkNotNull(signature);
+        @NonNull
+        public Builder setId(@Nullable String id) {
+            mId = id;
             return this;
         }
 
         /**
          * Builds and returns a {@link TextClassification} object.
          */
+        @NonNull
         public TextClassification build() {
             return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
-                    mLegacyOnClickListener, mActions, mEntityConfidence, mSignature);
+                    mLegacyOnClickListener, mActions, mEntityConfidence, mId);
         }
     }
 
     /**
-     * Optional input parameters for generating TextClassification.
+     * A request object for generating TextClassification.
      */
-    public static final class Options implements Parcelable {
+    public static final class Request implements Parcelable {
 
-        private @Nullable LocaleList mDefaultLocales;
-        private @Nullable ZonedDateTime mReferenceTime;
+        private final CharSequence mText;
+        private final int mStartIndex;
+        private final int mEndIndex;
+        @Nullable private final LocaleList mDefaultLocales;
+        @Nullable private final ZonedDateTime mReferenceTime;
 
-        public Options() {}
-
-        /**
-         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
-         *      the provided text. If no locale preferences exist, set this to null or an empty
-         *      locale list.
-         */
-        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+        private Request(
+                CharSequence text,
+                int startIndex,
+                int endIndex,
+                LocaleList defaultLocales,
+                ZonedDateTime referenceTime) {
+            mText = text;
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
             mDefaultLocales = defaultLocales;
-            return this;
+            mReferenceTime = referenceTime;
         }
 
         /**
-         * @param referenceTime reference time based on which relative dates (e.g. "tomorrow" should
-         *      be interpreted. This should usually be the time when the text was originally
-         *      composed. If no reference time is set, now is used.
+         * Returns the text providing context for the text to classify (which is specified
+         *      by the sub sequence starting at startIndex and ending at endIndex)
          */
-        public Options setReferenceTime(ZonedDateTime referenceTime) {
-            mReferenceTime = referenceTime;
-            return this;
+        @NonNull
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns start index of the text to classify.
+         */
+        @IntRange(from = 0)
+        public int getStartIndex() {
+            return mStartIndex;
+        }
+
+        /**
+         * Returns end index of the text to classify.
+         */
+        @IntRange(from = 0)
+        public int getEndIndex() {
+            return mEndIndex;
         }
 
         /**
@@ -554,6 +561,69 @@
             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 @@
 
         @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 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 @@
         // 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 @@
         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 0000000..0d6b033
--- /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 a88f2f6..a15411f 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -18,6 +18,8 @@
 
 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 @@
  * 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 @@
             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 6938e1a..e8e300a 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -33,32 +33,42 @@
 
     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 @@
     @Override
     public void destroy() {
         mEventHelper.endSession();
+        mDelegate.destroy();
         mDestroyed = true;
     }
 
@@ -162,7 +173,7 @@
                         .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 @@
                 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 0000000..1bbf2da
--- /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 3e4dc1c..1378bd9 100644
--- a/core/java/android/view/textclassifier/TextClassificationSessionId.java
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Locale;
 import java.util.UUID;
 
 /**
@@ -77,6 +78,11 @@
     }
 
     @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 2048f2b..54261be 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.LinkifyMask;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
 
@@ -156,76 +155,44 @@
      *
      * <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 @@
             @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 @@
      * <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 @@
      * <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 @@
      *      by the sub sequence starting at startIndex and ending at endIndex)
      * @param startIndex start index of the text to classify
      * @param endIndex end index of the text to classify
+     * @param defaultLocales ordered list of locale preferences that may be used to
+     *      disambiguate the provided text. If no locale preferences exist, set this to null
+     *      or an empty locale list.
      *
      * @throws IllegalArgumentException if text is null; startIndex is negative;
      *      endIndex is greater than text.length() or not greater than startIndex
      *
-     * @see #classifyText(CharSequence, int, int, TextClassification.Options)
-     */
-    @WorkerThread
-    @NonNull
-    default TextClassification classifyText(
-            @NonNull CharSequence text,
-            @IntRange(from = 0) int startIndex,
-            @IntRange(from = 0) int endIndex) {
-        return classifyText(text, startIndex, endIndex, (TextClassification.Options) null);
-    }
-
-    /**
-     * See {@link #classifyText(CharSequence, int, int, TextClassification.Options)} or
-     * {@link #classifyText(CharSequence, int, int)}.
-     *
-     * <p><strong>NOTE: </strong>Call on a worker thread.
-     *
-     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
-     * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
-     * calls this method, a stack overflow error will happen.
-     *
-     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
-     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     * @see #classifyText(TextClassification.Request)
      */
     @WorkerThread
     @NonNull
@@ -323,10 +260,11 @@
             @IntRange(from = 0) int startIndex,
             @IntRange(from = 0) int endIndex,
             @Nullable LocaleList defaultLocales) {
-        final TextClassification.Options options = (defaultLocales != null)
-                ? new TextClassification.Options().setDefaultLocales(defaultLocales)
-                : null;
-        return classifyText(text, startIndex, endIndex, options);
+        final TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(defaultLocales)
+                .build();
+        return classifyText(request);
     }
 
     /**
@@ -338,48 +276,16 @@
      * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
      * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
      *
-     * @param text the text to generate annotations for
-     * @param options configuration for link generation
+     * @param request the text links request
      *
-     * @throws IllegalArgumentException if text is null or the text is too long for the
-     *      TextClassifier implementation.
-     *
-     * @see #generateLinks(CharSequence)
      * @see #getMaxGenerateLinksTextLength()
      */
     @WorkerThread
     @NonNull
-    default TextLinks generateLinks(
-            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
-        Utils.validate(text, false);
-        return new TextLinks.Builder(text.toString()).build();
-    }
-
-    /**
-     * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
-     * links information.
-     *
-     * <p><strong>NOTE: </strong>Call on a worker thread.
-     *
-     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
-     * {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method,
-     * a stack overflow error will happen.
-     *
-     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
-     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
-     *
-     * @param text the text to generate annotations for
-     *
-     * @throws IllegalArgumentException if text is null or the text is too long for the
-     *      TextClassifier implementation.
-     *
-     * @see #generateLinks(CharSequence, TextLinks.Options)
-     * @see #getMaxGenerateLinksTextLength()
-     */
-    @WorkerThread
-    @NonNull
-    default TextLinks generateLinks(@NonNull CharSequence text) {
-        return generateLinks(text, null);
+    default TextLinks generateLinks(@NonNull TextLinks.Request request) {
+        Preconditions.checkNotNull(request);
+        Utils.checkMainThread();
+        return new TextLinks.Builder(request.getText().toString()).build();
     }
 
     /**
@@ -388,8 +294,7 @@
      * <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 @@
          *
          * @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 @@
          * @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 @@
          *      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 @@
             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 8d1ed0e..7e3748a 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -112,35 +112,32 @@
     /** @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 @@
                     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 @@
                     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 @@
             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 @@
             }
             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 @@
         }
     }
 
-    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 @@
             builder.addAction(action);
         }
 
-        return builder.setSignature(getSignature(text, start, end)).build();
+        return builder.setId(createId(text, start, end)).build();
     }
 
     /**
@@ -512,7 +502,7 @@
             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 1bbb798..5de2c77 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 38a7d9a..17c7b13 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -25,10 +25,9 @@
 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.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 @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 @@
     }
 
     /**
+     * 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 @@
         }
 
         @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 @@
     }
 
     /**
-     * Optional input parameters for generating TextLinks.
+     * A request object for generating TextLinks.
      */
-    public static final class Options implements Parcelable {
+    public static final class Request implements Parcelable {
 
-        private LocaleList mDefaultLocales;
-        private TextClassifier.EntityConfig mEntityConfig;
-        private boolean mLegacyFallback;
-
-        private @ApplyStrategy int mApplyStrategy;
-        private Function<TextLink, TextLinkSpan> mSpanFactory;
-
+        private final CharSequence mText;
+        @Nullable private final LocaleList mDefaultLocales;
+        @Nullable private final TextClassifier.EntityConfig mEntityConfig;
+        private final boolean mLegacyFallback;
         private String mCallingPackageName;
 
-        /**
-         * Returns a new options object based on the specified link mask.
-         */
-        public static Options fromLinkMask(@LinkifyMask int mask) {
-            final List<String> entitiesToFind = new ArrayList<>();
-
-            if ((mask & Linkify.WEB_URLS) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_URL);
-            }
-            if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_EMAIL);
-            }
-            if ((mask & Linkify.PHONE_NUMBERS) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_PHONE);
-            }
-            if ((mask & Linkify.MAP_ADDRESSES) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_ADDRESS);
-            }
-
-            return new Options().setEntityConfig(
-                    TextClassifier.EntityConfig.createWithEntityList(entitiesToFind));
-        }
-
-        public Options() {}
-
-        /**
-         * @param defaultLocales ordered list of locale preferences that may be used to
-         *                       disambiguate the provided text. If no locale preferences exist,
-         *                       set this to null or an empty locale list.
-         */
-        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+        private Request(
+                CharSequence text,
+                LocaleList defaultLocales,
+                TextClassifier.EntityConfig entityConfig,
+                boolean legacyFallback,
+                String callingPackageName) {
+            mText = text;
             mDefaultLocales = defaultLocales;
-            return this;
-        }
-
-        /**
-         * Sets the entity configuration to use. This determines what types of entities the
-         * TextClassifier will look for.
-         *
-         * @param entityConfig EntityConfig to use
-         */
-        public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
             mEntityConfig = entityConfig;
-            return this;
-        }
-
-        /**
-         * Sets whether the TextClassifier can fallback to legacy links if smart linkify is
-         * disabled.
-         * <strong>Note: </strong>This is not parcelled.
-         * @hide
-         */
-        public Options setLegacyFallback(boolean legacyFallback) {
             mLegacyFallback = legacyFallback;
-            return this;
-        }
-
-        /**
-         * Sets a strategy for resolving conflicts when applying generated links to text that
-         * already have links.
-         *
-         * @throws IllegalArgumentException if applyStrategy is not valid
-         *
-         * @see #APPLY_STRATEGY_IGNORE
-         * @see #APPLY_STRATEGY_REPLACE
-         */
-        public Options setApplyStrategy(@ApplyStrategy int applyStrategy) {
-            checkValidApplyStrategy(applyStrategy);
-            mApplyStrategy = applyStrategy;
-            return this;
-        }
-
-        /**
-         * Sets a factory for converting a TextLink to a TextLinkSpan.
-         *
-         * <p><strong>Note: </strong>This is not parceled over IPC.
-         */
-        public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
-            mSpanFactory = spanFactory;
-            return this;
-        }
-
-        /**
-         * Sets the name of the package that requested the links to get generated.
-         * @hide
-         */
-        public Options setCallingPackageName(@Nullable String callingPackageName) {
             mCallingPackageName = callingPackageName;
-            return this;
+        }
+
+        /**
+         * Returns the text to generate links for.
+         */
+        @NonNull
+        public CharSequence getText() {
+            return mText;
         }
 
         /**
@@ -437,26 +355,91 @@
         }
 
         /**
-         * @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 @@
 
         @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 @@
             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 @@
          *
          * @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 @@
          * @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 @@
         /**
          * Removes all {@link TextLink}s.
          */
+        @NonNull
         public Builder clearTextLinks() {
             mLinks.clear();
             return this;
@@ -624,18 +602,9 @@
          *
          * @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 0000000..be4c3bc
--- /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 dab1aef..b2fd9be 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 1c93be7..939e717 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -25,6 +25,7 @@
 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 @@
 
     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 @@
      * @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 @@
     }
 
     /**
-     * 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 @@
 
         private final int mStartIndex;
         private final int mEndIndex;
-        @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
-        @NonNull private String mSignature = "";
+        private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
+        @Nullable private String mId;
 
         /**
          * Creates a builder used to build {@link TextSelection} objects.
@@ -142,56 +141,96 @@
          *      0 implies the entity does not exist for the classified text.
          *      Values greater than 1 are clamped to 1.
          */
+        @NonNull
         public Builder setEntityType(
                 @NonNull @EntityType String type,
                 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+            Preconditions.checkNotNull(type);
             mEntityConfidence.put(type, confidenceScore);
             return this;
         }
 
         /**
-         * Sets a signature for the TextSelection object.
-         *
-         * The TextClassifier that generates the TextSelection object may use it as a way to
-         * internally identify the TextSelection object.
+         * Sets an id for the TextSelection object.
          */
-        public Builder setSignature(@NonNull String signature) {
-            mSignature = Preconditions.checkNotNull(signature);
+        @NonNull
+        public Builder setId(@NonNull String id) {
+            mId = Preconditions.checkNotNull(id);
             return this;
         }
 
         /**
          * Builds and returns {@link TextSelection} object.
          */
+        @NonNull
         public TextSelection build() {
             return new TextSelection(
-                    mStartIndex, mEndIndex, mEntityConfidence, mSignature);
+                    mStartIndex, mEndIndex, mEntityConfidence, mId);
         }
     }
 
     /**
-     * Optional input parameters for generating TextSelection.
+     * A request object for generating TextSelection.
      */
-    public static final class Options implements Parcelable {
+    public static final class Request implements Parcelable {
 
-        private @Nullable LocaleList mDefaultLocales;
-        private boolean mDarkLaunchAllowed;
+        private final CharSequence mText;
+        private final int mStartIndex;
+        private final int mEndIndex;
+        @Nullable private final LocaleList mDefaultLocales;
+        private final boolean mDarkLaunchAllowed;
 
-        public Options() {}
-
-        /**
-         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
-         *      the provided text. If no locale preferences exist, set this to null or an empty
-         *      locale list.
-         */
-        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+        private Request(
+                CharSequence text,
+                int startIndex,
+                int endIndex,
+                LocaleList defaultLocales,
+                boolean darkLaunchAllowed) {
+            mText = text;
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
             mDefaultLocales = defaultLocales;
-            return this;
+            mDarkLaunchAllowed = darkLaunchAllowed;
         }
 
         /**
-         * @return ordered list of locale preferences that can be used to disambiguate
-         *      the provided text.
+         * Returns the text providing context for the selected text (which is specified by the
+         * sub sequence starting at startIndex and ending at endIndex).
+         */
+        @NonNull
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns start index of the selected part of text.
+         */
+        @IntRange(from = 0)
+        public int getStartIndex() {
+            return mStartIndex;
+        }
+
+        /**
+         * Returns end index of the selected part of text.
+         */
+        @IntRange(from = 0)
+        public int getEndIndex() {
+            return mEndIndex;
+        }
+
+        /**
+         * Returns true if the TextClassifier should return selection suggestions when "dark
+         * launched". Otherwise, returns false.
+         *
+         * @hide
+         */
+        public boolean isDarkLaunchAllowed() {
+            return mDarkLaunchAllowed;
+        }
+
+        /**
+         * @return ordered list of locale preferences that can be used to disambiguate the
+         * provided text.
          */
         @Nullable
         public LocaleList getDefaultLocales() {
@@ -199,26 +238,71 @@
         }
 
         /**
-         * @param allowed whether or not the TextClassifier should return selection suggestions
-         *      when "dark launched". When a TextClassifier is dark launched, it can suggest
-         *      selection changes that should not be used to actually change the user's selection.
-         *      Instead, the suggested selection is logged, compared with the user's selection
-         *      interaction, and used to generate quality metrics for the TextClassifier.
-         *
-         * @hide
+         * A builder for building TextSelection requests.
          */
-        public void setDarkLaunchAllowed(boolean allowed) {
-            mDarkLaunchAllowed = allowed;
-        }
+        public static final class Builder {
 
-        /**
-         * Returns true if the TextClassifier should return selection suggestions when
-         * "dark launched". Otherwise, returns false.
-         *
-         * @hide
-         */
-        public boolean isDarkLaunchAllowed() {
-            return mDarkLaunchAllowed;
+            private final CharSequence mText;
+            private final int mStartIndex;
+            private final int mEndIndex;
+
+            @Nullable private LocaleList mDefaultLocales;
+            private boolean mDarkLaunchAllowed;
+
+            /**
+             * @param text text providing context for the selected text (which is specified by the
+             *      sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+             * @param startIndex start index of the selected part of text
+             * @param endIndex end index of the selected part of text
+             */
+            public Builder(
+                    @NonNull CharSequence text,
+                    @IntRange(from = 0) int startIndex,
+                    @IntRange(from = 0) int endIndex) {
+                Utils.checkArgument(text, startIndex, endIndex);
+                mText = text;
+                mStartIndex = startIndex;
+                mEndIndex = endIndex;
+            }
+
+            /**
+             * @param defaultLocales ordered list of locale preferences that may be used to
+             *      disambiguate the provided text. If no locale preferences exist, set this to null
+             *      or an empty locale list.
+             *
+             * @return this builder.
+             */
+            @NonNull
+            public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
+                mDefaultLocales = defaultLocales;
+                return this;
+            }
+
+            /**
+             * @param allowed whether or not the TextClassifier should return selection suggestions
+             *      when "dark launched". When a TextClassifier is dark launched, it can suggest
+             *      selection changes that should not be used to actually change the user's
+             *      selection. Instead, the suggested selection is logged, compared with the user's
+             *      selection interaction, and used to generate quality metrics for the
+             *      TextClassifier. Not parceled.
+             *
+             * @return this builder.
+             * @hide
+             */
+            @NonNull
+            public Builder setDarkLaunchAllowed(boolean allowed) {
+                mDarkLaunchAllowed = allowed;
+                return this;
+            }
+
+            /**
+             * Builds and returns the request object.
+             */
+            @NonNull
+            public Request build() {
+                return new Request(mText, mStartIndex, mEndIndex,
+                        mDefaultLocales, mDarkLaunchAllowed);
+            }
         }
 
         @Override
@@ -228,31 +312,34 @@
 
         @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 @@
         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 @@
         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 157b3d8..f7d75cd 100644
--- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -473,7 +473,7 @@
             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 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 @@
             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 @@
             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 8b49ccb..b3327a7 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -499,7 +499,8 @@
             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 @@
                             mSelectionStart, mSelectionEnd,
                             SelectionEvent.ACTION_ABANDON, null /* classification */);
                     mSelectionStart = mSelectionEnd = -1;
+                    mTextView.getTextClassificationSession().destroy();
                     mIsPending = false;
                 }
             }
@@ -661,16 +663,16 @@
         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 void logSelectionStarted(
+                TextClassifier classificationSession,
                 CharSequence text, int index,
                 @InvocationMethod int invocationMethod) {
             try {
@@ -701,7 +704,8 @@
                 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 @@
                     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 @@
                     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 @@
         /** 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 @@
             Preconditions.checkArgument(selectionEnd > selectionStart);
             mSelectionStart = selectionStart;
             mSelectionEnd = selectionEnd;
-            mClassificationOptions.setDefaultLocales(locales);
-            mSelectionOptions.setDefaultLocales(locales)
-                    .setDarkLaunchAllowed(true);
+            mDefaultLocales = locales;
         }
 
         @WorkerThread
@@ -983,13 +990,16 @@
             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 @@
             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 8bf497e..11db6b6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -36,6 +36,7 @@
 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 @@
 
     /**
      * 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 @@
             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");
                         }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index f96027d..a3c24cb 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -38,6 +38,7 @@
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -49,19 +50,14 @@
     private Context mContext;
     private TextClassificationManager mTcm;
     private TextClassifier mClassifier;
-    private TextSelection.Options mSelectionOptions;
-    private TextClassification.Options mClassificationOptions;
-    private TextLinks.Options mLinksOptions;
 
     @Before
     public void setup() {
         mContext = InstrumentationRegistry.getTargetContext();
         mTcm = mContext.getSystemService(TextClassificationManager.class);
-        mTcm.setTextClassifier(null);
-        mClassifier = mTcm.getTextClassifier();
-        mSelectionOptions = new TextSelection.Options().setDefaultLocales(LOCALES);
-        mClassificationOptions = new TextClassification.Options().setDefaultLocales(LOCALES);
-        mLinksOptions = new TextLinks.Options().setDefaultLocales(LOCALES);
+        // Test with the local textClassifier only. (We only bundle "en" model by default).
+        // It's hard to reliably test the results of the device's TextClassifierServiceImpl here.
+        mClassifier = mTcm.getTextClassifier(TextClassifier.LOCAL);
     }
 
     @Test
@@ -75,9 +71,12 @@
         int endIndex = startIndex + selected.length();
         int smartStartIndex = text.indexOf(suggested);
         int smartEndIndex = smartStartIndex + suggested.length();
+        TextSelection.Request request = new TextSelection.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextSelection selection = mClassifier.suggestSelection(
-                text, startIndex, endIndex, mSelectionOptions);
+        TextSelection selection = mClassifier.suggestSelection(request);
         assertThat(selection,
                 isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
     }
@@ -93,9 +92,12 @@
         int endIndex = startIndex + selected.length();
         int smartStartIndex = text.indexOf(suggested);
         int smartEndIndex = smartStartIndex + suggested.length();
+        TextSelection.Request request = new TextSelection.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextSelection selection = mClassifier.suggestSelection(
-                text, startIndex, endIndex, mSelectionOptions);
+        TextSelection selection = mClassifier.suggestSelection(request);
         assertThat(selection,
                 isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
     }
@@ -108,9 +110,12 @@
         String selected = "Hello";
         int startIndex = text.indexOf(selected);
         int endIndex = startIndex + selected.length();
+        TextSelection.Request request = new TextSelection.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextSelection selection = mClassifier.suggestSelection(
-                text, startIndex, endIndex, mSelectionOptions);
+        TextSelection selection = mClassifier.suggestSelection(request);
         assertThat(selection,
                 isTextSelection(startIndex, endIndex, NO_TYPE));
     }
@@ -123,9 +128,12 @@
         String classifiedText = "droid@android.com";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
+        TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextClassification classification = mClassifier.classifyText(
-                text, startIndex, endIndex, mClassificationOptions);
+        TextClassification classification = mClassifier.classifyText(request);
         assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_EMAIL));
     }
 
@@ -137,9 +145,12 @@
         String classifiedText = "www.android.com";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
+        TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextClassification classification = mClassifier.classifyText(
-                text, startIndex, endIndex, mClassificationOptions);
+        TextClassification classification = mClassifier.classifyText(request);
         assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
     }
 
@@ -148,8 +159,12 @@
         if (isTextClassifierDisabled()) return;
 
         String text = "Brandschenkestrasse 110, Zürich, Switzerland";
-        TextClassification classification = mClassifier.classifyText(
-                text, 0, text.length(), mClassificationOptions);
+        TextClassification.Request request = new TextClassification.Request.Builder(
+                text, 0, text.length())
+                .setDefaultLocales(LOCALES)
+                .build();
+
+        TextClassification classification = mClassifier.classifyText(request);
         assertThat(classification, isTextClassification(text, TextClassifier.TYPE_ADDRESS));
     }
 
@@ -161,9 +176,12 @@
         String classifiedText = "HTTP://ANDROID.COM";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
+        TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextClassification classification = mClassifier.classifyText(
-                text, startIndex, endIndex, mClassificationOptions);
+        TextClassification classification = mClassifier.classifyText(request);
         assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
     }
 
@@ -175,9 +193,12 @@
         String classifiedText = "January 9, 2018";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
+        TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextClassification classification = mClassifier.classifyText(
-                text, startIndex, endIndex, mClassificationOptions);
+        TextClassification classification = mClassifier.classifyText(request);
         assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_DATE));
     }
 
@@ -189,9 +210,12 @@
         String classifiedText = "2018/01/01 10:30:20";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
+        TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
 
-        TextClassification classification = mClassifier.classifyText(
-                text, startIndex, endIndex, mClassificationOptions);
+        TextClassification classification = mClassifier.classifyText(request);
         assertThat(classification,
                 isTextClassification(classifiedText, TextClassifier.TYPE_DATE_TIME));
     }
@@ -200,7 +224,8 @@
     public void testGenerateLinks_phone() {
         if (isTextClassifierDisabled()) return;
         String text = "The number is +12122537077. See you tonight!";
-        assertThat(mClassifier.generateLinks(text, null),
+        TextLinks.Request request = new TextLinks.Request.Builder(text).build();
+        assertThat(mClassifier.generateLinks(request),
                 isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE));
     }
 
@@ -208,9 +233,14 @@
     public void testGenerateLinks_exclude() {
         if (isTextClassifierDisabled()) return;
         String text = "The number is +12122537077. See you tonight!";
-        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
-                TextClassifier.EntityConfig.create(Collections.EMPTY_LIST,
-                        Collections.EMPTY_LIST, Arrays.asList(TextClassifier.TYPE_PHONE)))),
+        List<String> hints = Collections.EMPTY_LIST;
+        List<String> included = Collections.EMPTY_LIST;
+        List<String> excluded = Arrays.asList(TextClassifier.TYPE_PHONE);
+        TextLinks.Request request = new TextLinks.Request.Builder(text)
+                .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
+                .setDefaultLocales(LOCALES)
+                .build();
+        assertThat(mClassifier.generateLinks(request),
                 not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
     }
 
@@ -218,9 +248,12 @@
     public void testGenerateLinks_explicit_address() {
         if (isTextClassifierDisabled()) return;
         String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
-        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
-                TextClassifier.EntityConfig.createWithEntityList(
-                        Arrays.asList(TextClassifier.TYPE_ADDRESS)))),
+        List<String> explicit = Arrays.asList(TextClassifier.TYPE_ADDRESS);
+        TextLinks.Request request = new TextLinks.Request.Builder(text)
+                .setEntityConfig(TextClassifier.EntityConfig.createWithExplicitEntityList(explicit))
+                .setDefaultLocales(LOCALES)
+                .build();
+        assertThat(mClassifier.generateLinks(request),
                 isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
                         TextClassifier.TYPE_ADDRESS));
     }
@@ -229,10 +262,14 @@
     public void testGenerateLinks_exclude_override() {
         if (isTextClassifierDisabled()) return;
         String text = "The number is +12122537077. See you tonight!";
-        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
-                TextClassifier.EntityConfig.create(Collections.EMPTY_LIST,
-                        Arrays.asList(TextClassifier.TYPE_PHONE),
-                        Arrays.asList(TextClassifier.TYPE_PHONE)))),
+        List<String> hints = Collections.EMPTY_LIST;
+        List<String> included = Arrays.asList(TextClassifier.TYPE_PHONE);
+        List<String> excluded = Arrays.asList(TextClassifier.TYPE_PHONE);
+        TextLinks.Request request = new TextLinks.Request.Builder(text)
+                .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
+                .setDefaultLocales(LOCALES)
+                .build();
+        assertThat(mClassifier.generateLinks(request),
                 not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
     }
 
@@ -241,7 +278,8 @@
         if (isTextClassifierDisabled()) return;
         char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength()];
         Arrays.fill(manySpaces, ' ');
-        TextLinks links = mClassifier.generateLinks(new String(manySpaces), null);
+        TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
+        TextLinks links = mClassifier.generateLinks(request);
         assertTrue(links.getLinks().isEmpty());
     }
 
@@ -252,7 +290,8 @@
         }
         char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1];
         Arrays.fill(manySpaces, ' ');
-        mClassifier.generateLinks(new String(manySpaces), null);
+        TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
+        mClassifier.generateLinks(request);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index 5d58f55..09ace4c 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -79,14 +79,14 @@
         final RemoteAction remoteAction1 = new RemoteAction(secondaryIcon, secondaryLabel,
                 secondaryDescription, secondaryPendingIntent);
 
-        final String signature = "signature";
+        final String id = "id";
         final TextClassification reference = new TextClassification.Builder()
                 .setText(text)
                 .addAction(remoteAction0)
                 .addAction(remoteAction1)
                 .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
                 .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
-                .setSignature(signature)
+                .setId(id)
                 .build();
 
         // Parcel and unparcel
@@ -96,7 +96,7 @@
         final TextClassification result = TextClassification.CREATOR.createFromParcel(parcel);
 
         assertEquals(text, result.getText());
-        assertEquals(signature, result.getSignature());
+        assertEquals(id, result.getId());
         assertEquals(2, result.getActions().size());
 
         // Legacy API.
@@ -135,7 +135,7 @@
         final Intent intent = new Intent("intent");
         final View.OnClickListener onClickListener = v -> { };
 
-        final String signature = "signature";
+        final String id = "id";
         final TextClassification reference = new TextClassification.Builder()
                 .setText(text)
                 .setIcon(icon.loadDrawable(context))
@@ -144,7 +144,7 @@
                 .setOnClickListener(onClickListener)
                 .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
                 .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
-                .setSignature(signature)
+                .setId(id)
                 .build();
 
         // Parcel and unparcel
@@ -163,22 +163,29 @@
     }
 
     @Test
-    public void testParcelOptions() {
-        ZonedDateTime referenceTime = ZonedDateTime.ofInstant(
+    public void testParcelParcel() {
+        final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(
                 Instant.ofEpochMilli(946771200000L),  // 2000-01-02
                 ZoneId.of("UTC"));
+        final String text = "text";
 
-        TextClassification.Options reference = new TextClassification.Options();
-        reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
-        reference.setReferenceTime(referenceTime);
+        final TextClassification.Request reference =
+                new TextClassification.Request.Builder(text, 0, text.length())
+                        .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY))
+                        .setReferenceTime(referenceTime)
+                        .build();
 
         // Parcel and unparcel.
         final Parcel parcel = Parcel.obtain();
         reference.writeToParcel(parcel, reference.describeContents());
         parcel.setDataPosition(0);
-        TextClassification.Options result = TextClassification.Options.CREATOR.createFromParcel(
-                parcel);
+        final TextClassification.Request result =
+                TextClassification.Request.CREATOR.createFromParcel(parcel);
 
+        assertEquals(text, result.getText());
+        assertEquals(0, result.getStartIndex());
+        assertEquals(text.length(), result.getEndIndex());
+        assertEquals(referenceTime, result.getReferenceTime());
         assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
         assertEquals(referenceTime, result.getReferenceTime());
     }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
index 4279675..fb09e3e 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
@@ -20,12 +20,10 @@
 
 import android.os.LocaleList;
 import android.os.Parcel;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.ArrayMap;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,17 +38,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TextLinksTest {
 
-    private TextClassificationManager mTcm;
-    private TextClassifier mClassifier;
-
-    @Before
-    public void setup() {
-        mTcm = InstrumentationRegistry.getTargetContext()
-                .getSystemService(TextClassificationManager.class);
-        mTcm.setTextClassifier(null);
-        mClassifier = mTcm.getTextClassifier();
-    }
-
     private Map<String, Float> getEntityScores(float address, float phone, float other) {
         final Map<String, Float> result = new ArrayMap<>();
         if (address > 0.f) {
@@ -99,20 +86,22 @@
 
     @Test
     public void testParcelOptions() {
-        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
+        final TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
                 Arrays.asList(TextClassifier.HINT_TEXT_IS_EDITABLE),
                 Arrays.asList("a", "b", "c"),
                 Arrays.asList("b"));
-        TextLinks.Options reference = new TextLinks.Options();
-        reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
-        reference.setEntityConfig(entityConfig);
+        final TextLinks.Request reference = new TextLinks.Request.Builder("text")
+                .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY))
+                .setEntityConfig(entityConfig)
+                .build();
 
         // Parcel and unparcel.
         final Parcel parcel = Parcel.obtain();
         reference.writeToParcel(parcel, reference.describeContents());
         parcel.setDataPosition(0);
-        TextLinks.Options result = TextLinks.Options.CREATOR.createFromParcel(parcel);
+        final TextLinks.Request result = TextLinks.Request.CREATOR.createFromParcel(parcel);
 
+        assertEquals("text", result.getText());
         assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
         assertEquals(new String[]{TextClassifier.HINT_TEXT_IS_EDITABLE},
                 result.getEntityConfig().getHints().toArray());
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
index a6ea021..4855dad 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
@@ -17,7 +17,6 @@
 package android.view.textclassifier;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import android.os.LocaleList;
 import android.os.Parcel;
@@ -37,12 +36,12 @@
     public void testParcel() {
         final int startIndex = 13;
         final int endIndex = 37;
-        final String signature = "signature";
+        final String id = "id";
         final TextSelection reference = new TextSelection.Builder(startIndex, endIndex)
                 .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
                 .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
                 .setEntityType(TextClassifier.TYPE_URL, 0.1f)
-                .setSignature(signature)
+                .setId(id)
                 .build();
 
         // Parcel and unparcel
@@ -53,7 +52,7 @@
 
         assertEquals(startIndex, result.getSelectionStartIndex());
         assertEquals(endIndex, result.getSelectionEndIndex());
-        assertEquals(signature, result.getSignature());
+        assertEquals(id, result.getId());
 
         assertEquals(3, result.getEntityCount());
         assertEquals(TextClassifier.TYPE_PHONE, result.getEntity(0));
@@ -65,18 +64,22 @@
     }
 
     @Test
-    public void testParcelOptions() {
-        TextSelection.Options reference = new TextSelection.Options();
-        reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
-        reference.setDarkLaunchAllowed(true);
+    public void testParcelRequest() {
+        final String text = "text";
+        final TextSelection.Request reference =
+                new TextSelection.Request.Builder(text, 0, text.length())
+                        .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY))
+                        .build();
 
         // Parcel and unparcel.
         final Parcel parcel = Parcel.obtain();
         reference.writeToParcel(parcel, reference.describeContents());
         parcel.setDataPosition(0);
-        TextSelection.Options result = TextSelection.Options.CREATOR.createFromParcel(parcel);
+        final TextSelection.Request result = TextSelection.Request.CREATOR.createFromParcel(parcel);
 
+        assertEquals(text, result.getText());
+        assertEquals(0, result.getStartIndex());
+        assertEquals(text.length(), result.getEndIndex());
         assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
-        assertTrue(result.isDarkLaunchAllowed());
     }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 79433ac..320a7ac 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -80,6 +80,7 @@
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextLinks;
+import android.view.textclassifier.TextLinksParams;
 import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
 
 import com.android.frameworks.coretests.R;
@@ -381,8 +382,12 @@
                 mActivity.getSystemService(TextClassificationManager.class);
         TextClassifier textClassifier = textClassificationManager.getTextClassifier();
         Spannable content = new SpannableString("Call me at +19148277737");
-        TextLinks links = textClassifier.generateLinks(content);
-        links.apply(content, TextLinks.APPLY_STRATEGY_REPLACE, null, false /* allowPrefix */);
+        TextLinks.Request request = new TextLinks.Request.Builder(content).build();
+        TextLinks links = textClassifier.generateLinks(request);
+        TextLinksParams applyParams = new TextLinksParams.Builder()
+                .setApplyStrategy(TextLinks.APPLY_STRATEGY_REPLACE)
+                .build();
+        applyParams.apply(content, links);
 
         mActivityRule.runOnUiThread(() -> {
             textView.setText(content);