1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view.textclassifier;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.icu.util.ULocale;
23 import android.os.Bundle;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.util.Arrays;
32 import java.util.Objects;
33 
34 /**
35  * This class represents events that are sent by components to the {@link TextClassifier} to report
36  * something of note that relates to a feature powered by the TextClassifier. The TextClassifier may
37  * log these events or use them to improve future responses to queries.
38  * <p>
39  * Each category of events has its their own subclass. Events of each type have an associated
40  * set of related properties. You can find their specification in the subclasses.
41  */
42 public abstract class TextClassifierEvent implements Parcelable {
43 
44     private static final int PARCEL_TOKEN_TEXT_SELECTION_EVENT = 1;
45     private static final int PARCEL_TOKEN_TEXT_LINKIFY_EVENT = 2;
46     private static final int PARCEL_TOKEN_CONVERSATION_ACTION_EVENT = 3;
47     private static final int PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT = 4;
48 
49     /** @hide **/
50     @Retention(RetentionPolicy.SOURCE)
51     @IntDef({CATEGORY_SELECTION, CATEGORY_LINKIFY,
52             CATEGORY_CONVERSATION_ACTIONS, CATEGORY_LANGUAGE_DETECTION})
53     public @interface Category {
54         // For custom event categories, use range 1000+.
55     }
56 
57     /**
58      * Smart selection
59      *
60      * @see TextSelectionEvent
61      */
62     public static final int CATEGORY_SELECTION = 1;
63     /**
64      * Linkify
65      *
66      * @see TextLinkifyEvent
67      */
68     public static final int CATEGORY_LINKIFY = 2;
69     /**
70      *  Conversation actions
71      *
72      * @see ConversationActionsEvent
73      */
74     public static final int CATEGORY_CONVERSATION_ACTIONS = 3;
75     /**
76      * Language detection
77      *
78      * @see LanguageDetectionEvent
79      */
80     public static final int CATEGORY_LANGUAGE_DETECTION = 4;
81 
82     /** @hide */
83     @Retention(RetentionPolicy.SOURCE)
84     @IntDef({TYPE_SELECTION_STARTED, TYPE_SELECTION_MODIFIED,
85             TYPE_SMART_SELECTION_SINGLE, TYPE_SMART_SELECTION_MULTI, TYPE_AUTO_SELECTION,
86             TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION,
87             TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION,
88             TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL,
89             TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED, TYPE_LINKS_GENERATED})
90     public @interface Type {
91         // For custom event types, use range 1,000,000+.
92     }
93 
94     // All these event type constants are required to match with those defined in
95     // textclassifier_enums.proto.
96     /** User started a new selection. */
97     public static final int TYPE_SELECTION_STARTED = 1;
98     /** User modified an existing selection. */
99     public static final int TYPE_SELECTION_MODIFIED = 2;
100     /** Smart selection triggered for a single token (word). */
101     public static final int TYPE_SMART_SELECTION_SINGLE = 3;
102     /** Smart selection triggered spanning multiple tokens (words). */
103     public static final int TYPE_SMART_SELECTION_MULTI = 4;
104     /** Something else other than user or the default TextClassifier triggered a selection. */
105     public static final int TYPE_AUTO_SELECTION = 5;
106     /** Smart actions shown to the user. */
107     public static final int TYPE_ACTIONS_SHOWN = 6;
108     /** User clicked a link. */
109     public static final int TYPE_LINK_CLICKED = 7;
110     /** User typed over the selection. */
111     public static final int TYPE_OVERTYPE = 8;
112     /** User clicked on Copy action. */
113     public static final int TYPE_COPY_ACTION = 9;
114     /** User clicked on Paste action. */
115     public static final int TYPE_PASTE_ACTION = 10;
116     /** User clicked on Cut action. */
117     public static final int TYPE_CUT_ACTION = 11;
118     /** User clicked on Share action. */
119     public static final int TYPE_SHARE_ACTION = 12;
120     /** User clicked on a Smart action. */
121     public static final int TYPE_SMART_ACTION = 13;
122     /** User dragged+dropped the selection. */
123     public static final int TYPE_SELECTION_DRAG = 14;
124     /** Selection is destroyed. */
125     public static final int TYPE_SELECTION_DESTROYED = 15;
126     /** User clicked on a custom action. */
127     public static final int TYPE_OTHER_ACTION = 16;
128     /** User clicked on Select All action */
129     public static final int TYPE_SELECT_ALL = 17;
130     /** User reset the smart selection. */
131     public static final int TYPE_SELECTION_RESET = 18;
132     /** User composed a reply. */
133     public static final int TYPE_MANUAL_REPLY = 19;
134     /** TextClassifier generated some actions */
135     public static final int TYPE_ACTIONS_GENERATED = 20;
136     /** Some text links were generated.*/
137     public static final int TYPE_LINKS_GENERATED = 21;
138 
139     @Category
140     private final int mEventCategory;
141     @Type
142     private final int mEventType;
143     @Nullable
144     private final String[] mEntityTypes;
145     @Nullable
146     private TextClassificationContext mEventContext;
147     @Nullable
148     private final String mResultId;
149     private final int mEventIndex;
150     private final float[] mScores;
151     @Nullable
152     private final String mModelName;
153     private final int[] mActionIndices;
154     @Nullable
155     private final ULocale mLocale;
156     private final Bundle mExtras;
157 
158     /**
159      * Session id holder to help with converting this event to the legacy SelectionEvent.
160      * @hide
161      */
162     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
163     @Nullable
164     public TextClassificationSessionId mHiddenTempSessionId;
165 
TextClassifierEvent(Builder builder)166     private TextClassifierEvent(Builder builder) {
167         mEventCategory = builder.mEventCategory;
168         mEventType = builder.mEventType;
169         mEntityTypes = builder.mEntityTypes;
170         mEventContext = builder.mEventContext;
171         mResultId = builder.mResultId;
172         mEventIndex = builder.mEventIndex;
173         mScores = builder.mScores;
174         mModelName = builder.mModelName;
175         mActionIndices = builder.mActionIndices;
176         mLocale = builder.mLocale;
177         mExtras = builder.mExtras == null ? Bundle.EMPTY : builder.mExtras;
178     }
179 
TextClassifierEvent(Parcel in)180     private TextClassifierEvent(Parcel in) {
181         mEventCategory = in.readInt();
182         mEventType = in.readInt();
183         mEntityTypes = in.readStringArray();
184         mEventContext = in.readParcelable(null);
185         mResultId = in.readString();
186         mEventIndex = in.readInt();
187         int scoresLength = in.readInt();
188         mScores = new float[scoresLength];
189         in.readFloatArray(mScores);
190         mModelName = in.readString();
191         mActionIndices = in.createIntArray();
192         final String languageTag = in.readString();
193         mLocale = languageTag == null ? null : ULocale.forLanguageTag(languageTag);
194         mExtras = in.readBundle();
195     }
196 
197     @Override
describeContents()198     public int describeContents() {
199         return 0;
200     }
201 
202     @NonNull
203     public static final Creator<TextClassifierEvent> CREATOR = new Creator<TextClassifierEvent>() {
204         @Override
205         public TextClassifierEvent createFromParcel(Parcel in) {
206             int token = in.readInt();
207             if (token == PARCEL_TOKEN_TEXT_SELECTION_EVENT) {
208                 return new TextSelectionEvent(in);
209             }
210             if (token == PARCEL_TOKEN_TEXT_LINKIFY_EVENT) {
211                 return new TextLinkifyEvent(in);
212             }
213             if (token == PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT) {
214                 return new LanguageDetectionEvent(in);
215             }
216             if (token == PARCEL_TOKEN_CONVERSATION_ACTION_EVENT) {
217                 return new ConversationActionsEvent(in);
218             }
219             throw new IllegalStateException("Unexpected input event type token in parcel.");
220         }
221 
222         @Override
223         public TextClassifierEvent[] newArray(int size) {
224             return new TextClassifierEvent[size];
225         }
226     };
227 
228     @Override
writeToParcel(Parcel dest, int flags)229     public void writeToParcel(Parcel dest, int flags) {
230         dest.writeInt(getParcelToken());
231         dest.writeInt(mEventCategory);
232         dest.writeInt(mEventType);
233         dest.writeStringArray(mEntityTypes);
234         dest.writeParcelable(mEventContext, flags);
235         dest.writeString(mResultId);
236         dest.writeInt(mEventIndex);
237         dest.writeInt(mScores.length);
238         dest.writeFloatArray(mScores);
239         dest.writeString(mModelName);
240         dest.writeIntArray(mActionIndices);
241         dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
242         dest.writeBundle(mExtras);
243     }
244 
getParcelToken()245     private int getParcelToken() {
246         if (this instanceof TextSelectionEvent) {
247             return PARCEL_TOKEN_TEXT_SELECTION_EVENT;
248         }
249         if (this instanceof TextLinkifyEvent) {
250             return PARCEL_TOKEN_TEXT_LINKIFY_EVENT;
251         }
252         if (this instanceof LanguageDetectionEvent) {
253             return PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT;
254         }
255         if (this instanceof ConversationActionsEvent) {
256             return PARCEL_TOKEN_CONVERSATION_ACTION_EVENT;
257         }
258         throw new IllegalArgumentException("Unexpected type: " + this.getClass().getSimpleName());
259     }
260 
261     /**
262      * Returns the event category. e.g. {@link #CATEGORY_SELECTION}.
263      */
264     @Category
getEventCategory()265     public int getEventCategory() {
266         return mEventCategory;
267     }
268 
269     /**
270      * Returns the event type. e.g. {@link #TYPE_SELECTION_STARTED}.
271      */
272     @Type
getEventType()273     public int getEventType() {
274         return mEventType;
275     }
276 
277     /**
278      * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
279      *
280      * @see Builder#setEntityTypes(String...) for supported types.
281      */
282     @NonNull
getEntityTypes()283     public String[] getEntityTypes() {
284         return mEntityTypes;
285     }
286 
287     /**
288      * Returns the event context.
289      */
290     @Nullable
getEventContext()291     public TextClassificationContext getEventContext() {
292         return mEventContext;
293     }
294 
295     /**
296      * Sets the event context.
297      * <p>
298      * Package-private for SystemTextClassifier's use.
299      */
setEventContext(@ullable TextClassificationContext eventContext)300     void setEventContext(@Nullable TextClassificationContext eventContext) {
301         mEventContext = eventContext;
302     }
303 
304     /**
305      * Returns the id of the text classifier result related to this event.
306      */
307     @Nullable
getResultId()308     public String getResultId() {
309         return mResultId;
310     }
311 
312     /**
313      * Returns the index of this event in the series of event it belongs to.
314      */
getEventIndex()315     public int getEventIndex() {
316         return mEventIndex;
317     }
318 
319     /**
320      * Returns the scores of the suggestions.
321      */
322     @NonNull
getScores()323     public float[] getScores() {
324         return mScores;
325     }
326 
327     /**
328      * Returns the model name.
329      */
330     @Nullable
getModelName()331     public String getModelName() {
332         return mModelName;
333     }
334 
335     /**
336      * Returns the indices of the actions relating to this event.
337      * Actions are usually returned by the text classifier in priority order with the most
338      * preferred action at index 0. This list gives an indication of the position of the actions
339      * that are being reported.
340      *
341      * @see Builder#setActionIndices(int...)
342      */
343     @NonNull
getActionIndices()344     public int[] getActionIndices() {
345         return mActionIndices;
346     }
347 
348     /**
349      * Returns the detected locale.
350      */
351     @Nullable
getLocale()352     public ULocale getLocale() {
353         return mLocale;
354     }
355 
356     /**
357      * Returns a bundle containing non-structured extra information about this event.
358      *
359      * <p><b>NOTE: </b>Do not modify this bundle.
360      */
361     @NonNull
getExtras()362     public Bundle getExtras() {
363         return mExtras;
364     }
365 
366     @Override
toString()367     public String toString() {
368         StringBuilder out = new StringBuilder(128);
369         out.append(this.getClass().getSimpleName());
370         out.append("{");
371         out.append("mEventCategory=").append(mEventCategory);
372         out.append(", mEventType=").append(mEventType);
373         out.append(", mEntityTypes=").append(Arrays.toString(mEntityTypes));
374         out.append(", mEventContext=").append(mEventContext);
375         out.append(", mResultId=").append(mResultId);
376         out.append(", mEventIndex=").append(mEventIndex);
377         out.append(", mExtras=").append(mExtras);
378         out.append(", mScores=").append(Arrays.toString(mScores));
379         out.append(", mModelName=").append(mModelName);
380         out.append(", mActionIndices=").append(Arrays.toString(mActionIndices));
381         toString(out);
382         out.append("}");
383         return out.toString();
384     }
385 
386     /**
387      * Overrides this to append extra fields to the output of {@link #toString()}.
388      * <p>
389      * Extra fields should be  formatted like this: ", {field_name}={field_value}".
390      */
toString(StringBuilder out)391     void toString(StringBuilder out) {}
392 
393     /**
394      * Returns a {@link SelectionEvent} equivalent of this event; or {@code null} if it can not be
395      * converted to a {@link SelectionEvent}.
396      * @hide
397      */
398     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
399     @Nullable
toSelectionEvent()400     public final SelectionEvent toSelectionEvent() {
401         final int invocationMethod;
402         switch (getEventCategory()) {
403             case TextClassifierEvent.CATEGORY_SELECTION:
404                 invocationMethod = SelectionEvent.INVOCATION_MANUAL;
405                 break;
406             case TextClassifierEvent.CATEGORY_LINKIFY:
407                 invocationMethod = SelectionEvent.INVOCATION_LINK;
408                 break;
409             default:
410                 // Cannot be converted to a SelectionEvent.
411                 return null;
412         }
413 
414         final String entityType = getEntityTypes().length > 0
415                 ? getEntityTypes()[0] : TextClassifier.TYPE_UNKNOWN;
416         final SelectionEvent out = new SelectionEvent(
417                 /* absoluteStart= */ 0,
418                 /* absoluteEnd= */ 0,
419                 /* eventType= */0,
420                 entityType,
421                 SelectionEvent.INVOCATION_UNKNOWN,
422                 SelectionEvent.NO_SIGNATURE);
423         out.setInvocationMethod(invocationMethod);
424 
425         final TextClassificationContext eventContext = getEventContext();
426         if (eventContext != null) {
427             out.setTextClassificationSessionContext(getEventContext());
428         }
429         out.setSessionId(mHiddenTempSessionId);
430         final String resultId = getResultId();
431         out.setResultId(resultId == null ? SelectionEvent.NO_SIGNATURE : resultId);
432         out.setEventIndex(getEventIndex());
433 
434 
435         final int eventType;
436         switch (getEventType()) {
437             case TextClassifierEvent.TYPE_SELECTION_STARTED:
438                 eventType = SelectionEvent.EVENT_SELECTION_STARTED;
439                 break;
440             case TextClassifierEvent.TYPE_SELECTION_MODIFIED:
441                 eventType = SelectionEvent.EVENT_SELECTION_MODIFIED;
442                 break;
443             case TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE:
444                 eventType = SelectionEvent.EVENT_SMART_SELECTION_SINGLE;
445                 break;
446             case TextClassifierEvent.TYPE_SMART_SELECTION_MULTI:
447                 eventType = SelectionEvent.EVENT_SMART_SELECTION_MULTI;
448                 break;
449             case TextClassifierEvent.TYPE_AUTO_SELECTION:
450                 eventType = SelectionEvent.EVENT_AUTO_SELECTION;
451                 break;
452             case TextClassifierEvent.TYPE_OVERTYPE:
453                 eventType = SelectionEvent.ACTION_OVERTYPE;
454                 break;
455             case TextClassifierEvent.TYPE_COPY_ACTION:
456                 eventType = SelectionEvent.ACTION_COPY;
457                 break;
458             case TextClassifierEvent.TYPE_PASTE_ACTION:
459                 eventType = SelectionEvent.ACTION_PASTE;
460                 break;
461             case TextClassifierEvent.TYPE_CUT_ACTION:
462                 eventType = SelectionEvent.ACTION_CUT;
463                 break;
464             case TextClassifierEvent.TYPE_SHARE_ACTION:
465                 eventType = SelectionEvent.ACTION_SHARE;
466                 break;
467             case TextClassifierEvent.TYPE_SMART_ACTION:
468                 eventType = SelectionEvent.ACTION_SMART_SHARE;
469                 break;
470             case TextClassifierEvent.TYPE_SELECTION_DRAG:
471                 eventType = SelectionEvent.ACTION_DRAG;
472                 break;
473             case TextClassifierEvent.TYPE_SELECTION_DESTROYED:
474                 eventType = SelectionEvent.ACTION_ABANDON;
475                 break;
476             case TextClassifierEvent.TYPE_OTHER_ACTION:
477                 eventType = SelectionEvent.ACTION_OTHER;
478                 break;
479             case TextClassifierEvent.TYPE_SELECT_ALL:
480                 eventType = SelectionEvent.ACTION_SELECT_ALL;
481                 break;
482             case TextClassifierEvent.TYPE_SELECTION_RESET:
483                 eventType = SelectionEvent.ACTION_RESET;
484                 break;
485             default:
486                 eventType = 0;
487                 break;
488         }
489         out.setEventType(eventType);
490 
491         if (this instanceof TextClassifierEvent.TextSelectionEvent) {
492             final TextClassifierEvent.TextSelectionEvent selEvent =
493                     (TextClassifierEvent.TextSelectionEvent) this;
494             // TODO: Ideally, we should have these fields in events of type
495             // TextClassifierEvent.TextLinkifyEvent events too but we're now past the API deadline
496             // and will have to do with these fields being set only in TextSelectionEvent events.
497             // Fix this at the next API bump.
498             out.setStart(selEvent.getRelativeWordStartIndex());
499             out.setEnd(selEvent.getRelativeWordEndIndex());
500             out.setSmartStart(selEvent.getRelativeSuggestedWordStartIndex());
501             out.setSmartEnd(selEvent.getRelativeSuggestedWordEndIndex());
502         }
503 
504         return out;
505     }
506 
507     /**
508      * Builder to build a text classifier event.
509      *
510      * @param <T> The subclass to be built.
511      */
512     public abstract static class Builder<T extends Builder<T>> {
513 
514         private final int mEventCategory;
515         private final int mEventType;
516         private String[] mEntityTypes = new String[0];
517         @Nullable
518         private TextClassificationContext mEventContext;
519         @Nullable
520         private String mResultId;
521         private int mEventIndex;
522         private float[] mScores = new float[0];
523         @Nullable
524         private String mModelName;
525         private int[] mActionIndices = new int[0];
526         @Nullable
527         private ULocale mLocale;
528         @Nullable
529         private Bundle mExtras;
530 
531         /**
532          * Creates a builder for building {@link TextClassifierEvent}s.
533          *
534          * @param eventCategory The event category. e.g. {@link #CATEGORY_SELECTION}
535          * @param eventType     The event type. e.g. {@link #TYPE_SELECTION_STARTED}
536          */
Builder(@ategory int eventCategory, @Type int eventType)537         private Builder(@Category int eventCategory, @Type int eventType) {
538             mEventCategory = eventCategory;
539             mEventType = eventType;
540         }
541 
542         /**
543          * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
544          * <p>
545          * Supported types:
546          * <p>See {@link TextClassifier.EntityType}
547          * <p>See {@link ConversationAction.ActionType}
548          * <p>See {@link ULocale#toLanguageTag()}
549          */
550         @NonNull
setEntityTypes(@onNull String... entityTypes)551         public T setEntityTypes(@NonNull String... entityTypes) {
552             Objects.requireNonNull(entityTypes);
553             mEntityTypes = new String[entityTypes.length];
554             System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
555             return self();
556         }
557 
558         /**
559          * Sets the event context.
560          */
561         @NonNull
setEventContext(@ullable TextClassificationContext eventContext)562         public T setEventContext(@Nullable TextClassificationContext eventContext) {
563             mEventContext = eventContext;
564             return self();
565         }
566 
567         /**
568          * Sets the id of the text classifier result related to this event.
569          */
570         @NonNull
setResultId(@ullable String resultId)571         public T setResultId(@Nullable String resultId) {
572             mResultId = resultId;
573             return self();
574         }
575 
576         /**
577          * Sets the index of this event in the series of events it belongs to.
578          */
579         @NonNull
setEventIndex(int eventIndex)580         public T setEventIndex(int eventIndex) {
581             mEventIndex = eventIndex;
582             return self();
583         }
584 
585         /**
586          * Sets the scores of the suggestions.
587          */
588         @NonNull
setScores(@onNull float... scores)589         public T setScores(@NonNull float... scores) {
590             Objects.requireNonNull(scores);
591             mScores = new float[scores.length];
592             System.arraycopy(scores, 0, mScores, 0, scores.length);
593             return self();
594         }
595 
596         /**
597          * Sets the model name string.
598          */
599         @NonNull
setModelName(@ullable String modelVersion)600         public T setModelName(@Nullable String modelVersion) {
601             mModelName = modelVersion;
602             return self();
603         }
604 
605         /**
606          * Sets the indices of the actions involved in this event. Actions are usually returned by
607          * the text classifier in priority order with the most preferred action at index 0.
608          * These indices give an indication of the position of the actions that are being reported.
609          * <p>
610          * E.g.
611          * <pre>
612          *   // 3 smart actions are shown at index 0, 1, 2 respectively in response to a link click.
613          *   new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_ACTIONS_SHOWN)
614          *       .setEventIndex(0, 1, 2)
615          *       ...
616          *       .build();
617          *
618          *   ...
619          *
620          *   // Smart action at index 1 is activated.
621          *   new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_SMART_ACTION)
622          *       .setEventIndex(1)
623          *       ...
624          *       .build();
625          * </pre>
626          *
627          * @see TextClassification#getActions()
628          */
629         @NonNull
setActionIndices(@onNull int... actionIndices)630         public T setActionIndices(@NonNull int... actionIndices) {
631             mActionIndices = new int[actionIndices.length];
632             System.arraycopy(actionIndices, 0, mActionIndices, 0, actionIndices.length);
633             return self();
634         }
635 
636         /**
637          * Sets the detected locale.
638          */
639         @NonNull
setLocale(@ullable ULocale locale)640         public T setLocale(@Nullable ULocale locale) {
641             mLocale = locale;
642             return self();
643         }
644 
645         /**
646          * Sets a bundle containing non-structured extra information about the event.
647          *
648          * <p><b>NOTE: </b>Prefer to set only immutable values on the bundle otherwise, avoid
649          * updating the internals of this bundle as it may have unexpected consequences on the
650          * clients of the built event object. For similar reasons, avoid depending on mutable
651          * objects in this bundle.
652          */
653         @NonNull
setExtras(@onNull Bundle extras)654         public T setExtras(@NonNull Bundle extras) {
655             mExtras = Objects.requireNonNull(extras);
656             return self();
657         }
658 
self()659         abstract T self();
660     }
661 
662     /**
663      * This class represents events that are related to the smart text selection feature.
664      * <p>
665      * <pre>
666      *     // User started a selection. e.g. "York" in text "New York City, NY".
667      *     new TextSelectionEvent.Builder(TYPE_SELECTION_STARTED)
668      *         .setEventContext(classificationContext)
669      *         .setEventIndex(0)
670      *         .build();
671      *
672      *     // System smart-selects a recognized entity. e.g. "New York City".
673      *     new TextSelectionEvent.Builder(TYPE_SMART_SELECTION_MULTI)
674      *         .setEventContext(classificationContext)
675      *         .setResultId(textSelection.getId())
676      *         .setRelativeWordStartIndex(-1) // Goes back one word to "New" from "York".
677      *         .setRelativeWordEndIndex(2)    // Goes forward 2 words from "York" to start of ",".
678      *         .setEntityTypes(textClassification.getEntity(0))
679      *         .setScore(textClassification.getConfidenceScore(entityType))
680      *         .setEventIndex(1)
681      *         .build();
682      *
683      *     // User resets the selection to the original selection. i.e. "York".
684      *     new TextSelectionEvent.Builder(TYPE_SELECTION_RESET)
685      *         .setEventContext(classificationContext)
686      *         .setResultId(textSelection.getId())
687      *         .setRelativeSuggestedWordStartIndex(-1) // Repeated from above.
688      *         .setRelativeSuggestedWordEndIndex(2)    // Repeated from above.
689      *         .setRelativeWordStartIndex(0)           // Original selection is always at (0, 1].
690      *         .setRelativeWordEndIndex(1)
691      *         .setEntityTypes(textClassification.getEntity(0))
692      *         .setScore(textClassification.getConfidenceScore(entityType))
693      *         .setEventIndex(2)
694      *         .build();
695      *
696      *     // User modified the selection. e.g. "New".
697      *     new TextSelectionEvent.Builder(TYPE_SELECTION_MODIFIED)
698      *         .setEventContext(classificationContext)
699      *         .setResultId(textSelection.getId())
700      *         .setRelativeSuggestedWordStartIndex(-1) // Repeated from above.
701      *         .setRelativeSuggestedWordEndIndex(2)    // Repeated from above.
702      *         .setRelativeWordStartIndex(-1)          // Goes backward one word from "York" to
703      *         "New".
704      *         .setRelativeWordEndIndex(0)             // Goes backward one word to exclude "York".
705      *         .setEntityTypes(textClassification.getEntity(0))
706      *         .setScore(textClassification.getConfidenceScore(entityType))
707      *         .setEventIndex(3)
708      *         .build();
709      *
710      *     // Smart (contextual) actions (at indices, 0, 1, 2) presented to the user.
711      *     // e.g. "Map", "Ride share", "Explore".
712      *     new TextSelectionEvent.Builder(TYPE_ACTIONS_SHOWN)
713      *         .setEventContext(classificationContext)
714      *         .setResultId(textClassification.getId())
715      *         .setEntityTypes(textClassification.getEntity(0))
716      *         .setScore(textClassification.getConfidenceScore(entityType))
717      *         .setActionIndices(0, 1, 2)
718      *         .setEventIndex(4)
719      *         .build();
720      *
721      *     // User chooses the "Copy" action.
722      *     new TextSelectionEvent.Builder(TYPE_COPY_ACTION)
723      *         .setEventContext(classificationContext)
724      *         .setResultId(textClassification.getId())
725      *         .setEntityTypes(textClassification.getEntity(0))
726      *         .setScore(textClassification.getConfidenceScore(entityType))
727      *         .setEventIndex(5)
728      *         .build();
729      *
730      *     // User chooses smart action at index 1. i.e. "Ride share".
731      *     new TextSelectionEvent.Builder(TYPE_SMART_ACTION)
732      *         .setEventContext(classificationContext)
733      *         .setResultId(textClassification.getId())
734      *         .setEntityTypes(textClassification.getEntity(0))
735      *         .setScore(textClassification.getConfidenceScore(entityType))
736      *         .setActionIndices(1)
737      *         .setEventIndex(5)
738      *         .build();
739      *
740      *     // Selection dismissed.
741      *     new TextSelectionEvent.Builder(TYPE_SELECTION_DESTROYED)
742      *         .setEventContext(classificationContext)
743      *         .setResultId(textClassification.getId())
744      *         .setEntityTypes(textClassification.getEntity(0))
745      *         .setScore(textClassification.getConfidenceScore(entityType))
746      *         .setEventIndex(6)
747      *         .build();
748      * </pre>
749      * <p>
750      */
751     public static final class TextSelectionEvent extends TextClassifierEvent implements Parcelable {
752 
753         @NonNull
754         public static final Creator<TextSelectionEvent> CREATOR =
755                 new Creator<TextSelectionEvent>() {
756                     @Override
757                     public TextSelectionEvent createFromParcel(Parcel in) {
758                         in.readInt(); // skip token, we already know this is a TextSelectionEvent
759                         return new TextSelectionEvent(in);
760                     }
761 
762                     @Override
763                     public TextSelectionEvent[] newArray(int size) {
764                         return new TextSelectionEvent[size];
765                     }
766                 };
767 
768         final int mRelativeWordStartIndex;
769         final int mRelativeWordEndIndex;
770         final int mRelativeSuggestedWordStartIndex;
771         final int mRelativeSuggestedWordEndIndex;
772 
TextSelectionEvent(TextSelectionEvent.Builder builder)773         private TextSelectionEvent(TextSelectionEvent.Builder builder) {
774             super(builder);
775             mRelativeWordStartIndex = builder.mRelativeWordStartIndex;
776             mRelativeWordEndIndex = builder.mRelativeWordEndIndex;
777             mRelativeSuggestedWordStartIndex = builder.mRelativeSuggestedWordStartIndex;
778             mRelativeSuggestedWordEndIndex = builder.mRelativeSuggestedWordEndIndex;
779         }
780 
TextSelectionEvent(Parcel in)781         private TextSelectionEvent(Parcel in) {
782             super(in);
783             mRelativeWordStartIndex = in.readInt();
784             mRelativeWordEndIndex = in.readInt();
785             mRelativeSuggestedWordStartIndex = in.readInt();
786             mRelativeSuggestedWordEndIndex = in.readInt();
787         }
788 
789         @Override
writeToParcel(Parcel dest, int flags)790         public void writeToParcel(Parcel dest, int flags) {
791             super.writeToParcel(dest, flags);
792             dest.writeInt(mRelativeWordStartIndex);
793             dest.writeInt(mRelativeWordEndIndex);
794             dest.writeInt(mRelativeSuggestedWordStartIndex);
795             dest.writeInt(mRelativeSuggestedWordEndIndex);
796         }
797 
798         /**
799          * Returns the relative word index of the start of the selection.
800          */
getRelativeWordStartIndex()801         public int getRelativeWordStartIndex() {
802             return mRelativeWordStartIndex;
803         }
804 
805         /**
806          * Returns the relative word (exclusive) index of the end of the selection.
807          */
getRelativeWordEndIndex()808         public int getRelativeWordEndIndex() {
809             return mRelativeWordEndIndex;
810         }
811 
812         /**
813          * Returns the relative word index of the start of the smart selection.
814          */
getRelativeSuggestedWordStartIndex()815         public int getRelativeSuggestedWordStartIndex() {
816             return mRelativeSuggestedWordStartIndex;
817         }
818 
819         /**
820          * Returns the relative word (exclusive) index of the end of the
821          * smart selection.
822          */
getRelativeSuggestedWordEndIndex()823         public int getRelativeSuggestedWordEndIndex() {
824             return mRelativeSuggestedWordEndIndex;
825         }
826 
827         @Override
toString(StringBuilder out)828         void toString(StringBuilder out) {
829             out.append(", getRelativeWordStartIndex=").append(mRelativeWordStartIndex);
830             out.append(", getRelativeWordEndIndex=").append(mRelativeWordEndIndex);
831             out.append(", getRelativeSuggestedWordStartIndex=")
832                     .append(mRelativeSuggestedWordStartIndex);
833             out.append(", getRelativeSuggestedWordEndIndex=")
834                     .append(mRelativeSuggestedWordEndIndex);
835         }
836 
837         /**
838          * Builder class for {@link TextSelectionEvent}.
839          */
840         public static final class Builder extends
841                 TextClassifierEvent.Builder<TextSelectionEvent.Builder> {
842             int mRelativeWordStartIndex;
843             int mRelativeWordEndIndex;
844             int mRelativeSuggestedWordStartIndex;
845             int mRelativeSuggestedWordEndIndex;
846 
847             /**
848              * Creates a builder for building {@link TextSelectionEvent}s.
849              *
850              * @param eventType     The event type. e.g. {@link #TYPE_SELECTION_STARTED}
851              */
Builder(@ype int eventType)852             public Builder(@Type int eventType) {
853                 super(CATEGORY_SELECTION, eventType);
854             }
855 
856             /**
857              * Sets the relative word index of the start of the selection.
858              */
859             @NonNull
setRelativeWordStartIndex(int relativeWordStartIndex)860             public Builder setRelativeWordStartIndex(int relativeWordStartIndex) {
861                 mRelativeWordStartIndex = relativeWordStartIndex;
862                 return this;
863             }
864 
865             /**
866              * Sets the relative word (exclusive) index of the end of the
867              * selection.
868              */
869             @NonNull
setRelativeWordEndIndex(int relativeWordEndIndex)870             public Builder setRelativeWordEndIndex(int relativeWordEndIndex) {
871                 mRelativeWordEndIndex = relativeWordEndIndex;
872                 return this;
873             }
874 
875             /**
876              * Sets the relative word index of the start of the smart
877              * selection.
878              */
879             @NonNull
setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex)880             public Builder setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex) {
881                 mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex;
882                 return this;
883             }
884 
885             /**
886              * Sets the relative word (exclusive) index of the end of the
887              * smart selection.
888              */
889             @NonNull
setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex)890             public Builder setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex) {
891                 mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
892                 return this;
893             }
894 
895             @Override
self()896             TextSelectionEvent.Builder self() {
897                 return this;
898             }
899 
900             /**
901              * Builds and returns a {@link TextSelectionEvent}.
902              */
903             @NonNull
build()904             public TextSelectionEvent build() {
905                 return new TextSelectionEvent(this);
906             }
907         }
908     }
909 
910     /**
911      * This class represents events that are related to the smart linkify feature.
912      * <p>
913      * <pre>
914      *     // User clicked on a link.
915      *     new TextLinkifyEvent.Builder(TYPE_LINK_CLICKED)
916      *         .setEventContext(classificationContext)
917      *         .setResultId(textClassification.getId())
918      *         .setEntityTypes(textClassification.getEntity(0))
919      *         .setScore(textClassification.getConfidenceScore(entityType))
920      *         .setEventIndex(0)
921      *         .build();
922      *
923      *     // Smart (contextual) actions presented to the user in response to a link click.
924      *     new TextLinkifyEvent.Builder(TYPE_ACTIONS_SHOWN)
925      *         .setEventContext(classificationContext)
926      *         .setResultId(textClassification.getId())
927      *         .setEntityTypes(textClassification.getEntity(0))
928      *         .setScore(textClassification.getConfidenceScore(entityType))
929      *         .setActionIndices(range(textClassification.getActions().size()))
930      *         .setEventIndex(1)
931      *         .build();
932      *
933      *     // User chooses smart action at index 0.
934      *     new TextLinkifyEvent.Builder(TYPE_SMART_ACTION)
935      *         .setEventContext(classificationContext)
936      *         .setResultId(textClassification.getId())
937      *         .setEntityTypes(textClassification.getEntity(0))
938      *         .setScore(textClassification.getConfidenceScore(entityType))
939      *         .setActionIndices(0)
940      *         .setEventIndex(2)
941      *         .build();
942      * </pre>
943      */
944     public static final class TextLinkifyEvent extends TextClassifierEvent implements Parcelable {
945 
946         @NonNull
947         public static final Creator<TextLinkifyEvent> CREATOR =
948                 new Creator<TextLinkifyEvent>() {
949                     @Override
950                     public TextLinkifyEvent createFromParcel(Parcel in) {
951                         in.readInt(); // skip token, we already know this is a TextLinkifyEvent
952                         return new TextLinkifyEvent(in);
953                     }
954 
955                     @Override
956                     public TextLinkifyEvent[] newArray(int size) {
957                         return new TextLinkifyEvent[size];
958                     }
959                 };
960 
TextLinkifyEvent(Parcel in)961         private TextLinkifyEvent(Parcel in) {
962             super(in);
963         }
964 
TextLinkifyEvent(TextLinkifyEvent.Builder builder)965         private TextLinkifyEvent(TextLinkifyEvent.Builder builder) {
966             super(builder);
967         }
968 
969         /**
970          * Builder class for {@link TextLinkifyEvent}.
971          */
972         public static final class Builder
973                 extends TextClassifierEvent.Builder<TextLinkifyEvent.Builder> {
974             /**
975              * Creates a builder for building {@link TextLinkifyEvent}s.
976              *
977              * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
978              */
Builder(@ype int eventType)979             public Builder(@Type int eventType) {
980                 super(TextClassifierEvent.CATEGORY_LINKIFY, eventType);
981             }
982 
983             @Override
self()984             Builder self() {
985                 return this;
986             }
987 
988             /**
989              * Builds and returns a {@link TextLinkifyEvent}.
990              */
991             @NonNull
build()992             public TextLinkifyEvent build() {
993                 return new TextLinkifyEvent(this);
994             }
995         }
996     }
997 
998     /**
999      * This class represents events that are related to the language detection feature.
1000      * <p>
1001      * <pre>
1002      *     // Translate action shown for foreign text.
1003      *     new LanguageDetectionEvent.Builder(TYPE_ACTIONS_SHOWN)
1004      *         .setEventContext(classificationContext)
1005      *         .setResultId(textClassification.getId())
1006      *         .setEntityTypes(language)
1007      *         .setScore(score)
1008      *         .setActionIndices(textClassification.getActions().indexOf(translateAction))
1009      *         .setEventIndex(0)
1010      *         .build();
1011      *
1012      *     // Translate action selected.
1013      *     new LanguageDetectionEvent.Builder(TYPE_SMART_ACTION)
1014      *         .setEventContext(classificationContext)
1015      *         .setResultId(textClassification.getId())
1016      *         .setEntityTypes(language)
1017      *         .setScore(score)
1018      *         .setActionIndices(textClassification.getActions().indexOf(translateAction))
1019      *         .setEventIndex(1)
1020      *         .build();
1021      */
1022     public static final class LanguageDetectionEvent extends TextClassifierEvent
1023             implements Parcelable {
1024 
1025         @NonNull
1026         public static final Creator<LanguageDetectionEvent> CREATOR =
1027                 new Creator<LanguageDetectionEvent>() {
1028                     @Override
1029                     public LanguageDetectionEvent createFromParcel(Parcel in) {
1030                         // skip token, we already know this is a LanguageDetectionEvent.
1031                         in.readInt();
1032                         return new LanguageDetectionEvent(in);
1033                     }
1034 
1035                     @Override
1036                     public LanguageDetectionEvent[] newArray(int size) {
1037                         return new LanguageDetectionEvent[size];
1038                     }
1039                 };
1040 
LanguageDetectionEvent(Parcel in)1041         private LanguageDetectionEvent(Parcel in) {
1042             super(in);
1043         }
1044 
LanguageDetectionEvent(LanguageDetectionEvent.Builder builder)1045         private LanguageDetectionEvent(LanguageDetectionEvent.Builder builder) {
1046             super(builder);
1047         }
1048 
1049         /**
1050          * Builder class for {@link LanguageDetectionEvent}.
1051          */
1052         public static final class Builder
1053                 extends TextClassifierEvent.Builder<LanguageDetectionEvent.Builder> {
1054 
1055             /**
1056              * Creates a builder for building {@link TextSelectionEvent}s.
1057              *
1058              * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
1059              */
Builder(@ype int eventType)1060             public Builder(@Type int eventType) {
1061                 super(TextClassifierEvent.CATEGORY_LANGUAGE_DETECTION, eventType);
1062             }
1063 
1064             @Override
self()1065             Builder self() {
1066                 return this;
1067             }
1068 
1069             /**
1070              * Builds and returns a {@link LanguageDetectionEvent}.
1071              */
1072             @NonNull
build()1073             public LanguageDetectionEvent build() {
1074                 return new LanguageDetectionEvent(this);
1075             }
1076         }
1077     }
1078 
1079     /**
1080      * This class represents events that are related to the conversation actions feature.
1081      * <p>
1082      * <pre>
1083      *     // Conversation (contextual) actions/replies generated.
1084      *     new ConversationActionsEvent.Builder(TYPE_ACTIONS_GENERATED)
1085      *         .setEventContext(classificationContext)
1086      *         .setResultId(conversationActions.getId())
1087      *         .setEntityTypes(getTypes(conversationActions))
1088      *         .setActionIndices(range(conversationActions.getActions().size()))
1089      *         .setEventIndex(0)
1090      *         .build();
1091      *
1092      *     // Conversation actions/replies presented to user.
1093      *     new ConversationActionsEvent.Builder(TYPE_ACTIONS_SHOWN)
1094      *         .setEventContext(classificationContext)
1095      *         .setResultId(conversationActions.getId())
1096      *         .setEntityTypes(getTypes(conversationActions))
1097      *         .setActionIndices(range(conversationActions.getActions().size()))
1098      *         .setEventIndex(1)
1099      *         .build();
1100      *
1101      *     // User clicked the "Reply" button to compose their custom reply.
1102      *     new ConversationActionsEvent.Builder(TYPE_MANUAL_REPLY)
1103      *         .setEventContext(classificationContext)
1104      *         .setResultId(conversationActions.getId())
1105      *         .setEventIndex(2)
1106      *         .build();
1107      *
1108      *     // User selected a smart (contextual) action/reply.
1109      *     new ConversationActionsEvent.Builder(TYPE_SMART_ACTION)
1110      *         .setEventContext(classificationContext)
1111      *         .setResultId(conversationActions.getId())
1112      *         .setEntityTypes(conversationActions.get(1).getType())
1113      *         .setScore(conversationAction.get(1).getConfidenceScore())
1114      *         .setActionIndices(1)
1115      *         .setEventIndex(2)
1116      *         .build();
1117      * </pre>
1118      */
1119     public static final class ConversationActionsEvent extends TextClassifierEvent
1120             implements Parcelable {
1121 
1122         @NonNull
1123         public static final Creator<ConversationActionsEvent> CREATOR =
1124                 new Creator<ConversationActionsEvent>() {
1125                     @Override
1126                     public ConversationActionsEvent createFromParcel(Parcel in) {
1127                         // skip token, we already know this is a ConversationActionsEvent.
1128                         in.readInt();
1129                         return new ConversationActionsEvent(in);
1130                     }
1131 
1132                     @Override
1133                     public ConversationActionsEvent[] newArray(int size) {
1134                         return new ConversationActionsEvent[size];
1135                     }
1136                 };
1137 
ConversationActionsEvent(Parcel in)1138         private ConversationActionsEvent(Parcel in) {
1139             super(in);
1140         }
1141 
ConversationActionsEvent(ConversationActionsEvent.Builder builder)1142         private ConversationActionsEvent(ConversationActionsEvent.Builder builder) {
1143             super(builder);
1144         }
1145 
1146         /**
1147          * Builder class for {@link ConversationActionsEvent}.
1148          */
1149         public static final class Builder
1150                 extends TextClassifierEvent.Builder<ConversationActionsEvent.Builder> {
1151             /**
1152              * Creates a builder for building {@link TextSelectionEvent}s.
1153              *
1154              * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION}
1155              */
Builder(@ype int eventType)1156             public Builder(@Type int eventType) {
1157                 super(TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, eventType);
1158             }
1159 
1160             @Override
self()1161             Builder self() {
1162                 return this;
1163             }
1164 
1165             /**
1166              * Builds and returns a {@link ConversationActionsEvent}.
1167              */
1168             @NonNull
build()1169             public ConversationActionsEvent build() {
1170                 return new ConversationActionsEvent(this);
1171             }
1172         }
1173     }
1174 }
1175