Implement Stateful TextClassifier APIs.

Outstanding work tbd in other CLs
- Introduce request objects with session Ids
- Implement character based indexing for Selection events.

This CL hides the old Logger API but still keeps running so that we can
check that the modifications to the new API does not break anything.
We will remove the old Logger once we're convinced this is stable.

Please refer to I3c9ceea0863099fc4f0a5ce5e823c648ee9c4521 for previous
reviews related to this CL.

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
Change-Id: Iea744f1fa5964b4399290c31863ebeffa99af8d3
diff --git a/api/current.txt b/api/current.txt
index 5e7fa591..04f5c3e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -50307,38 +50307,13 @@
 
 package android.view.textclassifier {
 
-  public abstract class Logger {
-    ctor public Logger(android.view.textclassifier.Logger.Config);
-    method public java.text.BreakIterator getTokenIterator(java.util.Locale);
-    method public boolean isSmartSelection(java.lang.String);
-    method public final void logSelectionActionEvent(int, int, int);
-    method public final void logSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification);
-    method public final void logSelectionModifiedEvent(int, int);
-    method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification);
-    method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection);
-    method public final void logSelectionStartedEvent(int, int);
-    method public abstract void writeEvent(android.view.textclassifier.SelectionEvent);
-    field public static final int OUT_OF_BOUNDS = 2147483647; // 0x7fffffff
-    field public static final int OUT_OF_BOUNDS_NEGATIVE = -2147483648; // 0x80000000
-    field public static final java.lang.String WIDGET_CUSTOM_EDITTEXT = "customedit";
-    field public static final java.lang.String WIDGET_CUSTOM_TEXTVIEW = "customview";
-    field public static final java.lang.String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
-    field public static final java.lang.String WIDGET_EDITTEXT = "edittext";
-    field public static final java.lang.String WIDGET_EDIT_WEBVIEW = "edit-webview";
-    field public static final java.lang.String WIDGET_TEXTVIEW = "textview";
-    field public static final java.lang.String WIDGET_UNKNOWN = "unknown";
-    field public static final java.lang.String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview";
-    field public static final java.lang.String WIDGET_WEBVIEW = "webview";
-  }
-
-  public static final class Logger.Config {
-    ctor public Logger.Config(android.content.Context, java.lang.String, java.lang.String);
-    method public java.lang.String getPackageName();
-    method public java.lang.String getWidgetType();
-    method public java.lang.String getWidgetVersion();
-  }
-
   public final class SelectionEvent implements android.os.Parcelable {
+    method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int);
+    method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification);
+    method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int);
+    method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification);
+    method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection);
+    method public static android.view.textclassifier.SelectionEvent createSelectionStartedEvent(int, int);
     method public int describeContents();
     method public long getDurationSincePreviousEvent();
     method public long getDurationSinceSessionStart();
@@ -50349,13 +50324,14 @@
     method public int getEventType();
     method public int getInvocationMethod();
     method public java.lang.String getPackageName();
-    method public java.lang.String getSessionId();
+    method public android.view.textclassifier.TextClassificationSessionId getSessionId();
     method public java.lang.String getSignature();
     method public int getSmartEnd();
     method public int getSmartStart();
     method public int getStart();
     method public java.lang.String getWidgetType();
     method public java.lang.String getWidgetVersion();
+    method public static boolean isTerminal(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ACTION_ABANDON = 107; // 0x6b
     field public static final int ACTION_COPY = 101; // 0x65
@@ -50376,6 +50352,7 @@
     field public static final int EVENT_SMART_SELECTION_SINGLE = 3; // 0x3
     field public static final int INVOCATION_LINK = 2; // 0x2
     field public static final int INVOCATION_MANUAL = 1; // 0x1
+    field public static final int INVOCATION_UNKNOWN = 0; // 0x0
   }
 
   public final class TextClassification implements android.os.Parcelable {
@@ -50423,19 +50400,45 @@
     field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR;
   }
 
+  public final class TextClassificationContext {
+    method public java.lang.String getPackageName();
+    method public java.lang.String getWidgetType();
+    method public java.lang.String getWidgetVersion();
+  }
+
+  public static final class TextClassificationContext.Builder {
+    ctor public TextClassificationContext.Builder(java.lang.String, java.lang.String);
+    method public android.view.textclassifier.TextClassificationContext build();
+    method public android.view.textclassifier.TextClassificationContext.Builder setWidgetVersion(java.lang.String);
+  }
+
   public final class TextClassificationManager {
+    method public android.view.textclassifier.TextClassifier createTextClassificationSession(android.view.textclassifier.TextClassificationContext);
     method public android.view.textclassifier.TextClassifier getTextClassifier();
+    method public void setTextClassificationSessionFactory(android.view.textclassifier.TextClassificationSessionFactory);
     method public void setTextClassifier(android.view.textclassifier.TextClassifier);
   }
 
+  public abstract interface TextClassificationSessionFactory {
+    method public abstract android.view.textclassifier.TextClassifier createTextClassificationSession(android.view.textclassifier.TextClassificationContext);
+  }
+
+  public final class TextClassificationSessionId implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassificationSessionId> CREATOR;
+  }
+
   public abstract interface TextClassifier {
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int);
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
+    method public default void destroy();
     method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
     method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
-    method public default android.view.textclassifier.Logger getLogger(android.view.textclassifier.Logger.Config);
     method public default int getMaxGenerateLinksTextLength();
+    method public default boolean isDestroyed();
+    method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
@@ -50451,6 +50454,15 @@
     field public static final java.lang.String TYPE_PHONE = "phone";
     field public static final java.lang.String TYPE_UNKNOWN = "";
     field public static final java.lang.String TYPE_URL = "url";
+    field public static final java.lang.String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
+    field public static final java.lang.String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
+    field public static final java.lang.String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
+    field public static final java.lang.String WIDGET_TYPE_EDITTEXT = "edittext";
+    field public static final java.lang.String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
+    field public static final java.lang.String WIDGET_TYPE_TEXTVIEW = "textview";
+    field public static final java.lang.String WIDGET_TYPE_UNKNOWN = "unknown";
+    field public static final java.lang.String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
+    field public static final java.lang.String WIDGET_TYPE_WEBVIEW = "webview";
   }
 
   public static final class TextClassifier.EntityConfig implements android.os.Parcelable {
diff --git a/core/java/android/view/textclassifier/DefaultLogger.java b/core/java/android/view/textclassifier/DefaultLogger.java
index b2f4e39..46ff442 100644
--- a/core/java/android/view/textclassifier/DefaultLogger.java
+++ b/core/java/android/view/textclassifier/DefaultLogger.java
@@ -39,7 +39,7 @@
 public final class DefaultLogger extends Logger {
 
     private static final String LOG_TAG = "DefaultLogger";
-    private static final String CLASSIFIER_ID = "androidtc";
+    static final String CLASSIFIER_ID = "androidtc";
 
     private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
     private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
diff --git a/core/java/android/view/textclassifier/Logger.java b/core/java/android/view/textclassifier/Logger.java
index 9c92fd4..c29d3e6 100644
--- a/core/java/android/view/textclassifier/Logger.java
+++ b/core/java/android/view/textclassifier/Logger.java
@@ -18,56 +18,25 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.StringDef;
 import android.content.Context;
-import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.text.BreakIterator;
 import java.util.Locale;
 import java.util.Objects;
-import java.util.UUID;
 
 /**
  * A helper for logging TextClassifier related events.
+ * @hide
  */
 public abstract class Logger {
 
-    /**
-     * Use this to specify an indeterminate positive index.
-     */
-    public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE;
-
-    /**
-     * Use this to specify an indeterminate negative index.
-     */
-    public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE;
-
     private static final String LOG_TAG = "Logger";
     /* package */ static final boolean DEBUG_LOG_ENABLED = true;
 
     private static final String NO_SIGNATURE = "";
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @StringDef({WIDGET_TEXTVIEW, WIDGET_WEBVIEW, WIDGET_EDITTEXT,
-            WIDGET_EDIT_WEBVIEW, WIDGET_CUSTOM_TEXTVIEW, WIDGET_CUSTOM_EDITTEXT,
-            WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_UNKNOWN})
-    public @interface WidgetType {}
-
-    public static final String WIDGET_TEXTVIEW = "textview";
-    public static final String WIDGET_EDITTEXT = "edittext";
-    public static final String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview";
-    public static final String WIDGET_WEBVIEW = "webview";
-    public static final String WIDGET_EDIT_WEBVIEW = "edit-webview";
-    public static final String WIDGET_CUSTOM_TEXTVIEW = "customview";
-    public static final String WIDGET_CUSTOM_EDITTEXT = "customedit";
-    public static final String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
-    public static final String WIDGET_UNKNOWN = "unknown";
-
     private @SelectionEvent.InvocationMethod int mInvocationMethod;
     private SelectionEvent mPrevEvent;
     private SelectionEvent mSmartEvent;
@@ -108,7 +77,6 @@
         return false;
     }
 
-
     /**
      * Returns a token iterator for tokenizing text for logging purposes.
      */
@@ -299,6 +267,9 @@
                     // Selection did not change. Ignore event.
                     return;
                 }
+                break;
+            default:
+                // do nothing.
         }
 
         event.setEventTime(now);
@@ -325,9 +296,9 @@
         }
     }
 
-    private String startNewSession() {
+    private TextClassificationSessionId startNewSession() {
         endSession();
-        return UUID.randomUUID().toString();
+        return new TextClassificationSessionId();
     }
 
     private void endSession() {
@@ -372,12 +343,12 @@
         /**
          * @param context Context of the widget the logger logs for
          * @param widgetType a name for the widget being logged for. e.g.
-         *      {@link #WIDGET_TEXTVIEW}
+         *      {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}
          * @param widgetVersion a string version info for the widget the logger logs for
          */
         public Config(
                 @NonNull Context context,
-                @WidgetType String widgetType,
+                @TextClassifier.WidgetType String widgetType,
                 @Nullable String widgetVersion) {
             mPackageName = Preconditions.checkNotNull(context).getPackageName();
             mWidgetType = widgetType;
@@ -392,7 +363,8 @@
         }
 
         /**
-         * Returns the name for the widget being logged for. e.g. {@link #WIDGET_TEXTVIEW}.
+         * Returns the name for the widget being logged for. e.g.
+         * {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}.
          */
         public String getWidgetType() {
             return mWidgetType;
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 7ac094e..5a4d2cf 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -17,10 +17,12 @@
 package android.view.textclassifier;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.textclassifier.TextClassifier.EntityType;
+import android.view.textclassifier.TextClassifier.WidgetType;
 
 import com.android.internal.util.Preconditions;
 
@@ -103,30 +105,33 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({INVOCATION_MANUAL, INVOCATION_LINK})
+    @IntDef({INVOCATION_MANUAL, INVOCATION_LINK, INVOCATION_UNKNOWN})
     public @interface InvocationMethod {}
 
     /** Selection was invoked by the user long pressing, double tapping, or dragging to select. */
     public static final int INVOCATION_MANUAL = 1;
     /** Selection was invoked by the user tapping on a link. */
     public static final int INVOCATION_LINK = 2;
+    /** Unknown invocation method */
+    public static final int INVOCATION_UNKNOWN = 0;
+
+    private static final String NO_SIGNATURE = "";
 
     private final int mAbsoluteStart;
     private final int mAbsoluteEnd;
-    private final @EventType int mEventType;
     private final @EntityType String mEntityType;
-    @Nullable private final String mWidgetVersion;
-    private final String mPackageName;
-    private final String mWidgetType;
-    private final @InvocationMethod int mInvocationMethod;
 
-    // These fields should only be set by creator of a SelectionEvent.
-    private String mSignature;
+    private @EventType int mEventType;
+    private String mPackageName = "";
+    private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN;
+    private @InvocationMethod int mInvocationMethod;
+    @Nullable private String mWidgetVersion;
+    private String mSignature;  // TODO: Rename to resultId.
     private long mEventTime;
     private long mDurationSinceSessionStart;
     private long mDurationSincePreviousEvent;
     private int mEventIndex;
-    @Nullable private String mSessionId;
+    @Nullable private TextClassificationSessionId mSessionId;
     private int mStart;
     private int mEnd;
     private int mSmartStart;
@@ -135,20 +140,29 @@
     SelectionEvent(
             int start, int end,
             @EventType int eventType, @EntityType String entityType,
-            @InvocationMethod int invocationMethod, String signature, Logger.Config config) {
+            @InvocationMethod int invocationMethod, String signature) {
         Preconditions.checkArgument(end >= start, "end cannot be less than start");
         mAbsoluteStart = start;
         mAbsoluteEnd = end;
         mEventType = eventType;
         mEntityType = Preconditions.checkNotNull(entityType);
         mSignature = Preconditions.checkNotNull(signature);
-        Preconditions.checkNotNull(config);
-        mWidgetVersion = config.getWidgetVersion();
-        mPackageName = Preconditions.checkNotNull(config.getPackageName());
-        mWidgetType = Preconditions.checkNotNull(config.getWidgetType());
         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);
+        Preconditions.checkNotNull(config);
+        setTextClassificationSessionContext(
+                new TextClassificationContext.Builder(
+                        config.getPackageName(), config.getWidgetType())
+                        .setWidgetVersion(config.getWidgetVersion())
+                        .build());
+    }
+
     private SelectionEvent(Parcel in) {
         mAbsoluteStart = in.readInt();
         mAbsoluteEnd = in.readInt();
@@ -163,7 +177,8 @@
         mDurationSinceSessionStart = in.readLong();
         mDurationSincePreviousEvent = in.readLong();
         mEventIndex = in.readInt();
-        mSessionId = in.readInt() > 0 ? in.readString() : null;
+        mSessionId = in.readInt() > 0
+                ? TextClassificationSessionId.CREATOR.createFromParcel(in) : null;
         mStart = in.readInt();
         mEnd = in.readInt();
         mSmartStart = in.readInt();
@@ -190,7 +205,7 @@
         dest.writeInt(mEventIndex);
         dest.writeInt(mSessionId != null ? 1 : 0);
         if (mSessionId != null) {
-            dest.writeString(mSessionId);
+            mSessionId.writeToParcel(dest, flags);
         }
         dest.writeInt(mStart);
         dest.writeInt(mEnd);
@@ -203,6 +218,156 @@
         return 0;
     }
 
+    /**
+     * Creates a "selection started" event.
+     *
+     * @param invocationMethod  the way the selection was triggered
+     * @param start  the index of the selected text
+     */
+    @NonNull
+    public static SelectionEvent createSelectionStartedEvent(
+            @SelectionEvent.InvocationMethod int invocationMethod, int start) {
+        return new SelectionEvent(
+                start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED,
+                TextClassifier.TYPE_UNKNOWN, invocationMethod, NO_SIGNATURE);
+    }
+
+    /**
+     * Creates a "selection modified" event.
+     * Use when the user modifies the selection.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionModifiedEvent(int start, int end) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        return new SelectionEvent(
+                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
+                TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, NO_SIGNATURE);
+    }
+
+    /**
+     * Creates a "selection modified" event.
+     * Use when the user modifies the selection and the selection's entity type is known.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param classification  the TextClassification object returned by the TextClassifier that
+     *      classified the selected text
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionModifiedEvent(
+            int start, int end, @NonNull TextClassification classification) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        Preconditions.checkNotNull(classification);
+        final String entityType = classification.getEntityCount() > 0
+                ? classification.getEntity(0)
+                : TextClassifier.TYPE_UNKNOWN;
+        return new SelectionEvent(
+                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
+                entityType, INVOCATION_UNKNOWN, classification.getSignature());
+    }
+
+    /**
+     * Creates a "selection modified" event.
+     * Use when a TextClassifier modifies the selection.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param selection  the TextSelection object returned by the TextClassifier for the
+     *      specified selection
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionModifiedEvent(
+            int start, int end, @NonNull TextSelection selection) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        Preconditions.checkNotNull(selection);
+        final String entityType = selection.getEntityCount() > 0
+                ? selection.getEntity(0)
+                : TextClassifier.TYPE_UNKNOWN;
+        return new SelectionEvent(
+                start, end, SelectionEvent.EVENT_AUTO_SELECTION,
+                entityType, INVOCATION_UNKNOWN, selection.getSignature());
+    }
+
+    /**
+     * Creates an event specifying an action taken on a selection.
+     * Use when the user clicks on an action to act on the selected text.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param actionType  the action that was performed on the selection
+     *
+     * @throws IllegalArgumentException if end is less than start
+     */
+    @NonNull
+    public static SelectionEvent createSelectionActionEvent(
+            int start, int end, @SelectionEvent.ActionType int actionType) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        checkActionType(actionType);
+        return new SelectionEvent(
+                start, end, actionType, TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN,
+                NO_SIGNATURE);
+    }
+
+    /**
+     * Creates an event specifying an action taken on a selection.
+     * Use when the user clicks on an action to act on the selected text and the selection's
+     * entity type is known.
+     *
+     * @param start  the start (inclusive) index of the selection
+     * @param end  the end (exclusive) index of the selection
+     * @param actionType  the action that was performed on the selection
+     * @param classification  the TextClassification object returned by the TextClassifier that
+     *      classified the selected text
+     *
+     * @throws IllegalArgumentException if end is less than start
+     * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType
+     */
+    @NonNull
+    public static SelectionEvent createSelectionActionEvent(
+            int start, int end, @SelectionEvent.ActionType int actionType,
+            @NonNull TextClassification classification) {
+        Preconditions.checkArgument(end >= start, "end cannot be less than start");
+        Preconditions.checkNotNull(classification);
+        checkActionType(actionType);
+        final String entityType = classification.getEntityCount() > 0
+                ? classification.getEntity(0)
+                : TextClassifier.TYPE_UNKNOWN;
+        return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN,
+                classification.getSignature());
+    }
+
+    /**
+     * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType}
+     */
+    private static void checkActionType(@SelectionEvent.EventType int eventType)
+            throws IllegalArgumentException {
+        switch (eventType) {
+            case SelectionEvent.ACTION_OVERTYPE:  // fall through
+            case SelectionEvent.ACTION_COPY:  // fall through
+            case SelectionEvent.ACTION_PASTE:  // fall through
+            case SelectionEvent.ACTION_CUT:  // fall through
+            case SelectionEvent.ACTION_SHARE:  // fall through
+            case SelectionEvent.ACTION_SMART_SHARE:  // fall through
+            case SelectionEvent.ACTION_DRAG:  // fall through
+            case SelectionEvent.ACTION_ABANDON:  // fall through
+            case SelectionEvent.ACTION_SELECT_ALL:  // fall through
+            case SelectionEvent.ACTION_RESET:  // fall through
+                return;
+            default:
+                throw new IllegalArgumentException(
+                        String.format(Locale.US, "%d is not an eventType", eventType));
+        }
+    }
+
     int getAbsoluteStart() {
         return mAbsoluteStart;
     }
@@ -214,15 +379,24 @@
     /**
      * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}.
      */
+    @EventType
     public int getEventType() {
         return mEventType;
     }
 
     /**
+     * Sets the event type.
+     */
+    void setEventType(@EventType int eventType) {
+        mEventType = eventType;
+    }
+
+    /**
      * Returns the type of entity that is associated with this event. e.g.
      * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}.
      */
     @EntityType
+    @NonNull
     public String getEntityType() {
         return mEntityType;
     }
@@ -230,6 +404,7 @@
     /**
      * Returns the package name of the app that this event originated in.
      */
+    @NonNull
     public String getPackageName() {
         return mPackageName;
     }
@@ -237,6 +412,8 @@
     /**
      * Returns the type of widget that was involved in triggering this event.
      */
+    @WidgetType
+    @NonNull
     public String getWidgetType() {
         return mWidgetType;
     }
@@ -244,11 +421,21 @@
     /**
      * Returns a string version info for the widget this event was triggered in.
      */
+    @Nullable
     public String getWidgetVersion() {
         return mWidgetVersion;
     }
 
     /**
+     * Sets the {@link TextClassificationContext} for this event.
+     */
+    void setTextClassificationSessionContext(TextClassificationContext context) {
+        mPackageName = context.getPackageName();
+        mWidgetType = context.getWidgetType();
+        mWidgetVersion = context.getWidgetVersion();
+    }
+
+    /**
      * Returns the way the selection mode was invoked.
      */
     public @InvocationMethod int getInvocationMethod() {
@@ -256,6 +443,13 @@
     }
 
     /**
+     * Sets the invocationMethod for this event.
+     */
+    void setInvocationMethod(@InvocationMethod int invocationMethod) {
+        mInvocationMethod = invocationMethod;
+    }
+
+    /**
      * Returns the signature of the text classifier result associated with this event.
      */
     public String getSignature() {
@@ -320,17 +514,18 @@
     /**
      * Returns the selection session id.
      */
-    public String getSessionId() {
+    @Nullable
+    public TextClassificationSessionId getSessionId() {
         return mSessionId;
     }
 
-    SelectionEvent setSessionId(String id) {
+    SelectionEvent setSessionId(TextClassificationSessionId id) {
         mSessionId = id;
         return this;
     }
 
     /**
-     * Returns the start index of this events token relative to the index of the start selection
+     * Returns the start index of this events relative to the index of the start selection
      * event in the selection session.
      */
     public int getStart() {
@@ -343,7 +538,7 @@
     }
 
     /**
-     * Returns the end index of this events token relative to the index of the start selection
+     * Returns the end index of this events relative to the index of the start selection
      * event in the selection session.
      */
     public int getEnd() {
@@ -356,7 +551,7 @@
     }
 
     /**
-     * Returns the start index of this events token relative to the index of the smart selection
+     * Returns the start index of this events relative to the index of the smart selection
      * event in the selection session.
      */
     public int getSmartStart() {
@@ -369,7 +564,7 @@
     }
 
     /**
-     * Returns the end index of this events token relative to the index of the smart selection
+     * Returns the end index of this events relative to the index of the smart selection
      * event in the selection session.
      */
     public int getSmartEnd() {
@@ -382,7 +577,15 @@
     }
 
     boolean isTerminal() {
-        switch (mEventType) {
+        return isTerminal(mEventType);
+    }
+
+    /**
+     * Returns true if the eventType is a terminal event type. Otherwise returns false.
+     * A terminal event is an event that ends a selection interaction.
+     */
+    public static boolean isTerminal(@EventType int eventType) {
+        switch (eventType) {
             case ACTION_OVERTYPE:  // fall through
             case ACTION_COPY:  // fall through
             case ACTION_PASTE:  // fall through
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
new file mode 100644
index 0000000..a88f2f6
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.textclassifier.TextClassifier.WidgetType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Locale;
+
+/**
+ * A representation of the context in which text classification would be performed.
+ * @see TextClassificationManager#createTextClassificationSession(TextClassificationContext)
+ */
+public final class TextClassificationContext {
+
+    private final String mPackageName;
+    private final String mWidgetType;
+    @Nullable private final String mWidgetVersion;
+
+    private TextClassificationContext(
+            String packageName,
+            String widgetType,
+            String widgetVersion) {
+        mPackageName = Preconditions.checkNotNull(packageName);
+        mWidgetType = Preconditions.checkNotNull(widgetType);
+        mWidgetVersion = widgetVersion;
+    }
+
+    /**
+     * Returns the package name for the calling package.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Returns the widget type for this classification context.
+     */
+    @NonNull
+    @WidgetType
+    public String getWidgetType() {
+        return mWidgetType;
+    }
+
+    /**
+     * Returns a custom version string for the widget type.
+     *
+     * @see #getWidgetType()
+     */
+    @Nullable
+    public String getWidgetVersion() {
+        return mWidgetVersion;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US, "TextClassificationContext{"
+                + "packageName=%s, widgetType=%s, widgetVersion=%s}",
+                mPackageName, mWidgetType, mWidgetVersion);
+    }
+
+    /**
+     * A builder for building a TextClassification context.
+     */
+    public static final class Builder {
+
+        private final String mPackageName;
+        private final String mWidgetType;
+
+        @Nullable private String mWidgetVersion;
+
+        /**
+         * Initializes a new builder for text classification context objects.
+         *
+         * @param packageName the name of the calling package
+         * @param widgetType the type of widget e.g. {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}
+         *
+         * @return this builder
+         */
+        public Builder(@NonNull String packageName, @NonNull @WidgetType String widgetType) {
+            mPackageName = Preconditions.checkNotNull(packageName);
+            mWidgetType = Preconditions.checkNotNull(widgetType);
+        }
+
+        /**
+         * Sets an optional custom version string for the widget type.
+         *
+         * @return this builder
+         */
+        public Builder setWidgetVersion(@Nullable String widgetVersion) {
+            mWidgetVersion = widgetVersion;
+            return this;
+        }
+
+        /**
+         * Builds the text classification context object.
+         *
+         * @return the built TextClassificationContext object
+         */
+        @NonNull
+        public TextClassificationContext build() {
+            return new TextClassificationContext(mPackageName, mWidgetType, mWidgetVersion);
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index a7f1ca1..262d9b8 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -16,6 +16,7 @@
 
 package android.view.textclassifier;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
@@ -36,6 +37,9 @@
     private static final String LOG_TAG = "TextClassificationManager";
 
     private final Object mLock = new Object();
+    private final TextClassificationSessionFactory mDefaultSessionFactory =
+            classificationContext -> new TextClassificationSession(
+                    classificationContext, getTextClassifier());
 
     private final Context mContext;
     private final TextClassificationConstants mSettings;
@@ -46,12 +50,15 @@
     private TextClassifier mLocalTextClassifier;
     @GuardedBy("mLock")
     private TextClassifier mSystemTextClassifier;
+    @GuardedBy("mLock")
+    private TextClassificationSessionFactory mSessionFactory;
 
     /** @hide */
     public TextClassificationManager(Context context) {
         mContext = Preconditions.checkNotNull(context);
         mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString(
                 context.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
+        mSessionFactory = mDefaultSessionFactory;
     }
 
     /**
@@ -61,6 +68,7 @@
      *
      * @see #setTextClassifier(TextClassifier)
      */
+    @NonNull
     public TextClassifier getTextClassifier() {
         synchronized (mLock) {
             if (mTextClassifier == null) {
@@ -93,7 +101,6 @@
      * @see TextClassifier#SYSTEM
      * @hide
      */
-    // TODO: Expose as system API.
     public TextClassifier getTextClassifier(@TextClassifierType int type) {
         switch (type) {
             case TextClassifier.LOCAL:
@@ -108,6 +115,61 @@
         return mSettings;
     }
 
+    /**
+     * Call this method to start a text classification session with the given context.
+     * A session is created with a context helping the classifier better understand
+     * what the user needs and consists of queries and feedback events. The queries
+     * are directly related to providing useful functionality to the user and the events
+     * are a feedback loop back to the classifier helping it learn and better serve
+     * future queries.
+     *
+     * <p> All interactions with the returned classifier are considered part of a single
+     * session and are logically grouped. For example, when a text widget is focused
+     * all user interactions around text editing (selection, editing, etc) can be
+     * grouped together to allow the classifier get better.
+     *
+     * @param classificationContext The context in which classification would occur
+     *
+     * @return An instance to perform classification in the given context
+     */
+    @NonNull
+    public TextClassifier createTextClassificationSession(
+            @NonNull TextClassificationContext classificationContext) {
+        Preconditions.checkNotNull(classificationContext);
+        final TextClassifier textClassifier =
+                mSessionFactory.createTextClassificationSession(classificationContext);
+        Preconditions.checkNotNull(textClassifier, "Session Factory should never return null");
+        return textClassifier;
+    }
+
+    /**
+     * @see #createTextClassificationSession(TextClassificationContext, TextClassifier)
+     * @hide
+     */
+    public TextClassifier createTextClassificationSession(
+            TextClassificationContext classificationContext, TextClassifier textClassifier) {
+        Preconditions.checkNotNull(classificationContext);
+        Preconditions.checkNotNull(textClassifier);
+        return new TextClassificationSession(classificationContext, textClassifier);
+    }
+
+    /**
+     * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers.
+     *
+     * @param factory the textClassification session factory. If this is null, the default factory
+     *      will be used.
+     */
+    public void setTextClassificationSessionFactory(
+            @Nullable TextClassificationSessionFactory factory) {
+        synchronized (mLock) {
+            if (factory != null) {
+                mSessionFactory = factory;
+            } else {
+                mSessionFactory = mDefaultSessionFactory;
+            }
+        }
+    }
+
     private TextClassifier getSystemTextClassifier() {
         synchronized (mLock) {
             if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) {
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
new file mode 100644
index 0000000..6938e1a
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -0,0 +1,217 @@
+/*
+ * 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;
+
+import android.annotation.WorkerThread;
+import android.view.textclassifier.DefaultLogger.SignatureParser;
+import android.view.textclassifier.SelectionEvent.InvocationMethod;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Session-aware TextClassifier.
+ */
+@WorkerThread
+final class TextClassificationSession implements TextClassifier {
+
+    /* package */ static final boolean DEBUG_LOG_ENABLED = true;
+    private static final String LOG_TAG = "TextClassificationSession";
+
+    private final TextClassifier mDelegate;
+    private final SelectionEventHelper mEventHelper;
+
+    private boolean mDestroyed;
+
+    TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
+        mDelegate = Preconditions.checkNotNull(delegate);
+        mEventHelper = new SelectionEventHelper(new TextClassificationSessionId(), context);
+    }
+
+    @Override
+    public TextSelection suggestSelection(CharSequence text, int selectionStartIndex,
+            int selectionEndIndex, TextSelection.Options options) {
+        checkDestroyed();
+        return mDelegate.suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
+    }
+
+    @Override
+    public TextClassification classifyText(CharSequence text, int startIndex, int endIndex,
+            TextClassification.Options options) {
+        checkDestroyed();
+        return mDelegate.classifyText(text, startIndex, endIndex, options);
+    }
+
+    @Override
+    public TextLinks generateLinks(CharSequence text, TextLinks.Options options) {
+        checkDestroyed();
+        return mDelegate.generateLinks(text, options);
+    }
+
+    @Override
+    public void onSelectionEvent(SelectionEvent event) {
+        checkDestroyed();
+        Preconditions.checkNotNull(event);
+        if (mEventHelper.sanitizeEvent(event)) {
+            mDelegate.onSelectionEvent(event);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        mEventHelper.endSession();
+        mDestroyed = true;
+    }
+
+    @Override
+    public boolean isDestroyed() {
+        return mDestroyed;
+    }
+
+    /**
+     * @throws IllegalStateException if this TextClassification session has been destroyed.
+     * @see #isDestroyed()
+     * @see #destroy()
+     */
+    private void checkDestroyed() {
+        if (mDestroyed) {
+            throw new IllegalStateException("This TextClassification session has been destroyed");
+        }
+    }
+
+    /**
+     * Helper class for updating SelectionEvent fields.
+     */
+    private static final class SelectionEventHelper {
+
+        private final TextClassificationSessionId mSessionId;
+        private final TextClassificationContext mContext;
+
+        @InvocationMethod
+        private int mInvocationMethod = SelectionEvent.INVOCATION_UNKNOWN;
+        private SelectionEvent mPrevEvent;
+        private SelectionEvent mSmartEvent;
+        private SelectionEvent mStartEvent;
+
+        SelectionEventHelper(
+                TextClassificationSessionId sessionId, TextClassificationContext context) {
+            mSessionId = Preconditions.checkNotNull(sessionId);
+            mContext = Preconditions.checkNotNull(context);
+        }
+
+        /**
+         * Updates the necessary fields in the event for the current session.
+         *
+         * @return true if the event should be reported. false if the event should be ignored
+         */
+        boolean sanitizeEvent(SelectionEvent event) {
+            updateInvocationMethod(event);
+            modifyAutoSelectionEventType(event);
+
+            if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED
+                    && mStartEvent == null) {
+                if (DEBUG_LOG_ENABLED) {
+                    Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
+                }
+                return false;
+            }
+
+            final long now = System.currentTimeMillis();
+            switch (event.getEventType()) {
+                case SelectionEvent.EVENT_SELECTION_STARTED:
+                    Preconditions.checkArgument(
+                            event.getAbsoluteEnd() == event.getAbsoluteStart() + 1);
+                    event.setSessionId(mSessionId);
+                    mStartEvent = event;
+                    break;
+                case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:  // fall through
+                case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
+                    mSmartEvent = event;
+                    break;
+                case SelectionEvent.EVENT_SELECTION_MODIFIED:  // fall through
+                case SelectionEvent.EVENT_AUTO_SELECTION:
+                    if (mPrevEvent != null
+                            && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart()
+                            && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) {
+                        // Selection did not change. Ignore event.
+                        return false;
+                    }
+                    break;
+                default:
+                    // do nothing.
+            }
+
+            event.setEventTime(now);
+            if (mStartEvent != null) {
+                event.setSessionId(mStartEvent.getSessionId())
+                        .setDurationSinceSessionStart(now - mStartEvent.getEventTime())
+                        .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
+                        .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
+            }
+            if (mSmartEvent != null) {
+                event.setSignature(mSmartEvent.getSignature())
+                        .setSmartStart(
+                                mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
+                        .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
+            }
+            if (mPrevEvent != null) {
+                event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime())
+                        .setEventIndex(mPrevEvent.getEventIndex() + 1);
+            }
+            mPrevEvent = event;
+            return true;
+        }
+
+        void endSession() {
+            mPrevEvent = null;
+            mSmartEvent = null;
+            mStartEvent = null;
+        }
+
+        private void updateInvocationMethod(SelectionEvent event) {
+            event.setTextClassificationSessionContext(mContext);
+            if (event.getInvocationMethod() == SelectionEvent.INVOCATION_UNKNOWN) {
+                event.setInvocationMethod(mInvocationMethod);
+            } else {
+                mInvocationMethod = event.getInvocationMethod();
+            }
+        }
+
+        private void modifyAutoSelectionEventType(SelectionEvent event) {
+            switch (event.getEventType()) {
+                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 (event.getAbsoluteEnd() - event.getAbsoluteStart() > 1) {
+                            event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI);
+                        } else {
+                            event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_SINGLE);
+                        }
+                    } else {
+                        event.setEventType(SelectionEvent.EVENT_AUTO_SELECTION);
+                    }
+                    return;
+                default:
+                    return;
+            }
+        }
+
+        private static boolean isPlatformLocalTextClassifierSmartSelection(String signature) {
+            return DefaultLogger.CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature));
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionFactory.java b/core/java/android/view/textclassifier/TextClassificationSessionFactory.java
new file mode 100644
index 0000000..c0914b6
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationSessionFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+
+/**
+ * An interface for creating a session-aware TextClassifier.
+ *
+ * @see TextClassificationManager#createTextClassificationSession(TextClassificationContext)
+ */
+public interface TextClassificationSessionFactory {
+
+    /**
+     * Creates and returns a session-aware TextClassifier.
+     *
+     * @param classificationContext the classification context
+     */
+    @NonNull
+    TextClassifier createTextClassificationSession(
+            @NonNull TextClassificationContext classificationContext);
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java
new file mode 100644
index 0000000..3e4dc1c
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 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.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.UUID;
+
+/**
+ * This class represents the id of a text classification session.
+ */
+public final class TextClassificationSessionId implements Parcelable {
+    private final @NonNull String mValue;
+
+    /**
+     * Creates a new instance.
+     *
+     * @hide
+     */
+    public TextClassificationSessionId() {
+        this(UUID.randomUUID().toString());
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param value The internal value.
+     *
+     * @hide
+     */
+    public TextClassificationSessionId(@NonNull String value) {
+        mValue = value;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mValue.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        TextClassificationSessionId other = (TextClassificationSessionId) obj;
+        if (!mValue.equals(other.mValue)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mValue);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flattens this id to a string.
+     *
+     * @return The flattened id.
+     *
+     * @hide
+     */
+    public @NonNull String flattenToString() {
+        return mValue;
+    }
+
+    /**
+     * Unflattens a print job id from a string.
+     *
+     * @param string The string.
+     * @return The unflattened id, or null if the string is malformed.
+     *
+     * @hide
+     */
+    public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) {
+        return new TextClassificationSessionId(string);
+    }
+
+    public static final Parcelable.Creator<TextClassificationSessionId> CREATOR =
+            new Parcelable.Creator<TextClassificationSessionId>() {
+                @Override
+                public TextClassificationSessionId createFromParcel(Parcel parcel) {
+                    return new TextClassificationSessionId(
+                            Preconditions.checkNotNull(parcel.readString()));
+                }
+
+                @Override
+                public TextClassificationSessionId[] newArray(int size) {
+                    return new TextClassificationSessionId[size];
+                }
+            };
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 98fa574..2048f2b 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -112,6 +112,38 @@
     @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
     @interface Hints {}
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDITTEXT,
+            WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW, WIDGET_TYPE_CUSTOM_EDITTEXT,
+            WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_TYPE_UNKNOWN})
+    @interface WidgetType {}
+
+    /** The widget involved in the text classification session is a standard
+     * {@link android.widget.TextView}. */
+    String WIDGET_TYPE_TEXTVIEW = "textview";
+    /** The widget involved in the text classification session is a standard
+     * {@link android.widget.EditText}. */
+    String WIDGET_TYPE_EDITTEXT = "edittext";
+    /** The widget involved in the text classification session is a standard non-selectable
+     * {@link android.widget.TextView}. */
+    String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
+    /** The widget involved in the text classification session is a standard
+     * {@link android.webkit.WebView}. */
+    String WIDGET_TYPE_WEBVIEW = "webview";
+    /** The widget involved in the text classification session is a standard editable
+     * {@link android.webkit.WebView}. */
+    String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
+    /** The widget involved in the text classification session is a custom text widget. */
+    String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
+    /** The widget involved in the text classification session is a custom editable text widget. */
+    String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
+    /** The widget involved in the text classification session is a custom non-selectable text
+     * widget. */
+    String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
+    /** The widget involved in the text classification session is of an unknown/unspecified type. */
+    String WIDGET_TYPE_UNKNOWN = "unknown";
+
     /**
      * No-op TextClassifier.
      * This may be used to turn off TextClassifier features.
@@ -124,6 +156,9 @@
      *
      * <p><strong>NOTE: </strong>Call on a worker thread.
      *
+     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
      * @param text 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
@@ -156,6 +191,9 @@
      *
      * <p><strong>NOTE: </strong>Call on a worker thread.
      *
+     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
      * @param text 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
@@ -185,6 +223,9 @@
      * <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()}.
      */
     @WorkerThread
     @NonNull
@@ -205,6 +246,9 @@
      *
      * <p><strong>NOTE: </strong>Call on a worker thread.
      *
+     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
      * @param text 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
@@ -237,6 +281,9 @@
      * {@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()}.
+     *
      * @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
@@ -265,6 +312,9 @@
      * <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()}.
      */
     @WorkerThread
     @NonNull
@@ -285,6 +335,9 @@
      *
      * <p><strong>NOTE: </strong>Call on a worker thread.
      *
+     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
      * @param text the text to generate annotations for
      * @param options configuration for link generation
      *
@@ -295,6 +348,7 @@
      * @see #getMaxGenerateLinksTextLength()
      */
     @WorkerThread
+    @NonNull
     default TextLinks generateLinks(
             @NonNull CharSequence text, @Nullable TextLinks.Options options) {
         Utils.validate(text, false);
@@ -311,6 +365,9 @@
      * {@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
@@ -320,6 +377,7 @@
      * @see #getMaxGenerateLinksTextLength()
      */
     @WorkerThread
+    @NonNull
     default TextLinks generateLinks(@NonNull CharSequence text) {
         return generateLinks(text, null);
     }
@@ -327,6 +385,9 @@
     /**
      * Returns the maximal length of text that can be processed by generateLinks.
      *
+     * <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)
      */
@@ -339,6 +400,7 @@
      * Returns a helper for logging TextClassifier related events.
      *
      * @param config logger configuration
+     * @hide
      */
     @WorkerThread
     default Logger getLogger(@NonNull Logger.Config config) {
@@ -347,6 +409,37 @@
     }
 
     /**
+     * Reports a selection event.
+     *
+     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     */
+    default void onSelectionEvent(@NonNull SelectionEvent event) {}
+
+    /**
+     * Destroys this TextClassifier.
+     *
+     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should
+     * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
+     *
+     * <p>Subsequent calls to this method are no-ops.
+     */
+    default void destroy() {}
+
+    /**
+     * Returns whether or not this TextClassifier has been destroyed.
+     *
+     * <strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact
+     * with the classifier and an attempt to do so would throw an {@link IllegalStateException}.
+     * However, this method should never throw an {@link IllegalStateException}.
+     *
+     * @see #destroy()
+     */
+    default boolean isDestroyed() {
+        return false;
+    }
+
+    /**
      * Configuration object for specifying what entities to identify.
      *
      * Configs are initially based on a predefined preset, and can be modified from there.
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index c2fb032..3164427 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -93,6 +93,8 @@
     private Logger.Config mLoggerConfig;
     @GuardedBy("mLoggerLock") // Do not access outside this lock.
     private Logger mLogger;
+    @GuardedBy("mLoggerLock") // Do not access outside this lock.
+    private Logger mLogger2;  // This is the new logger. Will replace mLogger.
 
     private final TextClassificationConstants mSettings;
 
@@ -299,6 +301,18 @@
         return mLogger;
     }
 
+    @Override
+    public void onSelectionEvent(SelectionEvent event) {
+        Preconditions.checkNotNull(event);
+        synchronized (mLoggerLock) {
+            if (mLogger2 == null) {
+                mLogger2 = new DefaultLogger(
+                        new Logger.Config(mContext, WIDGET_TYPE_UNKNOWN, null));
+            }
+            mLogger2.writeEvent(event);
+        }
+    }
+
     private TextClassifierImplNative getNative(LocaleList localeList)
             throws FileNotFoundException {
         synchronized (mLock) {
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 6e855ba..9e4e6d6 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -35,6 +35,7 @@
 import android.view.ActionMode;
 import android.view.textclassifier.Logger;
 import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.SelectionEvent.InvocationMethod;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationConstants;
 import android.view.textclassifier.TextClassificationManager;
@@ -86,7 +87,7 @@
         mTextClassificationSettings = TextClassificationManager.getSettings(mTextView.getContext());
         mTextClassificationHelper = new TextClassificationHelper(
                 mTextView.getContext(),
-                mTextView.getTextClassifier(),
+                mTextView::getTextClassifier,
                 getText(mTextView),
                 0, 1, mTextView.getTextLocales());
         mSelectionTracker = new SelectionTracker(mTextView);
@@ -218,7 +219,7 @@
 
     private boolean skipTextClassification() {
         // No need to make an async call for a no-op TextClassifier.
-        final boolean noOpTextClassifier = mTextView.getTextClassifier() == TextClassifier.NO_OP;
+        final boolean noOpTextClassifier = mTextView.usesNoOpTextClassifier();
         // Do not call the TextClassifier if there is no selection.
         final boolean noSelection = mTextView.getSelectionEnd() == mTextView.getSelectionStart();
         // Do not call the TextClassifier if this is a password field.
@@ -444,7 +445,7 @@
             selectionEnd = mTextView.getSelectionEnd();
         }
         mTextClassificationHelper.init(
-                mTextView.getTextClassifier(),
+                mTextView::getTextClassifier,
                 getText(mTextView),
                 selectionStart, selectionEnd,
                 mTextView.getTextLocales());
@@ -657,6 +658,7 @@
         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;
@@ -665,26 +667,27 @@
 
         SelectionMetricsLogger(TextView textView) {
             Preconditions.checkNotNull(textView);
+            mTextClassificationSession = textView::getTextClassificationSession;
             mLogger = textView.getTextClassifier().getLogger(
                     new Logger.Config(textView.getContext(), getWidetType(textView), null));
             mEditTextLogger = textView.isTextEditable();
             mTokenIterator = mLogger.getTokenIterator(textView.getTextLocale());
         }
 
-        @Logger.WidgetType
+        @TextClassifier.WidgetType
         private static String getWidetType(TextView textView) {
             if (textView.isTextEditable()) {
-                return Logger.WIDGET_EDITTEXT;
+                return TextClassifier.WIDGET_TYPE_EDITTEXT;
             }
             if (textView.isTextSelectable()) {
-                return Logger.WIDGET_TEXTVIEW;
+                return TextClassifier.WIDGET_TYPE_TEXTVIEW;
             }
-            return Logger.WIDGET_UNSELECTABLE_TEXTVIEW;
+            return TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
         }
 
         public void logSelectionStarted(
                 CharSequence text, int index,
-                @SelectionEvent.InvocationMethod int invocationMethod) {
+                @InvocationMethod int invocationMethod) {
             try {
                 Preconditions.checkNotNull(text);
                 Preconditions.checkArgumentInRange(index, 0, text.length(), "index");
@@ -694,9 +697,12 @@
                 mTokenIterator.setText(mText);
                 mStartIndex = index;
                 mLogger.logSelectionStartedEvent(invocationMethod, 0);
+                // TODO: Remove the above legacy logging.
+                mTextClassificationSession.get().onSelectionEvent(
+                        SelectionEvent.createSelectionStartedEvent(invocationMethod, 0));
             } catch (Exception e) {
                 // Avoid crashes due to logging.
-                Log.d(LOG_TAG, e.getMessage());
+                Log.e(LOG_TAG, "" + e.getMessage(), e);
             }
         }
 
@@ -709,16 +715,28 @@
                 if (selection != null) {
                     mLogger.logSelectionModifiedEvent(
                             wordIndices[0], wordIndices[1], selection);
+                    // TODO: Remove the above legacy logging.
+                    mTextClassificationSession.get().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));
                 } else {
                     mLogger.logSelectionModifiedEvent(
                             wordIndices[0], wordIndices[1]);
+                    // TODO: Remove the above legacy logging.
+                    mTextClassificationSession.get().onSelectionEvent(
+                            SelectionEvent.createSelectionModifiedEvent(
+                                    wordIndices[0], wordIndices[1]));
                 }
             } catch (Exception e) {
                 // Avoid crashes due to logging.
-                Log.d(LOG_TAG, e.getMessage());
+                Log.e(LOG_TAG, "" + e.getMessage(), e);
             }
         }
 
@@ -733,13 +751,25 @@
                 if (classification != null) {
                     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));
                 } else {
                     mLogger.logSelectionActionEvent(
                             wordIndices[0], wordIndices[1], action);
+                    // TODO: Remove the above legacy logging.
+                    mTextClassificationSession.get().onSelectionEvent(
+                            SelectionEvent.createSelectionActionEvent(
+                                    wordIndices[0], wordIndices[1], action));
                 }
             } catch (Exception e) {
                 // Avoid crashes due to logging.
-                Log.d(LOG_TAG, e.getMessage());
+                Log.e(LOG_TAG, "" + e.getMessage(), e);
+            } finally {
+                if (SelectionEvent.isTerminal(action)) {
+                    mTextClassificationSession.get().destroy();
+                }
             }
         }
 
@@ -880,7 +910,7 @@
 
         private final Context mContext;
         private final boolean mDarkLaunchEnabled;
-        private TextClassifier mTextClassifier;
+        private Supplier<TextClassifier> mTextClassifier;
 
         /** The original TextView text. **/
         private String mText;
@@ -912,7 +942,7 @@
         /** Whether the TextClassifier has been initialized. */
         private boolean mHot;
 
-        TextClassificationHelper(Context context, TextClassifier textClassifier,
+        TextClassificationHelper(Context context, Supplier<TextClassifier> textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
             init(textClassifier, text, selectionStart, selectionEnd, locales);
             mContext = Preconditions.checkNotNull(context);
@@ -921,7 +951,7 @@
         }
 
         @UiThread
-        public void init(TextClassifier textClassifier, CharSequence text,
+        public void init(Supplier<TextClassifier> textClassifier, CharSequence text,
                 int selectionStart, int selectionEnd, LocaleList locales) {
             mTextClassifier = Preconditions.checkNotNull(textClassifier);
             mText = Preconditions.checkNotNull(text).toString();
@@ -946,11 +976,11 @@
             trimText();
             final TextSelection selection;
             if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
-                selection = mTextClassifier.suggestSelection(
+                selection = mTextClassifier.get().suggestSelection(
                         mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
             } else {
                 // Use old APIs.
-                selection = mTextClassifier.suggestSelection(
+                selection = mTextClassifier.get().suggestSelection(
                         mTrimmedText, mRelativeStart, mRelativeEnd,
                         mSelectionOptions.getDefaultLocales());
             }
@@ -995,11 +1025,11 @@
                 trimText();
                 final TextClassification classification;
                 if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
-                    classification = mTextClassifier.classifyText(
+                    classification = mTextClassifier.get().classifyText(
                             mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
                 } else {
                     // Use old APIs.
-                    classification = mTextClassifier.classifyText(
+                    classification = mTextClassifier.get().classifyText(
                             mTrimmedText, mRelativeStart, mRelativeEnd,
                             mClassificationOptions.getDefaultLocales());
                 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c366a91..6f25577 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -163,6 +163,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextLinks;
@@ -427,6 +428,7 @@
     private boolean mPreDrawListenerDetached;
 
     private TextClassifier mTextClassifier;
+    private TextClassifier mTextClassificationSession;
 
     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
@@ -11509,18 +11511,63 @@
     @NonNull
     public TextClassifier getTextClassifier() {
         if (mTextClassifier == null) {
-            TextClassificationManager tcm =
+            final TextClassificationManager tcm =
                     mContext.getSystemService(TextClassificationManager.class);
             if (tcm != null) {
-                mTextClassifier = tcm.getTextClassifier();
-            } else {
-                mTextClassifier = TextClassifier.NO_OP;
+                return tcm.getTextClassifier();
             }
+            return TextClassifier.NO_OP;
         }
         return mTextClassifier;
     }
 
     /**
+     * Returns a session-aware text classifier.
+     */
+    @NonNull
+    TextClassifier getTextClassificationSession() {
+        if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
+            final TextClassificationManager tcm =
+                    mContext.getSystemService(TextClassificationManager.class);
+            if (tcm != null) {
+                final String widgetType;
+                if (isTextEditable()) {
+                    widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
+                } else if (isTextSelectable()) {
+                    widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
+                } else {
+                    widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
+                }
+                // TODO: Tagged this widgetType with a * so it we can monitor if it reports
+                // SelectionEvents exactly as the older Logger does. Remove once investigations
+                // are complete.
+                final TextClassificationContext textClassificationContext =
+                        new TextClassificationContext.Builder(
+                                mContext.getPackageName(), "*" + widgetType)
+                                .build();
+                if (mTextClassifier != null) {
+                    mTextClassificationSession = tcm.createTextClassificationSession(
+                            textClassificationContext, mTextClassifier);
+                } else {
+                    mTextClassificationSession = tcm.createTextClassificationSession(
+                            textClassificationContext);
+                }
+            } else {
+                mTextClassificationSession = TextClassifier.NO_OP;
+            }
+        }
+        return mTextClassificationSession;
+    }
+
+    /**
+     * Returns true if this TextView uses a no-op TextClassifier.
+     */
+    boolean usesNoOpTextClassifier() {
+        return getTextClassifier() == TextClassifier.NO_OP;
+    }
+
+
+    /**
      * Starts an ActionMode for the specified TextLinkSpan.
      *
      * @return Whether or not we're attempting to start the action mode.
diff --git a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
index b77982b..861a43a 100644
--- a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
@@ -33,8 +33,11 @@
     @Test
     public void testParcel() {
         final SelectionEvent[] captured = new SelectionEvent[1];
-        final Logger logger = new Logger(new Logger.Config(
-                InstrumentationRegistry.getTargetContext(), Logger.WIDGET_TEXTVIEW, null)) {
+        final Logger logger = new Logger(
+                new Logger.Config(
+                        InstrumentationRegistry.getTargetContext(),
+                        TextClassifier.WIDGET_TYPE_TEXTVIEW,
+                        null)) {
             @Override
             public void writeEvent(SelectionEvent event) {
                 captured[0] = event;