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