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