SelectionEvent.java revision 35b3057627387102496b647c90740af4dd6b833d
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 SelectionEvent( 154 int start, int end, 155 @EventType int eventType, @EntityType String entityType, 156 @InvocationMethod int invocationMethod, @Nullable String resultId, 157 Logger.Config config) { 158 this(start, end, eventType, entityType, invocationMethod, resultId); 159 Preconditions.checkNotNull(config); 160 setTextClassificationSessionContext( 161 new TextClassificationContext.Builder( 162 config.getPackageName(), config.getWidgetType()) 163 .setWidgetVersion(config.getWidgetVersion()) 164 .build()); 165 } 166 167 private SelectionEvent(Parcel in) { 168 mAbsoluteStart = in.readInt(); 169 mAbsoluteEnd = in.readInt(); 170 mEventType = in.readInt(); 171 mEntityType = in.readString(); 172 mWidgetVersion = in.readInt() > 0 ? in.readString() : null; 173 mPackageName = in.readString(); 174 mWidgetType = in.readString(); 175 mInvocationMethod = in.readInt(); 176 mResultId = in.readString(); 177 mEventTime = in.readLong(); 178 mDurationSinceSessionStart = in.readLong(); 179 mDurationSincePreviousEvent = in.readLong(); 180 mEventIndex = in.readInt(); 181 mSessionId = in.readInt() > 0 182 ? TextClassificationSessionId.CREATOR.createFromParcel(in) : null; 183 mStart = in.readInt(); 184 mEnd = in.readInt(); 185 mSmartStart = in.readInt(); 186 mSmartEnd = in.readInt(); 187 } 188 189 @Override 190 public void writeToParcel(Parcel dest, int flags) { 191 dest.writeInt(mAbsoluteStart); 192 dest.writeInt(mAbsoluteEnd); 193 dest.writeInt(mEventType); 194 dest.writeString(mEntityType); 195 dest.writeInt(mWidgetVersion != null ? 1 : 0); 196 if (mWidgetVersion != null) { 197 dest.writeString(mWidgetVersion); 198 } 199 dest.writeString(mPackageName); 200 dest.writeString(mWidgetType); 201 dest.writeInt(mInvocationMethod); 202 dest.writeString(mResultId); 203 dest.writeLong(mEventTime); 204 dest.writeLong(mDurationSinceSessionStart); 205 dest.writeLong(mDurationSincePreviousEvent); 206 dest.writeInt(mEventIndex); 207 dest.writeInt(mSessionId != null ? 1 : 0); 208 if (mSessionId != null) { 209 mSessionId.writeToParcel(dest, flags); 210 } 211 dest.writeInt(mStart); 212 dest.writeInt(mEnd); 213 dest.writeInt(mSmartStart); 214 dest.writeInt(mSmartEnd); 215 } 216 217 @Override 218 public int describeContents() { 219 return 0; 220 } 221 222 /** 223 * Creates a "selection started" event. 224 * 225 * @param invocationMethod the way the selection was triggered 226 * @param start the index of the selected text 227 */ 228 @NonNull 229 public static SelectionEvent createSelectionStartedEvent( 230 @SelectionEvent.InvocationMethod int invocationMethod, int start) { 231 return new SelectionEvent( 232 start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED, 233 TextClassifier.TYPE_UNKNOWN, invocationMethod, NO_SIGNATURE); 234 } 235 236 /** 237 * Creates a "selection modified" event. 238 * Use when the user modifies the selection. 239 * 240 * @param start the start (inclusive) index of the selection 241 * @param end the end (exclusive) index of the selection 242 * 243 * @throws IllegalArgumentException if end is less than start 244 */ 245 @NonNull 246 public static SelectionEvent createSelectionModifiedEvent(int start, int end) { 247 Preconditions.checkArgument(end >= start, "end cannot be less than start"); 248 return new SelectionEvent( 249 start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, 250 TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, NO_SIGNATURE); 251 } 252 253 /** 254 * Creates a "selection modified" event. 255 * Use when the user modifies the selection and the selection's entity type is known. 256 * 257 * @param start the start (inclusive) index of the selection 258 * @param end the end (exclusive) index of the selection 259 * @param classification the TextClassification object returned by the TextClassifier that 260 * classified the selected text 261 * 262 * @throws IllegalArgumentException if end is less than start 263 */ 264 @NonNull 265 public static SelectionEvent createSelectionModifiedEvent( 266 int start, int end, @NonNull TextClassification classification) { 267 Preconditions.checkArgument(end >= start, "end cannot be less than start"); 268 Preconditions.checkNotNull(classification); 269 final String entityType = classification.getEntityCount() > 0 270 ? classification.getEntity(0) 271 : TextClassifier.TYPE_UNKNOWN; 272 return new SelectionEvent( 273 start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, 274 entityType, INVOCATION_UNKNOWN, classification.getId()); 275 } 276 277 /** 278 * Creates a "selection modified" event. 279 * Use when a TextClassifier modifies the selection. 280 * 281 * @param start the start (inclusive) index of the selection 282 * @param end the end (exclusive) index of the selection 283 * @param selection the TextSelection object returned by the TextClassifier for the 284 * specified selection 285 * 286 * @throws IllegalArgumentException if end is less than start 287 */ 288 @NonNull 289 public static SelectionEvent createSelectionModifiedEvent( 290 int start, int end, @NonNull TextSelection selection) { 291 Preconditions.checkArgument(end >= start, "end cannot be less than start"); 292 Preconditions.checkNotNull(selection); 293 final String entityType = selection.getEntityCount() > 0 294 ? selection.getEntity(0) 295 : TextClassifier.TYPE_UNKNOWN; 296 return new SelectionEvent( 297 start, end, SelectionEvent.EVENT_AUTO_SELECTION, 298 entityType, INVOCATION_UNKNOWN, selection.getId()); 299 } 300 301 /** 302 * Creates an event specifying an action taken on a selection. 303 * Use when the user clicks on an action to act on the selected text. 304 * 305 * @param start the start (inclusive) index of the selection 306 * @param end the end (exclusive) index of the selection 307 * @param actionType the action that was performed on the selection 308 * 309 * @throws IllegalArgumentException if end is less than start 310 */ 311 @NonNull 312 public static SelectionEvent createSelectionActionEvent( 313 int start, int end, @SelectionEvent.ActionType int actionType) { 314 Preconditions.checkArgument(end >= start, "end cannot be less than start"); 315 checkActionType(actionType); 316 return new SelectionEvent( 317 start, end, actionType, TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, 318 NO_SIGNATURE); 319 } 320 321 /** 322 * Creates an event specifying an action taken on a selection. 323 * Use when the user clicks on an action to act on the selected text and the selection's 324 * entity type is known. 325 * 326 * @param start the start (inclusive) index of the selection 327 * @param end the end (exclusive) index of the selection 328 * @param actionType the action that was performed on the selection 329 * @param classification the TextClassification object returned by the TextClassifier that 330 * classified the selected text 331 * 332 * @throws IllegalArgumentException if end is less than start 333 * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType 334 */ 335 @NonNull 336 public static SelectionEvent createSelectionActionEvent( 337 int start, int end, @SelectionEvent.ActionType int actionType, 338 @NonNull TextClassification classification) { 339 Preconditions.checkArgument(end >= start, "end cannot be less than start"); 340 Preconditions.checkNotNull(classification); 341 checkActionType(actionType); 342 final String entityType = classification.getEntityCount() > 0 343 ? classification.getEntity(0) 344 : TextClassifier.TYPE_UNKNOWN; 345 return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN, 346 classification.getId()); 347 } 348 349 /** 350 * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType} 351 */ 352 private static void checkActionType(@SelectionEvent.EventType int eventType) 353 throws IllegalArgumentException { 354 switch (eventType) { 355 case SelectionEvent.ACTION_OVERTYPE: // fall through 356 case SelectionEvent.ACTION_COPY: // fall through 357 case SelectionEvent.ACTION_PASTE: // fall through 358 case SelectionEvent.ACTION_CUT: // fall through 359 case SelectionEvent.ACTION_SHARE: // fall through 360 case SelectionEvent.ACTION_SMART_SHARE: // fall through 361 case SelectionEvent.ACTION_DRAG: // fall through 362 case SelectionEvent.ACTION_ABANDON: // fall through 363 case SelectionEvent.ACTION_SELECT_ALL: // fall through 364 case SelectionEvent.ACTION_RESET: // fall through 365 case SelectionEvent.ACTION_OTHER: // fall through 366 return; 367 default: 368 throw new IllegalArgumentException( 369 String.format(Locale.US, "%d is not an eventType", eventType)); 370 } 371 } 372 373 int getAbsoluteStart() { 374 return mAbsoluteStart; 375 } 376 377 int getAbsoluteEnd() { 378 return mAbsoluteEnd; 379 } 380 381 /** 382 * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}. 383 */ 384 @EventType 385 public int getEventType() { 386 return mEventType; 387 } 388 389 /** 390 * Sets the event type. 391 */ 392 void setEventType(@EventType int eventType) { 393 mEventType = eventType; 394 } 395 396 /** 397 * Returns the type of entity that is associated with this event. e.g. 398 * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}. 399 */ 400 @EntityType 401 @NonNull 402 public String getEntityType() { 403 return mEntityType; 404 } 405 406 /** 407 * Returns the package name of the app that this event originated in. 408 */ 409 @NonNull 410 public String getPackageName() { 411 return mPackageName; 412 } 413 414 /** 415 * Returns the type of widget that was involved in triggering this event. 416 */ 417 @WidgetType 418 @NonNull 419 public String getWidgetType() { 420 return mWidgetType; 421 } 422 423 /** 424 * Returns a string version info for the widget this event was triggered in. 425 */ 426 @Nullable 427 public String getWidgetVersion() { 428 return mWidgetVersion; 429 } 430 431 /** 432 * Sets the {@link TextClassificationContext} for this event. 433 */ 434 void setTextClassificationSessionContext(TextClassificationContext context) { 435 mPackageName = context.getPackageName(); 436 mWidgetType = context.getWidgetType(); 437 mWidgetVersion = context.getWidgetVersion(); 438 } 439 440 /** 441 * Returns the way the selection mode was invoked. 442 */ 443 public @InvocationMethod int getInvocationMethod() { 444 return mInvocationMethod; 445 } 446 447 /** 448 * Sets the invocationMethod for this event. 449 */ 450 void setInvocationMethod(@InvocationMethod int invocationMethod) { 451 mInvocationMethod = invocationMethod; 452 } 453 454 /** 455 * Returns the id of the text classifier result associated with this event. 456 */ 457 @Nullable 458 public String getResultId() { 459 return mResultId; 460 } 461 462 SelectionEvent setResultId(@Nullable String resultId) { 463 mResultId = resultId; 464 return this; 465 } 466 467 /** 468 * Returns the time this event was triggered. 469 */ 470 public long getEventTime() { 471 return mEventTime; 472 } 473 474 SelectionEvent setEventTime(long timeMs) { 475 mEventTime = timeMs; 476 return this; 477 } 478 479 /** 480 * Returns the duration in ms between when this event was triggered and when the first event in 481 * the selection session was triggered. 482 */ 483 public long getDurationSinceSessionStart() { 484 return mDurationSinceSessionStart; 485 } 486 487 SelectionEvent setDurationSinceSessionStart(long durationMs) { 488 mDurationSinceSessionStart = durationMs; 489 return this; 490 } 491 492 /** 493 * Returns the duration in ms between when this event was triggered and when the previous event 494 * in the selection session was triggered. 495 */ 496 public long getDurationSincePreviousEvent() { 497 return mDurationSincePreviousEvent; 498 } 499 500 SelectionEvent setDurationSincePreviousEvent(long durationMs) { 501 this.mDurationSincePreviousEvent = durationMs; 502 return this; 503 } 504 505 /** 506 * Returns the index (e.g. 1st event, 2nd event, etc.) of this event in the selection session. 507 */ 508 public int getEventIndex() { 509 return mEventIndex; 510 } 511 512 SelectionEvent setEventIndex(int index) { 513 mEventIndex = index; 514 return this; 515 } 516 517 /** 518 * Returns the selection session id. 519 */ 520 @Nullable 521 public TextClassificationSessionId getSessionId() { 522 return mSessionId; 523 } 524 525 SelectionEvent setSessionId(TextClassificationSessionId id) { 526 mSessionId = id; 527 return this; 528 } 529 530 /** 531 * Returns the start index of this events relative to the index of the start selection 532 * event in the selection session. 533 */ 534 public int getStart() { 535 return mStart; 536 } 537 538 SelectionEvent setStart(int start) { 539 mStart = start; 540 return this; 541 } 542 543 /** 544 * Returns the end index of this events relative to the index of the start selection 545 * event in the selection session. 546 */ 547 public int getEnd() { 548 return mEnd; 549 } 550 551 SelectionEvent setEnd(int end) { 552 mEnd = end; 553 return this; 554 } 555 556 /** 557 * Returns the start index of this events relative to the index of the smart selection 558 * event in the selection session. 559 */ 560 public int getSmartStart() { 561 return mSmartStart; 562 } 563 564 SelectionEvent setSmartStart(int start) { 565 this.mSmartStart = start; 566 return this; 567 } 568 569 /** 570 * Returns the end index of this events relative to the index of the smart selection 571 * event in the selection session. 572 */ 573 public int getSmartEnd() { 574 return mSmartEnd; 575 } 576 577 SelectionEvent setSmartEnd(int end) { 578 mSmartEnd = end; 579 return this; 580 } 581 582 boolean isTerminal() { 583 return isTerminal(mEventType); 584 } 585 586 /** 587 * Returns true if the eventType is a terminal event type. Otherwise returns false. 588 * A terminal event is an event that ends a selection interaction. 589 */ 590 public static boolean isTerminal(@EventType int eventType) { 591 switch (eventType) { 592 case ACTION_OVERTYPE: // fall through 593 case ACTION_COPY: // fall through 594 case ACTION_PASTE: // fall through 595 case ACTION_CUT: // fall through 596 case ACTION_SHARE: // fall through 597 case ACTION_SMART_SHARE: // fall through 598 case ACTION_DRAG: // fall through 599 case ACTION_ABANDON: // fall through 600 case ACTION_OTHER: // fall through 601 return true; 602 default: 603 return false; 604 } 605 } 606 607 @Override 608 public int hashCode() { 609 return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, 610 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId, 611 mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, 612 mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); 613 } 614 615 @Override 616 public boolean equals(Object obj) { 617 if (this == obj) { 618 return true; 619 } 620 if (!(obj instanceof SelectionEvent)) { 621 return false; 622 } 623 624 final SelectionEvent other = (SelectionEvent) obj; 625 return mAbsoluteStart == other.mAbsoluteStart 626 && mAbsoluteEnd == other.mAbsoluteEnd 627 && mEventType == other.mEventType 628 && Objects.equals(mEntityType, other.mEntityType) 629 && Objects.equals(mWidgetVersion, other.mWidgetVersion) 630 && Objects.equals(mPackageName, other.mPackageName) 631 && Objects.equals(mWidgetType, other.mWidgetType) 632 && mInvocationMethod == other.mInvocationMethod 633 && Objects.equals(mResultId, other.mResultId) 634 && mEventTime == other.mEventTime 635 && mDurationSinceSessionStart == other.mDurationSinceSessionStart 636 && mDurationSincePreviousEvent == other.mDurationSincePreviousEvent 637 && mEventIndex == other.mEventIndex 638 && Objects.equals(mSessionId, other.mSessionId) 639 && mStart == other.mStart 640 && mEnd == other.mEnd 641 && mSmartStart == other.mSmartStart 642 && mSmartEnd == other.mSmartEnd; 643 } 644 645 @Override 646 public String toString() { 647 return String.format(Locale.US, 648 "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, " 649 + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, " 650 + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, " 651 + "durationSincePreviousEvent=%d, eventIndex=%d," 652 + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}", 653 mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, 654 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, 655 mResultId, mEventTime, mDurationSinceSessionStart, 656 mDurationSincePreviousEvent, mEventIndex, 657 mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); 658 } 659 660 public static final Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() { 661 @Override 662 public SelectionEvent createFromParcel(Parcel in) { 663 return new SelectionEvent(in); 664 } 665 666 @Override 667 public SelectionEvent[] newArray(int size) { 668 return new SelectionEvent[size]; 669 } 670 }; 671} 672