1 /*
2  * Copyright (C) 2017 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.os.Parcel;
23 import android.os.Parcelable;
24 import android.view.textclassifier.TextClassifier.EntityType;
25 import android.view.textclassifier.TextClassifier.WidgetType;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.util.Preconditions;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.Locale;
33 import java.util.Objects;
34 
35 /**
36  * A selection event.
37  * Specify index parameters as word token indices.
38  */
39 public final class SelectionEvent implements Parcelable {
40 
41     /** @hide */
42     @Retention(RetentionPolicy.SOURCE)
43     @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT,
44             ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON,
45             ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET})
46     // NOTE: ActionType values should not be lower than 100 to avoid colliding with the other
47     // EventTypes declared below.
48     public @interface ActionType {
49         /*
50          * Terminal event types range: [100,200).
51          * Non-terminal event types range: [200,300).
52          */
53     }
54 
55     /** User typed over the selection. */
56     public static final int ACTION_OVERTYPE = 100;
57     /** User copied the selection. */
58     public static final int ACTION_COPY = 101;
59     /** User pasted over the selection. */
60     public static final int ACTION_PASTE = 102;
61     /** User cut the selection. */
62     public static final int ACTION_CUT = 103;
63     /** User shared the selection. */
64     public static final int ACTION_SHARE = 104;
65     /** User clicked the textAssist menu item. */
66     public static final int ACTION_SMART_SHARE = 105;
67     /** User dragged+dropped the selection. */
68     public static final int ACTION_DRAG = 106;
69     /** User abandoned the selection. */
70     public static final int ACTION_ABANDON = 107;
71     /** User performed an action on the selection. */
72     public static final int ACTION_OTHER = 108;
73 
74     // Non-terminal actions.
75     /** User activated Select All */
76     public static final int ACTION_SELECT_ALL = 200;
77     /** User reset the smart selection. */
78     public static final int ACTION_RESET = 201;
79 
80     /** @hide */
81     @Retention(RetentionPolicy.SOURCE)
82     @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT,
83             ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON,
84             ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET,
85             EVENT_SELECTION_STARTED, EVENT_SELECTION_MODIFIED,
86             EVENT_SMART_SELECTION_SINGLE, EVENT_SMART_SELECTION_MULTI,
87             EVENT_AUTO_SELECTION})
88     // NOTE: EventTypes declared here must be less than 100 to avoid colliding with the
89     // ActionTypes declared above.
90     public @interface EventType {
91         /*
92          * Range: 1 -> 99.
93          */
94     }
95 
96     /** User started a new selection. */
97     public static final int EVENT_SELECTION_STARTED = 1;
98     /** User modified an existing selection. */
99     public static final int EVENT_SELECTION_MODIFIED = 2;
100     /** Smart selection triggered for a single token (word). */
101     public static final int EVENT_SMART_SELECTION_SINGLE = 3;
102     /** Smart selection triggered spanning multiple tokens (words). */
103     public static final int EVENT_SMART_SELECTION_MULTI = 4;
104     /** Something else other than User or the default TextClassifier triggered a selection. */
105     public static final int EVENT_AUTO_SELECTION = 5;
106 
107     /** @hide */
108     @Retention(RetentionPolicy.SOURCE)
109     @IntDef({INVOCATION_MANUAL, INVOCATION_LINK, INVOCATION_UNKNOWN})
110     public @interface InvocationMethod {}
111 
112     /** Selection was invoked by the user long pressing, double tapping, or dragging to select. */
113     public static final int INVOCATION_MANUAL = 1;
114     /** Selection was invoked by the user tapping on a link. */
115     public static final int INVOCATION_LINK = 2;
116     /** Unknown invocation method */
117     public static final int INVOCATION_UNKNOWN = 0;
118 
119     static final String NO_SIGNATURE = "";
120 
121     private final int mAbsoluteStart;
122     private final int mAbsoluteEnd;
123     private final @EntityType String mEntityType;
124 
125     private @EventType int mEventType;
126     private String mPackageName = "";
127     private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN;
128     private @InvocationMethod int mInvocationMethod;
129     @Nullable private String mWidgetVersion;
130     @Nullable private String mResultId;
131     private long mEventTime;
132     private long mDurationSinceSessionStart;
133     private long mDurationSincePreviousEvent;
134     private int mEventIndex;
135     @Nullable private TextClassificationSessionId mSessionId;
136     private int mStart;
137     private int mEnd;
138     private int mSmartStart;
139     private int mSmartEnd;
140 
SelectionEvent( int start, int end, @EventType int eventType, @EntityType String entityType, @InvocationMethod int invocationMethod, @Nullable String resultId)141     SelectionEvent(
142             int start, int end,
143             @EventType int eventType, @EntityType String entityType,
144             @InvocationMethod int invocationMethod, @Nullable String resultId) {
145         Preconditions.checkArgument(end >= start, "end cannot be less than start");
146         mAbsoluteStart = start;
147         mAbsoluteEnd = end;
148         mEventType = eventType;
149         mEntityType = Preconditions.checkNotNull(entityType);
150         mResultId = resultId;
151         mInvocationMethod = invocationMethod;
152     }
153 
SelectionEvent(Parcel in)154     private SelectionEvent(Parcel in) {
155         mAbsoluteStart = in.readInt();
156         mAbsoluteEnd = in.readInt();
157         mEventType = in.readInt();
158         mEntityType = in.readString();
159         mWidgetVersion = in.readInt() > 0 ? in.readString() : null;
160         mPackageName = in.readString();
161         mWidgetType = in.readString();
162         mInvocationMethod = in.readInt();
163         mResultId = in.readString();
164         mEventTime = in.readLong();
165         mDurationSinceSessionStart = in.readLong();
166         mDurationSincePreviousEvent = in.readLong();
167         mEventIndex = in.readInt();
168         mSessionId = in.readInt() > 0
169                 ? TextClassificationSessionId.CREATOR.createFromParcel(in) : null;
170         mStart = in.readInt();
171         mEnd = in.readInt();
172         mSmartStart = in.readInt();
173         mSmartEnd = in.readInt();
174     }
175 
176     @Override
writeToParcel(Parcel dest, int flags)177     public void writeToParcel(Parcel dest, int flags) {
178         dest.writeInt(mAbsoluteStart);
179         dest.writeInt(mAbsoluteEnd);
180         dest.writeInt(mEventType);
181         dest.writeString(mEntityType);
182         dest.writeInt(mWidgetVersion != null ? 1 : 0);
183         if (mWidgetVersion != null) {
184             dest.writeString(mWidgetVersion);
185         }
186         dest.writeString(mPackageName);
187         dest.writeString(mWidgetType);
188         dest.writeInt(mInvocationMethod);
189         dest.writeString(mResultId);
190         dest.writeLong(mEventTime);
191         dest.writeLong(mDurationSinceSessionStart);
192         dest.writeLong(mDurationSincePreviousEvent);
193         dest.writeInt(mEventIndex);
194         dest.writeInt(mSessionId != null ? 1 : 0);
195         if (mSessionId != null) {
196             mSessionId.writeToParcel(dest, flags);
197         }
198         dest.writeInt(mStart);
199         dest.writeInt(mEnd);
200         dest.writeInt(mSmartStart);
201         dest.writeInt(mSmartEnd);
202     }
203 
204     @Override
describeContents()205     public int describeContents() {
206         return 0;
207     }
208 
209     /**
210      * Creates a "selection started" event.
211      *
212      * @param invocationMethod  the way the selection was triggered
213      * @param start  the index of the selected text
214      */
215     @NonNull
createSelectionStartedEvent( @electionEvent.InvocationMethod int invocationMethod, int start)216     public static SelectionEvent createSelectionStartedEvent(
217             @SelectionEvent.InvocationMethod int invocationMethod, int start) {
218         return new SelectionEvent(
219                 start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED,
220                 TextClassifier.TYPE_UNKNOWN, invocationMethod, NO_SIGNATURE);
221     }
222 
223     /**
224      * Creates a "selection modified" event.
225      * Use when the user modifies the selection.
226      *
227      * @param start  the start (inclusive) index of the selection
228      * @param end  the end (exclusive) index of the selection
229      *
230      * @throws IllegalArgumentException if end is less than start
231      */
232     @NonNull
createSelectionModifiedEvent(int start, int end)233     public static SelectionEvent createSelectionModifiedEvent(int start, int end) {
234         Preconditions.checkArgument(end >= start, "end cannot be less than start");
235         return new SelectionEvent(
236                 start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
237                 TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, NO_SIGNATURE);
238     }
239 
240     /**
241      * Creates a "selection modified" event.
242      * Use when the user modifies the selection and the selection's entity type is known.
243      *
244      * @param start  the start (inclusive) index of the selection
245      * @param end  the end (exclusive) index of the selection
246      * @param classification  the TextClassification object returned by the TextClassifier that
247      *      classified the selected text
248      *
249      * @throws IllegalArgumentException if end is less than start
250      */
251     @NonNull
createSelectionModifiedEvent( int start, int end, @NonNull TextClassification classification)252     public static SelectionEvent createSelectionModifiedEvent(
253             int start, int end, @NonNull TextClassification classification) {
254         Preconditions.checkArgument(end >= start, "end cannot be less than start");
255         Preconditions.checkNotNull(classification);
256         final String entityType = classification.getEntityCount() > 0
257                 ? classification.getEntity(0)
258                 : TextClassifier.TYPE_UNKNOWN;
259         return new SelectionEvent(
260                 start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
261                 entityType, INVOCATION_UNKNOWN, classification.getId());
262     }
263 
264     /**
265      * Creates a "selection modified" event.
266      * Use when a TextClassifier modifies the selection.
267      *
268      * @param start  the start (inclusive) index of the selection
269      * @param end  the end (exclusive) index of the selection
270      * @param selection  the TextSelection object returned by the TextClassifier for the
271      *      specified selection
272      *
273      * @throws IllegalArgumentException if end is less than start
274      */
275     @NonNull
createSelectionModifiedEvent( int start, int end, @NonNull TextSelection selection)276     public static SelectionEvent createSelectionModifiedEvent(
277             int start, int end, @NonNull TextSelection selection) {
278         Preconditions.checkArgument(end >= start, "end cannot be less than start");
279         Preconditions.checkNotNull(selection);
280         final String entityType = selection.getEntityCount() > 0
281                 ? selection.getEntity(0)
282                 : TextClassifier.TYPE_UNKNOWN;
283         return new SelectionEvent(
284                 start, end, SelectionEvent.EVENT_AUTO_SELECTION,
285                 entityType, INVOCATION_UNKNOWN, selection.getId());
286     }
287 
288     /**
289      * Creates an event specifying an action taken on a selection.
290      * Use when the user clicks on an action to act on the selected text.
291      *
292      * @param start  the start (inclusive) index of the selection
293      * @param end  the end (exclusive) index of the selection
294      * @param actionType  the action that was performed on the selection
295      *
296      * @throws IllegalArgumentException if end is less than start
297      */
298     @NonNull
createSelectionActionEvent( int start, int end, @SelectionEvent.ActionType int actionType)299     public static SelectionEvent createSelectionActionEvent(
300             int start, int end, @SelectionEvent.ActionType int actionType) {
301         Preconditions.checkArgument(end >= start, "end cannot be less than start");
302         checkActionType(actionType);
303         return new SelectionEvent(
304                 start, end, actionType, TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN,
305                 NO_SIGNATURE);
306     }
307 
308     /**
309      * Creates an event specifying an action taken on a selection.
310      * Use when the user clicks on an action to act on the selected text and the selection's
311      * entity type is known.
312      *
313      * @param start  the start (inclusive) index of the selection
314      * @param end  the end (exclusive) index of the selection
315      * @param actionType  the action that was performed on the selection
316      * @param classification  the TextClassification object returned by the TextClassifier that
317      *      classified the selected text
318      *
319      * @throws IllegalArgumentException if end is less than start
320      * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType
321      */
322     @NonNull
createSelectionActionEvent( int start, int end, @SelectionEvent.ActionType int actionType, @NonNull TextClassification classification)323     public static SelectionEvent createSelectionActionEvent(
324             int start, int end, @SelectionEvent.ActionType int actionType,
325             @NonNull TextClassification classification) {
326         Preconditions.checkArgument(end >= start, "end cannot be less than start");
327         Preconditions.checkNotNull(classification);
328         checkActionType(actionType);
329         final String entityType = classification.getEntityCount() > 0
330                 ? classification.getEntity(0)
331                 : TextClassifier.TYPE_UNKNOWN;
332         return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN,
333                 classification.getId());
334     }
335 
336     /**
337      * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType}
338      */
checkActionType(@electionEvent.EventType int eventType)339     private static void checkActionType(@SelectionEvent.EventType int eventType)
340             throws IllegalArgumentException {
341         switch (eventType) {
342             case SelectionEvent.ACTION_OVERTYPE:  // fall through
343             case SelectionEvent.ACTION_COPY:  // fall through
344             case SelectionEvent.ACTION_PASTE:  // fall through
345             case SelectionEvent.ACTION_CUT:  // fall through
346             case SelectionEvent.ACTION_SHARE:  // fall through
347             case SelectionEvent.ACTION_SMART_SHARE:  // fall through
348             case SelectionEvent.ACTION_DRAG:  // fall through
349             case SelectionEvent.ACTION_ABANDON:  // fall through
350             case SelectionEvent.ACTION_SELECT_ALL:  // fall through
351             case SelectionEvent.ACTION_RESET:  // fall through
352             case SelectionEvent.ACTION_OTHER:  // fall through
353                 return;
354             default:
355                 throw new IllegalArgumentException(
356                         String.format(Locale.US, "%d is not an eventType", eventType));
357         }
358     }
359 
getAbsoluteStart()360     int getAbsoluteStart() {
361         return mAbsoluteStart;
362     }
363 
getAbsoluteEnd()364     int getAbsoluteEnd() {
365         return mAbsoluteEnd;
366     }
367 
368     /**
369      * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}.
370      */
371     @EventType
getEventType()372     public int getEventType() {
373         return mEventType;
374     }
375 
376     /**
377      * Sets the event type.
378      * @hide
379      */
380     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setEventType(@ventType int eventType)381     public void setEventType(@EventType int eventType) {
382         mEventType = eventType;
383     }
384 
385     /**
386      * Returns the type of entity that is associated with this event. e.g.
387      * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}.
388      */
389     @EntityType
390     @NonNull
getEntityType()391     public String getEntityType() {
392         return mEntityType;
393     }
394 
395     /**
396      * Returns the package name of the app that this event originated in.
397      */
398     @NonNull
getPackageName()399     public String getPackageName() {
400         return mPackageName;
401     }
402 
403     /**
404      * Returns the type of widget that was involved in triggering this event.
405      */
406     @WidgetType
407     @NonNull
getWidgetType()408     public String getWidgetType() {
409         return mWidgetType;
410     }
411 
412     /**
413      * Returns a string version info for the widget this event was triggered in.
414      */
415     @Nullable
getWidgetVersion()416     public String getWidgetVersion() {
417         return mWidgetVersion;
418     }
419 
420     /**
421      * Sets the {@link TextClassificationContext} for this event.
422      * @hide
423      */
424     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setTextClassificationSessionContext(TextClassificationContext context)425     public void setTextClassificationSessionContext(TextClassificationContext context) {
426         mPackageName = context.getPackageName();
427         mWidgetType = context.getWidgetType();
428         mWidgetVersion = context.getWidgetVersion();
429     }
430 
431     /**
432      * Returns the way the selection mode was invoked.
433      */
getInvocationMethod()434     public @InvocationMethod int getInvocationMethod() {
435         return mInvocationMethod;
436     }
437 
438     /**
439      * Sets the invocationMethod for this event.
440      * @hide
441      */
442     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setInvocationMethod(@nvocationMethod int invocationMethod)443     public void setInvocationMethod(@InvocationMethod int invocationMethod) {
444         mInvocationMethod = invocationMethod;
445     }
446 
447     /**
448      * Returns the id of the text classifier result associated with this event.
449      */
450     @Nullable
getResultId()451     public String getResultId() {
452         return mResultId;
453     }
454 
setResultId(@ullable String resultId)455     SelectionEvent setResultId(@Nullable String resultId) {
456         mResultId = resultId;
457         return this;
458     }
459 
460     /**
461      * Returns the time this event was triggered.
462      */
getEventTime()463     public long getEventTime() {
464         return mEventTime;
465     }
466 
setEventTime(long timeMs)467     SelectionEvent setEventTime(long timeMs) {
468         mEventTime = timeMs;
469         return this;
470     }
471 
472     /**
473      * Returns the duration in ms between when this event was triggered and when the first event in
474      * the selection session was triggered.
475      */
getDurationSinceSessionStart()476     public long getDurationSinceSessionStart() {
477         return mDurationSinceSessionStart;
478     }
479 
setDurationSinceSessionStart(long durationMs)480     SelectionEvent setDurationSinceSessionStart(long durationMs) {
481         mDurationSinceSessionStart = durationMs;
482         return this;
483     }
484 
485     /**
486      * Returns the duration in ms between when this event was triggered and when the previous event
487      * in the selection session was triggered.
488      */
getDurationSincePreviousEvent()489     public long getDurationSincePreviousEvent() {
490         return mDurationSincePreviousEvent;
491     }
492 
setDurationSincePreviousEvent(long durationMs)493     SelectionEvent setDurationSincePreviousEvent(long durationMs) {
494         this.mDurationSincePreviousEvent = durationMs;
495         return this;
496     }
497 
498     /**
499      * Returns the index (e.g. 1st event, 2nd event, etc.) of this event in the selection session.
500      */
getEventIndex()501     public int getEventIndex() {
502         return mEventIndex;
503     }
504 
505     /** @hide */
506     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setEventIndex(int index)507     public SelectionEvent setEventIndex(int index) {
508         mEventIndex = index;
509         return this;
510     }
511 
512     /**
513      * Returns the selection session id.
514      */
515     @Nullable
getSessionId()516     public TextClassificationSessionId getSessionId() {
517         return mSessionId;
518     }
519 
520     /** @hide */
521     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setSessionId(@ullable TextClassificationSessionId id)522     public SelectionEvent setSessionId(@Nullable TextClassificationSessionId id) {
523         mSessionId = id;
524         return this;
525     }
526 
527     /**
528      * Returns the start index of this events relative to the index of the start selection
529      * event in the selection session.
530      */
getStart()531     public int getStart() {
532         return mStart;
533     }
534 
535     /** @hide */
536     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setStart(int start)537     public SelectionEvent setStart(int start) {
538         mStart = start;
539         return this;
540     }
541 
542     /**
543      * Returns the end index of this events relative to the index of the start selection
544      * event in the selection session.
545      */
getEnd()546     public int getEnd() {
547         return mEnd;
548     }
549 
550     /** @hide */
551     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setEnd(int end)552     public SelectionEvent setEnd(int end) {
553         mEnd = end;
554         return this;
555     }
556 
557     /**
558      * Returns the start index of this events relative to the index of the smart selection
559      * event in the selection session.
560      */
getSmartStart()561     public int getSmartStart() {
562         return mSmartStart;
563     }
564 
565     /** @hide */
566     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setSmartStart(int start)567     public SelectionEvent setSmartStart(int start) {
568         this.mSmartStart = start;
569         return this;
570     }
571 
572     /**
573      * Returns the end index of this events relative to the index of the smart selection
574      * event in the selection session.
575      */
getSmartEnd()576     public int getSmartEnd() {
577         return mSmartEnd;
578     }
579 
580     /** @hide */
581     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setSmartEnd(int end)582     public SelectionEvent setSmartEnd(int end) {
583         mSmartEnd = end;
584         return this;
585     }
586 
isTerminal()587     boolean isTerminal() {
588         return isTerminal(mEventType);
589     }
590 
591     /**
592      * Returns true if the eventType is a terminal event type. Otherwise returns false.
593      * A terminal event is an event that ends a selection interaction.
594      */
isTerminal(@ventType int eventType)595     public static boolean isTerminal(@EventType int eventType) {
596         switch (eventType) {
597             case ACTION_OVERTYPE:  // fall through
598             case ACTION_COPY:  // fall through
599             case ACTION_PASTE:  // fall through
600             case ACTION_CUT:  // fall through
601             case ACTION_SHARE:  // fall through
602             case ACTION_SMART_SHARE:  // fall through
603             case ACTION_DRAG:  // fall through
604             case ACTION_ABANDON:  // fall through
605             case ACTION_OTHER:  // fall through
606                 return true;
607             default:
608                 return false;
609         }
610     }
611 
612     @Override
hashCode()613     public int hashCode() {
614         return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
615                 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId,
616                 mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
617                 mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
618     }
619 
620     @Override
equals(Object obj)621     public boolean equals(Object obj) {
622         if (this == obj) {
623             return true;
624         }
625         if (!(obj instanceof SelectionEvent)) {
626             return false;
627         }
628 
629         final SelectionEvent other = (SelectionEvent) obj;
630         return mAbsoluteStart == other.mAbsoluteStart
631                 && mAbsoluteEnd == other.mAbsoluteEnd
632                 && mEventType == other.mEventType
633                 && Objects.equals(mEntityType, other.mEntityType)
634                 && Objects.equals(mWidgetVersion, other.mWidgetVersion)
635                 && Objects.equals(mPackageName, other.mPackageName)
636                 && Objects.equals(mWidgetType, other.mWidgetType)
637                 && mInvocationMethod == other.mInvocationMethod
638                 && Objects.equals(mResultId, other.mResultId)
639                 && mEventTime == other.mEventTime
640                 && mDurationSinceSessionStart == other.mDurationSinceSessionStart
641                 && mDurationSincePreviousEvent == other.mDurationSincePreviousEvent
642                 && mEventIndex == other.mEventIndex
643                 && Objects.equals(mSessionId, other.mSessionId)
644                 && mStart == other.mStart
645                 && mEnd == other.mEnd
646                 && mSmartStart == other.mSmartStart
647                 && mSmartEnd == other.mSmartEnd;
648     }
649 
650     @Override
toString()651     public String toString() {
652         return String.format(Locale.US,
653                 "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, "
654                         + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
655                         + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
656                         + "durationSincePreviousEvent=%d, eventIndex=%d,"
657                         + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}",
658                 mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
659                 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod,
660                 mResultId, mEventTime, mDurationSinceSessionStart,
661                 mDurationSincePreviousEvent, mEventIndex,
662                 mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
663     }
664 
665     public static final @android.annotation.NonNull Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() {
666         @Override
667         public SelectionEvent createFromParcel(Parcel in) {
668             return new SelectionEvent(in);
669         }
670 
671         @Override
672         public SelectionEvent[] newArray(int size) {
673             return new SelectionEvent[size];
674         }
675     };
676 }
677