content: /*
   * 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.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.icu.util.ULocale;
  import android.os.Bundle;
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.Preconditions;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.Arrays;
  
  /**
   * This class represents events that are sent by components to the {@link TextClassifier} to report
   * something of note that relates to a feature powered by the TextClassifier. The TextClassifier may
   * log these events or use them to improve future responses to queries.
   * <p>
   * Each category of events has its their own subclass. Events of each type have an associated
   * set of related properties. You can find their specification in the subclasses.
   */
  public abstract class TextClassifierEvent implements Parcelable {
  
      private static final int PARCEL_TOKEN_TEXT_SELECTION_EVENT = 1;
      private static final int PARCEL_TOKEN_TEXT_LINKIFY_EVENT = 2;
      private static final int PARCEL_TOKEN_CONVERSATION_ACTION_EVENT = 3;
      private static final int PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT = 4;
  
      /** @hide **/
      @Retention(RetentionPolicy.SOURCE)
      @IntDef({CATEGORY_SELECTION, CATEGORY_LINKIFY,
              CATEGORY_CONVERSATION_ACTIONS, CATEGORY_LANGUAGE_DETECTION})
      public @interface Category {
          // For custom event categories, use range 1000+.
      }
  
      /**
       * Smart selection
       *
       * @see TextSelectionEvent
       */
      public static final int CATEGORY_SELECTION = 1;
      /**
       * Linkify
       *
       * @see TextLinkifyEvent
       */
      public static final int CATEGORY_LINKIFY = 2;
      /**
       *  Conversation actions
       *
       * @see ConversationActionsEvent
       */
      public static final int CATEGORY_CONVERSATION_ACTIONS = 3;
      /**
       * Language detection
       *
       * @see LanguageDetectionEvent
       */
      public static final int CATEGORY_LANGUAGE_DETECTION = 4;
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef({TYPE_SELECTION_STARTED, TYPE_SELECTION_MODIFIED,
              TYPE_SMART_SELECTION_SINGLE, TYPE_SMART_SELECTION_MULTI, TYPE_AUTO_SELECTION,
              TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION,
              TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION,
              TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL,
              TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED})
      public @interface Type {
          // For custom event types, use range 1,000,000+.
      }
  
      /** User started a new selection. */
      public static final int TYPE_SELECTION_STARTED = 1;
      /** User modified an existing selection. */
      public static final int TYPE_SELECTION_MODIFIED = 2;
      /** Smart selection triggered for a single token (word). */
      public static final int TYPE_SMART_SELECTION_SINGLE = 3;
      /** Smart selection triggered spanning multiple tokens (words). */
      public static final int TYPE_SMART_SELECTION_MULTI = 4;
      /** Something else other than user or the default TextClassifier triggered a selection. */
      public static final int TYPE_AUTO_SELECTION = 5;
      /** Smart actions shown to the user. */
      public static final int TYPE_ACTIONS_SHOWN = 6;
      /** User clicked a link. */
      public static final int TYPE_LINK_CLICKED = 7;
      /** User typed over the selection. */
      public static final int TYPE_OVERTYPE = 8;
      /** User clicked on Copy action. */
      public static final int TYPE_COPY_ACTION = 9;
      /** User clicked on Paste action. */
      public static final int TYPE_PASTE_ACTION = 10;
      /** User clicked on Cut action. */
      public static final int TYPE_CUT_ACTION = 11;
      /** User clicked on Share action. */
      public static final int TYPE_SHARE_ACTION = 12;
      /** User clicked on a Smart action. */
      public static final int TYPE_SMART_ACTION = 13;
      /** User dragged+dropped the selection. */
      public static final int TYPE_SELECTION_DRAG = 14;
      /** Selection is destroyed. */
      public static final int TYPE_SELECTION_DESTROYED = 15;
      /** User clicked on a custom action. */
      public static final int TYPE_OTHER_ACTION = 16;
      /** User clicked on Select All action */
      public static final int TYPE_SELECT_ALL = 17;
      /** User reset the smart selection. */
      public static final int TYPE_SELECTION_RESET = 18;
      /** User composed a reply. */
      public static final int TYPE_MANUAL_REPLY = 19;
      /** TextClassifier generated some actions */
      public static final int TYPE_ACTIONS_GENERATED = 20;
  
      @Category
      private final int mEventCategory;
      @Type
      private final int mEventType;
      @Nullable
      private final String[] mEntityTypes;
      @Nullable
      private TextClassificationContext mEventContext;
      @Nullable
      private final String mResultId;
      private final int mEventIndex;
      private final float[] mScores;
      @Nullable
      private final String mModelName;
      private final int[] mActionIndices;
      @Nullable
      private final ULocale mLocale;
      private final Bundle mExtras;
  
      /**
       * Session id holder to help with converting this event to the legacy SelectionEvent.
       * @hide
       */
      @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
      @Nullable
      public TextClassificationSessionId mHiddenTempSessionId;
  
      private TextClassifierEvent(Builder builder) {
          mEventCategory = builder.mEventCategory;
          mEventType = builder.mEventType;
          mEntityTypes = builder.mEntityTypes;
          mEventContext = builder.mEventContext;
          mResultId = builder.mResultId;
          mEventIndex = builder.mEventIndex;
          mScores = builder.mScores;
          mModelName = builder.mModelName;
          mActionIndices = builder.mActionIndices;
          mLocale = builder.mLocale;
          mExtras = builder.mExtras == null ? Bundle.EMPTY : builder.mExtras;
      }
  
      private TextClassifierEvent(Parcel in) {
          mEventCategory = in.readInt();
          mEventType = in.readInt();
          mEntityTypes = in.readStringArray();
          mEventContext = in.readParcelable(null);
          mResultId = in.readString();
          mEventIndex = in.readInt();
          int scoresLength = in.readInt();
          mScores = new float[scoresLength];
          in.readFloatArray(mScores);
          mModelName = in.readString();
          mActionIndices = in.createIntArray();
          final String languageTag = in.readString();
          mLocale = languageTag == null ? null : ULocale.forLanguageTag(languageTag);
          mExtras = in.readBundle();
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @NonNull
      public static final Creator<TextClassifierEvent> CREATOR = new Creator<TextClassifierEvent>() {
          @Override
          public TextClassifierEvent createFromParcel(Parcel in) {
              int token = in.readInt();
              if (token == PARCEL_TOKEN_TEXT_SELECTION_EVENT) {
                  return new TextSelectionEvent(in);
              }
              if (token == PARCEL_TOKEN_TEXT_LINKIFY_EVENT) {
                  return new TextLinkifyEvent(in);
              }
              if (token == PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT) {
                  return new LanguageDetectionEvent(in);
              }
              if (token == PARCEL_TOKEN_CONVERSATION_ACTION_EVENT) {
                  return new ConversationActionsEvent(in);
              }
              throw new IllegalStateException("Unexpected input event type token in parcel.");
          }
  
          @Override
          public TextClassifierEvent[] newArray(int size) {
              return new TextClassifierEvent[size];
          }
      };
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeInt(getParcelToken());
          dest.writeInt(mEventCategory);
          dest.writeInt(mEventType);
          dest.writeStringArray(mEntityTypes);
          dest.writeParcelable(mEventContext, flags);
          dest.writeString(mResultId);
          dest.writeInt(mEventIndex);
          dest.writeInt(mScores.length);
          dest.writeFloatArray(mScores);
          dest.writeString(mModelName);
          dest.writeIntArray(mActionIndices);
          dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
          dest.writeBundle(mExtras);
      }
  
      private int getParcelToken() {
          if (this instanceof TextSelectionEvent) {
              return PARCEL_TOKEN_TEXT_SELECTION_EVENT;
          }
          if (this instanceof TextLinkifyEvent) {
              return PARCEL_TOKEN_TEXT_LINKIFY_EVENT;
          }
          if (this instanceof LanguageDetectionEvent) {
              return PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT;
          }
          if (this instanceof ConversationActionsEvent) {
              return PARCEL_TOKEN_CONVERSATION_ACTION_EVENT;
          }
          throw new IllegalArgumentException("Unexpected type: " + this.getClass().getSimpleName());
      }
  
      /**
       * Returns the event category. e.g. {@link #CATEGORY_SELECTION}.
       */
      @Category
      public int getEventCategory() {
          return mEventCategory;
      }
  
      /**
       * Returns the event type. e.g. {@link #TYPE_SELECTION_STARTED}.
       */
      @Type
      public int getEventType() {
          return mEventType;
      }
  
      /**
       * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
       *
       * @see Builder#setEntityTypes(String...) for supported types.
       */
      @NonNull
      public String[] getEntityTypes() {
          return mEntityTypes;
      }
  
      /**
       * Returns the event context.
       */
      @Nullable
      public TextClassificationContext getEventContext() {
          return mEventContext;
      }
  
      /**
       * Sets the event context.
       * <p>
       * Package-private for SystemTextClassifier's use.
       */
      void setEventContext(@Nullable TextClassificationContext eventContext) {
          mEventContext = eventContext;
      }
  
      /**
       * Returns the id of the text classifier result related to this event.
       */
      @Nullable
      public String getResultId() {
          return mResultId;
      }
  
      /**
       * Returns the index of this event in the series of event it belongs to.
       */
      public int getEventIndex() {
          return mEventIndex;
      }
  
      /**
       * Returns the scores of the suggestions.
       */
      @NonNull
      public float[] getScores() {
          return mScores;
      }
  
      /**
       * Returns the model name.
       */
      @Nullable
      public String getModelName() {
          return mModelName;
      }
  
      /**
       * Returns the indices of the actions relating to this event.
       * Actions are usually returned by the text classifier in priority order with the most
       * preferred action at index 0. This list gives an indication of the position of the actions
       * that are being reported.
       *
       * @see Builder#setActionIndices(int...)
       */
      @NonNull
      public int[] getActionIndices() {
          return mActionIndices;
      }
  
      /**
       * Returns the detected locale.
       */
      @Nullable
      public ULocale getLocale() {
          return mLocale;
      }
  
      /**
       * Returns a bundle containing non-structured extra information about this event.
       *
       * <p><b>NOTE: </b>Do not modify this bundle.
       */
      @NonNull
      public Bundle getExtras() {
          return mExtras;
      }
  
      @Override
      public String toString() {
          StringBuilder out = new StringBuilder(128);
          out.append(this.getClass().getSimpleName());
          out.append("{");
          out.append("mEventCategory=").append(mEventCategory);
          out.append(", mEventTypes=").append(Arrays.toString(mEntityTypes));
          out.append(", mEventContext=").append(mEventContext);
          out.append(", mResultId=").append(mResultId);
          out.append(", mEventIndex=").append(mEventIndex);
          out.append(", mExtras=").append(mExtras);
          out.append(", mScores=").append(Arrays.toString(mScores));
          out.append(", mModelName=").append(mModelName);
          out.append(", mActionIndices=").append(Arrays.toString(mActionIndices));
          out.append("}");
          return out.toString();
      }
  
      /**
       * Returns a {@link SelectionEvent} equivalent of this event; or {@code null} if it can not be
       * converted to a {@link SelectionEvent}.
       * @hide
       */
      @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
      @Nullable
      public final SelectionEvent toSelectionEvent() {
          final int invocationMethod;
          switch (getEventCategory()) {
              case TextClassifierEvent.CATEGORY_SELECTION:
                  invocationMethod = SelectionEvent.INVOCATION_MANUAL;
                  break;
              case TextClassifierEvent.CATEGORY_LINKIFY:
                  invocationMethod = SelectionEvent.INVOCATION_LINK;
                  break;
              default:
                  // Cannot be converted to a SelectionEvent.
                  return null;
          }
  
          final String entityType = getEntityTypes().length > 0
                  ? getEntityTypes()[0] : TextClassifier.TYPE_UNKNOWN;
          final SelectionEvent out = new SelectionEvent(
                  /* absoluteStart= */ 0,
                  /* absoluteEnd= */ 0,
                  /* eventType= */0,
                  entityType,
                  SelectionEvent.INVOCATION_UNKNOWN,
                  SelectionEvent.NO_SIGNATURE);
          out.setInvocationMethod(invocationMethod);
  
          final TextClassificationContext eventContext = getEventContext();
          if (eventContext != null) {
              out.setTextClassificationSessionContext(getEventContext());
          }
          out.setSessionId(mHiddenTempSessionId);
          final String resultId = getResultId();
          out.setResultId(resultId == null ? SelectionEvent.NO_SIGNATURE : resultId);
          out.setEventIndex(getEventIndex());
  
  
          final int eventType;
          switch (getEventType()) {
              case TextClassifierEvent.TYPE_SELECTION_STARTED:
                  eventType = SelectionEvent.EVENT_SELECTION_STARTED;
                  break;
              case TextClassifierEvent.TYPE_SELECTION_MODIFIED:
                  eventType = SelectionEvent.EVENT_SELECTION_MODIFIED;
                  break;
              case TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE:
                  eventType = SelectionEvent.EVENT_SMART_SELECTION_SINGLE;
                  break;
              case TextClassifierEvent.TYPE_SMART_SELECTION_MULTI:
                  eventType = SelectionEvent.EVENT_SMART_SELECTION_MULTI;
                  break;
              case TextClassifierEvent.TYPE_AUTO_SELECTION:
                  eventType = SelectionEvent.EVENT_AUTO_SELECTION;
                  break;
              case TextClassifierEvent.TYPE_OVERTYPE:
                  eventType = SelectionEvent.ACTION_OVERTYPE;
                  break;
              case TextClassifierEvent.TYPE_COPY_ACTION:
                  eventType = SelectionEvent.ACTION_COPY;
                  break;
              case TextClassifierEvent.TYPE_PASTE_ACTION:
                  eventType = SelectionEvent.ACTION_PASTE;
                  break;
              case TextClassifierEvent.TYPE_CUT_ACTION:
                  eventType = SelectionEvent.ACTION_CUT;
                  break;
              case TextClassifierEvent.TYPE_SHARE_ACTION:
                  eventType = SelectionEvent.ACTION_SHARE;
                  break;
              case TextClassifierEvent.TYPE_SMART_ACTION:
                  eventType = SelectionEvent.ACTION_SMART_SHARE;
                  break;
              case TextClassifierEvent.TYPE_SELECTION_DRAG:
                  eventType = SelectionEvent.ACTION_DRAG;
                  break;
              case TextClassifierEvent.TYPE_SELECTION_DESTROYED:
                  eventType = SelectionEvent.ACTION_ABANDON;
                  break;
              case TextClassifierEvent.TYPE_OTHER_ACTION:
                  eventType = SelectionEvent.ACTION_OTHER;
                  break;
              case TextClassifierEvent.TYPE_SELECT_ALL:
                  eventType = SelectionEvent.ACTION_SELECT_ALL;
                  break;
              case TextClassifierEvent.TYPE_SELECTION_RESET:
                  eventType = SelectionEvent.ACTION_RESET;
                  break;
              default:
                  eventType = 0;
                  break;
          }
          out.setEventType(eventType);
  
          if (this instanceof TextClassifierEvent.TextSelectionEvent) {
              final TextClassifierEvent.TextSelectionEvent selEvent =
                      (TextClassifierEvent.TextSelectionEvent) this;
              // TODO: Ideally, we should have these fields in events of type
              // TextClassifierEvent.TextLinkifyEvent events too but we're now past the API deadline
              // and will have to do with these fields being set only in TextSelectionEvent events.
              // Fix this at the next API bump.
              out.setStart(selEvent.getRelativeWordStartIndex());
              out.setEnd(selEvent.getRelativeWordEndIndex());
              out.setSmartStart(selEvent.getRelativeSuggestedWordStartIndex());
              out.setSmartEnd(selEvent.getRelativeSuggestedWordEndIndex());
          }
  
          return out;
      }
  
      /**
       * Builder to build a text classifier event.
       *
       * @param <T> The subclass to be built.
       */
      public abstract static class Builder<T extends Builder<T>> {
  
          private final int mEventCategory;
          private final int mEventType;
          private String[] mEntityTypes = new String[0];
          @Nullable
          private TextClassificationContext mEventContext;
          @Nullable
          private String mResultId;
          private int mEventIndex;
          private float[] mScores = new float[0];
          @Nullable
          private String mModelName;
          private int[] mActionIndices = new int[0];
          @Nullable
          private ULocale mLocale;
          @Nullable
          private Bundle mExtras;
  
          /**
           * Creates a builder for building {@link TextClassifierEvent}s.
           *
           * @param eventCategory The event category. e.g. {@link #CATEGORY_SELECTION}
           * @param eventType     The event type. e.g. {@link #TYPE_SELECTION_STARTED}
           */
          private Builder(@Category int eventCategory, @Type int eventType) {
              mEventCategory = eventCategory;
              mEventType = eventType;
          }
  
          /**
           * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
           * <p>
           * Supported types:
           * <p>See {@link TextClassifier.EntityType}
           * <p>See {@link ConversationAction.ActionType}
           * <p>See {@link ULocale#toLanguageTag()}
           */
          @NonNull
          public T setEntityTypes(@NonNull String... entityTypes) {
              Preconditions.checkNotNull(entityTypes);
              mEntityTypes = new String[entityTypes.length];
              System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
              return self();
          }
  
          /**
           * Sets the event context.
           */
          @NonNull
          public T setEventContext(@Nullable TextClassificationContext eventContext) {
              mEventContext = eventContext;
              return self();
          }
  
          /**
           * Sets the id of the text classifier result related to this event.
           */
          @NonNull
          public T setResultId(@Nullable String resultId) {
              mResultId = resultId;
              return self();
          }
  
          /**
           * Sets the index of this event in the series of events it belongs to.
           */
          @NonNull
          public T setEventIndex(int eventIndex) {
              mEventIndex = eventIndex;
              return self();
          }
  
          /**
           * Sets the scores of the suggestions.
           */
          @NonNull
          public T setScores(@NonNull float... scores) {
              Preconditions.checkNotNull(scores);
              mScores = new float[scores.length];
              System.arraycopy(scores, 0, mScores, 0, scores.length);
              return self();
          }
  
          /**
           * Sets the model name string.
           */
          @NonNull
          public T setModelName(@Nullable String modelVersion) {
              mModelName = modelVersion;
              return self();
          }
  
          /**
           * Sets the indices of the actions involved in this event. Actions are usually returned by
           * the text classifier in priority order with the most preferred action at index 0.
           * These indices give an indication of the position of the actions that are being reported.
           * <p>
           * E.g.
           * <pre>
           *   // 3 smart actions are shown at index 0, 1, 2 respectively in response to a link click.
           *   new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_ACTIONS_SHOWN)
           *       .setEventIndex(0, 1, 2)
           *       ...
           *       .build();
           *
           *   ...
           *
           *   // Smart action at index 1 is activated.
           *   new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_SMART_ACTION)
           *       .setEventIndex(1)
           *       ...
           *       .build();
           * </pre>
           *
           * @see TextClassification#getActions()
           */
          @NonNull
          public T setActionIndices(@NonNull int... actionIndices) {
              mActionIndices = new int[actionIndices.length];
              System.arraycopy(actionIndices, 0, mActionIndices, 0, actionIndices.length);
              return self();
          }
  
          /**
           * Sets the detected locale.
           */
          @NonNull
          public T setLocale(@Nullable ULocale locale) {
              mLocale = locale;
              return self();
          }
  
          /**
           * Sets a bundle containing non-structured extra information about the event.
           *
           * <p><b>NOTE: </b>Prefer to set only immutable values on the bundle otherwise, avoid
           * updating the internals of this bundle as it may have unexpected consequences on the
           * clients of the built event object. For similar reasons, avoid depending on mutable
           * objects in this bundle.
           */
          @NonNull
          public T setExtras(@NonNull Bundle extras) {
              mExtras = Preconditions.checkNotNull(extras);
              return self();
          }
  
          abstract T self();
      }
  
      /**
       * This class represents events that are related to the smart text selection feature.
       * <p>
       * <pre>
       *     // User started a selection. e.g. "York" in text "New York City, NY".
       *     new TextSelectionEvent.Builder(TYPE_SELECTION_STARTED)
       *         .setEventContext(classificationContext)
       *         .setEventIndex(0)
       *         .build();
       *
       *     // System smart-selects a recognized entity. e.g. "New York City".
       *     new TextSelectionEvent.Builder(TYPE_SMART_SELECTION_MULTI)
       *         .setEventContext(classificationContext)
       *         .setResultId(textSelection.getId())
       *         .setRelativeWordStartIndex(-1) // Goes back one word to "New" from "York".
       *         .setRelativeWordEndIndex(2)    // Goes forward 2 words from "York" to start of ",".
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setEventIndex(1)
       *         .build();
       *
       *     // User resets the selection to the original selection. i.e. "York".
       *     new TextSelectionEvent.Builder(TYPE_SELECTION_RESET)
       *         .setEventContext(classificationContext)
       *         .setResultId(textSelection.getId())
       *         .setRelativeSuggestedWordStartIndex(-1) // Repeated from above.
       *         .setRelativeSuggestedWordEndIndex(2)    // Repeated from above.
       *         .setRelativeWordStartIndex(0)           // Original selection is always at (0, 1].
       *         .setRelativeWordEndIndex(1)
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setEventIndex(2)
       *         .build();
       *
       *     // User modified the selection. e.g. "New".
       *     new TextSelectionEvent.Builder(TYPE_SELECTION_MODIFIED)
       *         .setEventContext(classificationContext)
       *         .setResultId(textSelection.getId())
       *         .setRelativeSuggestedWordStartIndex(-1) // Repeated from above.
       *         .setRelativeSuggestedWordEndIndex(2)    // Repeated from above.
       *         .setRelativeWordStartIndex(-1)          // Goes backward one word from "York" to
       *         "New".
       *         .setRelativeWordEndIndex(0)             // Goes backward one word to exclude "York".
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setEventIndex(3)
       *         .build();
       *
       *     // Smart (contextual) actions (at indices, 0, 1, 2) presented to the user.
       *     // e.g. "Map", "Ride share", "Explore".
       *     new TextSelectionEvent.Builder(TYPE_ACTIONS_SHOWN)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setActionIndices(0, 1, 2)
       *         .setEventIndex(4)
       *         .build();
       *
       *     // User chooses the "Copy" action.
       *     new TextSelectionEvent.Builder(TYPE_COPY_ACTION)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setEventIndex(5)
       *         .build();
       *
       *     // User chooses smart action at index 1. i.e. "Ride share".
       *     new TextSelectionEvent.Builder(TYPE_SMART_ACTION)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setActionIndices(1)
       *         .setEventIndex(5)
       *         .build();
       *
       *     // Selection dismissed.
       *     new TextSelectionEvent.Builder(TYPE_SELECTION_DESTROYED)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setEventIndex(6)
       *         .build();
       * </pre>
       * <p>
       */
      public static final class TextSelectionEvent extends TextClassifierEvent implements Parcelable {
  
          @NonNull
          public static final Creator<TextSelectionEvent> CREATOR =
                  new Creator<TextSelectionEvent>() {
                      @Override
                      public TextSelectionEvent createFromParcel(Parcel in) {
                          in.readInt(); // skip token, we already know this is a TextSelectionEvent
                          return new TextSelectionEvent(in);
                      }
  
                      @Override
                      public TextSelectionEvent[] newArray(int size) {
                          return new TextSelectionEvent[size];
                      }
                  };
  
          final int mRelativeWordStartIndex;
          final int mRelativeWordEndIndex;
          final int mRelativeSuggestedWordStartIndex;
          final int mRelativeSuggestedWordEndIndex;
  
          private TextSelectionEvent(TextSelectionEvent.Builder builder) {
              super(builder);
              mRelativeWordStartIndex = builder.mRelativeWordStartIndex;
              mRelativeWordEndIndex = builder.mRelativeWordEndIndex;
              mRelativeSuggestedWordStartIndex = builder.mRelativeSuggestedWordStartIndex;
              mRelativeSuggestedWordEndIndex = builder.mRelativeSuggestedWordEndIndex;
          }
  
          private TextSelectionEvent(Parcel in) {
              super(in);
              mRelativeWordStartIndex = in.readInt();
              mRelativeWordEndIndex = in.readInt();
              mRelativeSuggestedWordStartIndex = in.readInt();
              mRelativeSuggestedWordEndIndex = in.readInt();
          }
  
          @Override
          public void writeToParcel(Parcel dest, int flags) {
              super.writeToParcel(dest, flags);
              dest.writeInt(mRelativeWordStartIndex);
              dest.writeInt(mRelativeWordEndIndex);
              dest.writeInt(mRelativeSuggestedWordStartIndex);
              dest.writeInt(mRelativeSuggestedWordEndIndex);
          }
  
          /**
           * Returns the relative word index of the start of the selection.
           */
          public int getRelativeWordStartIndex() {
              return mRelativeWordStartIndex;
          }
  
          /**
           * Returns the relative word (exclusive) index of the end of the selection.
           */
          public int getRelativeWordEndIndex() {
              return mRelativeWordEndIndex;
          }
  
          /**
           * Returns the relative word index of the start of the smart selection.
           */
          public int getRelativeSuggestedWordStartIndex() {
              return mRelativeSuggestedWordStartIndex;
          }
  
          /**
           * Returns the relative word (exclusive) index of the end of the
           * smart selection.
           */
          public int getRelativeSuggestedWordEndIndex() {
              return mRelativeSuggestedWordEndIndex;
          }
  
          /**
           * Builder class for {@link TextSelectionEvent}.
           */
          public static final class Builder extends
                  TextClassifierEvent.Builder<TextSelectionEvent.Builder> {
              int mRelativeWordStartIndex;
              int mRelativeWordEndIndex;
              int mRelativeSuggestedWordStartIndex;
              int mRelativeSuggestedWordEndIndex;
  
              /**
               * Creates a builder for building {@link TextSelectionEvent}s.
               *
               * @param eventType     The event type. e.g. {@link #TYPE_SELECTION_STARTED}
               */
              public Builder(@Type int eventType) {
                  super(CATEGORY_SELECTION, eventType);
              }
  
              /**
               * Sets the relative word index of the start of the selection.
               */
              @NonNull
              public Builder setRelativeWordStartIndex(int relativeWordStartIndex) {
                  mRelativeWordStartIndex = relativeWordStartIndex;
                  return this;
              }
  
              /**
               * Sets the relative word (exclusive) index of the end of the
               * selection.
               */
              @NonNull
              public Builder setRelativeWordEndIndex(int relativeWordEndIndex) {
                  mRelativeWordEndIndex = relativeWordEndIndex;
                  return this;
              }
  
              /**
               * Sets the relative word index of the start of the smart
               * selection.
               */
              @NonNull
              public Builder setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex) {
                  mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex;
                  return this;
              }
  
              /**
               * Sets the relative word (exclusive) index of the end of the
               * smart selection.
               */
              @NonNull
              public Builder setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex) {
                  mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
                  return this;
              }
  
              @Override
              TextSelectionEvent.Builder self() {
                  return this;
              }
  
              /**
               * Builds and returns a {@link TextSelectionEvent}.
               */
              @NonNull
              public TextSelectionEvent build() {
                  return new TextSelectionEvent(this);
              }
          }
      }
  
      /**
       * This class represents events that are related to the smart linkify feature.
       * <p>
       * <pre>
       *     // User clicked on a link.
       *     new TextLinkifyEvent.Builder(TYPE_LINK_CLICKED)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setEventIndex(0)
       *         .build();
       *
       *     // Smart (contextual) actions presented to the user in response to a link click.
       *     new TextLinkifyEvent.Builder(TYPE_ACTIONS_SHOWN)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setActionIndices(range(textClassification.getActions().size()))
       *         .setEventIndex(1)
       *         .build();
       *
       *     // User chooses smart action at index 0.
       *     new TextLinkifyEvent.Builder(TYPE_SMART_ACTION)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(textClassification.getEntity(0))
       *         .setScore(textClassification.getConfidenceScore(entityType))
       *         .setActionIndices(0)
       *         .setEventIndex(2)
       *         .build();
       * </pre>
       */
      public static final class TextLinkifyEvent extends TextClassifierEvent implements Parcelable {
  
          @NonNull
          public static final Creator<TextLinkifyEvent> CREATOR =
                  new Creator<TextLinkifyEvent>() {
                      @Override
                      public TextLinkifyEvent createFromParcel(Parcel in) {
                          in.readInt(); // skip token, we already know this is a TextLinkifyEvent
                          return new TextLinkifyEvent(in);
                      }
  
                      @Override
                      public TextLinkifyEvent[] newArray(int size) {
                          return new TextLinkifyEvent[size];
                      }
                  };
  
          private TextLinkifyEvent(Parcel in) {
              super(in);
          }
  
          private TextLinkifyEvent(TextLinkifyEvent.Builder builder) {
              super(builder);
          }
  
          /**
           * Builder class for {@link TextLinkifyEvent}.
           */
          public static final class Builder
                  extends TextClassifierEvent.Builder<TextLinkifyEvent.Builder> {
              /**
               * Creates a builder for building {@link TextLinkifyEvent}s.
               *
               * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
               */
              public Builder(@Type int eventType) {
                  super(TextClassifierEvent.CATEGORY_LINKIFY, eventType);
              }
  
              @Override
              Builder self() {
                  return this;
              }
  
              /**
               * Builds and returns a {@link TextLinkifyEvent}.
               */
              @NonNull
              public TextLinkifyEvent build() {
                  return new TextLinkifyEvent(this);
              }
          }
      }
  
      /**
       * This class represents events that are related to the language detection feature.
       * <p>
       * <pre>
       *     // Translate action shown for foreign text.
       *     new LanguageDetectionEvent.Builder(TYPE_ACTIONS_SHOWN)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(language)
       *         .setScore(score)
       *         .setActionIndices(textClassification.getActions().indexOf(translateAction))
       *         .setEventIndex(0)
       *         .build();
       *
       *     // Translate action selected.
       *     new LanguageDetectionEvent.Builder(TYPE_SMART_ACTION)
       *         .setEventContext(classificationContext)
       *         .setResultId(textClassification.getId())
       *         .setEntityTypes(language)
       *         .setScore(score)
       *         .setActionIndices(textClassification.getActions().indexOf(translateAction))
       *         .setEventIndex(1)
       *         .build();
       */
      public static final class LanguageDetectionEvent extends TextClassifierEvent
              implements Parcelable {
  
          @NonNull
          public static final Creator<LanguageDetectionEvent> CREATOR =
                  new Creator<LanguageDetectionEvent>() {
                      @Override
                      public LanguageDetectionEvent createFromParcel(Parcel in) {
                          // skip token, we already know this is a LanguageDetectionEvent.
                          in.readInt();
                          return new LanguageDetectionEvent(in);
                      }
  
                      @Override
                      public LanguageDetectionEvent[] newArray(int size) {
                          return new LanguageDetectionEvent[size];
                      }
                  };
  
          private LanguageDetectionEvent(Parcel in) {
              super(in);
          }
  
          private LanguageDetectionEvent(LanguageDetectionEvent.Builder builder) {
              super(builder);
          }
  
          /**
           * Builder class for {@link LanguageDetectionEvent}.
           */
          public static final class Builder
                  extends TextClassifierEvent.Builder<LanguageDetectionEvent.Builder> {
  
              /**
               * Creates a builder for building {@link TextSelectionEvent}s.
               *
               * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
               */
              public Builder(@Type int eventType) {
                  super(TextClassifierEvent.CATEGORY_LANGUAGE_DETECTION, eventType);
              }
  
              @Override
              Builder self() {
                  return this;
              }
  
              /**
               * Builds and returns a {@link LanguageDetectionEvent}.
               */
              @NonNull
              public LanguageDetectionEvent build() {
                  return new LanguageDetectionEvent(this);
              }
          }
      }
  
      /**
       * This class represents events that are related to the conversation actions feature.
       * <p>
       * <pre>
       *     // Conversation (contextual) actions/replies generated.
       *     new ConversationActionsEvent.Builder(TYPE_ACTIONS_GENERATED)
       *         .setEventContext(classificationContext)
       *         .setResultId(conversationActions.getId())
       *         .setEntityTypes(getTypes(conversationActions))
       *         .setActionIndices(range(conversationActions.getActions().size()))
       *         .setEventIndex(0)
       *         .build();
       *
       *     // Conversation actions/replies presented to user.
       *     new ConversationActionsEvent.Builder(TYPE_ACTIONS_SHOWN)
       *         .setEventContext(classificationContext)
       *         .setResultId(conversationActions.getId())
       *         .setEntityTypes(getTypes(conversationActions))
       *         .setActionIndices(range(conversationActions.getActions().size()))
       *         .setEventIndex(1)
       *         .build();
       *
       *     // User clicked the "Reply" button to compose their custom reply.
       *     new ConversationActionsEvent.Builder(TYPE_MANUAL_REPLY)
       *         .setEventContext(classificationContext)
       *         .setResultId(conversationActions.getId())
       *         .setEventIndex(2)
       *         .build();
       *
       *     // User selected a smart (contextual) action/reply.
       *     new ConversationActionsEvent.Builder(TYPE_SMART_ACTION)
       *         .setEventContext(classificationContext)
       *         .setResultId(conversationActions.getId())
       *         .setEntityTypes(conversationActions.get(1).getType())
       *         .setScore(conversationAction.get(1).getConfidenceScore())
       *         .setActionIndices(1)
       *         .setEventIndex(2)
       *         .build();
       * </pre>
       */
      public static final class ConversationActionsEvent extends TextClassifierEvent
              implements Parcelable {
  
          @NonNull
          public static final Creator<ConversationActionsEvent> CREATOR =
                  new Creator<ConversationActionsEvent>() {
                      @Override
                      public ConversationActionsEvent createFromParcel(Parcel in) {
                          // skip token, we already know this is a ConversationActionsEvent.
                          in.readInt();
                          return new ConversationActionsEvent(in);
                      }
  
                      @Override
                      public ConversationActionsEvent[] newArray(int size) {
                          return new ConversationActionsEvent[size];
                      }
                  };
  
          private ConversationActionsEvent(Parcel in) {
              super(in);
          }
  
          private ConversationActionsEvent(ConversationActionsEvent.Builder builder) {
              super(builder);
          }
  
          /**
           * Builder class for {@link ConversationActionsEvent}.
           */
          public static final class Builder
                  extends TextClassifierEvent.Builder<ConversationActionsEvent.Builder> {
              /**
               * Creates a builder for building {@link TextSelectionEvent}s.
               *
               * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
               */
              public Builder(@Type int eventType) {
                  super(TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, eventType);
              }
  
              @Override
              Builder self() {
                  return this;
              }
  
              /**
               * Builds and returns a {@link ConversationActionsEvent}.
               */
              @NonNull
              public ConversationActionsEvent build() {
                  return new ConversationActionsEvent(this);
              }
          }
      }
  }
  