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