SelectionEvent.java revision 080c8542b68cf17a0441862c404cb49ce0e86cfe
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 return; 366 default: 367 throw new IllegalArgumentException( 368 String.format(Locale.US, "%d is not an eventType", eventType)); 369 } 370 } 371 372 int getAbsoluteStart() { 373 return mAbsoluteStart; 374 } 375 376 int getAbsoluteEnd() { 377 return mAbsoluteEnd; 378 } 379 380 /** 381 * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}. 382 */ 383 @EventType 384 public int getEventType() { 385 return mEventType; 386 } 387 388 /** 389 * Sets the event type. 390 */ 391 void setEventType(@EventType int eventType) { 392 mEventType = eventType; 393 } 394 395 /** 396 * Returns the type of entity that is associated with this event. e.g. 397 * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}. 398 */ 399 @EntityType 400 @NonNull 401 public String getEntityType() { 402 return mEntityType; 403 } 404 405 /** 406 * Returns the package name of the app that this event originated in. 407 */ 408 @NonNull 409 public String getPackageName() { 410 return mPackageName; 411 } 412 413 /** 414 * Returns the type of widget that was involved in triggering this event. 415 */ 416 @WidgetType 417 @NonNull 418 public String getWidgetType() { 419 return mWidgetType; 420 } 421 422 /** 423 * Returns a string version info for the widget this event was triggered in. 424 */ 425 @Nullable 426 public String getWidgetVersion() { 427 return mWidgetVersion; 428 } 429 430 /** 431 * Sets the {@link TextClassificationContext} for this event. 432 */ 433 void setTextClassificationSessionContext(TextClassificationContext context) { 434 mPackageName = context.getPackageName(); 435 mWidgetType = context.getWidgetType(); 436 mWidgetVersion = context.getWidgetVersion(); 437 } 438 439 /** 440 * Returns the way the selection mode was invoked. 441 */ 442 public @InvocationMethod int getInvocationMethod() { 443 return mInvocationMethod; 444 } 445 446 /** 447 * Sets the invocationMethod for this event. 448 */ 449 void setInvocationMethod(@InvocationMethod int invocationMethod) { 450 mInvocationMethod = invocationMethod; 451 } 452 453 /** 454 * Returns the id of the text classifier result associated with this event. 455 */ 456 @Nullable 457 public String getResultId() { 458 return mResultId; 459 } 460 461 SelectionEvent setResultId(@Nullable String resultId) { 462 mResultId = resultId; 463 return this; 464 } 465 466 /** 467 * Returns the time this event was triggered. 468 */ 469 public long getEventTime() { 470 return mEventTime; 471 } 472 473 SelectionEvent setEventTime(long timeMs) { 474 mEventTime = timeMs; 475 return this; 476 } 477 478 /** 479 * Returns the duration in ms between when this event was triggered and when the first event in 480 * the selection session was triggered. 481 */ 482 public long getDurationSinceSessionStart() { 483 return mDurationSinceSessionStart; 484 } 485 486 SelectionEvent setDurationSinceSessionStart(long durationMs) { 487 mDurationSinceSessionStart = durationMs; 488 return this; 489 } 490 491 /** 492 * Returns the duration in ms between when this event was triggered and when the previous event 493 * in the selection session was triggered. 494 */ 495 public long getDurationSincePreviousEvent() { 496 return mDurationSincePreviousEvent; 497 } 498 499 SelectionEvent setDurationSincePreviousEvent(long durationMs) { 500 this.mDurationSincePreviousEvent = durationMs; 501 return this; 502 } 503 504 /** 505 * Returns the index (e.g. 1st event, 2nd event, etc.) of this event in the selection session. 506 */ 507 public int getEventIndex() { 508 return mEventIndex; 509 } 510 511 SelectionEvent setEventIndex(int index) { 512 mEventIndex = index; 513 return this; 514 } 515 516 /** 517 * Returns the selection session id. 518 */ 519 @Nullable 520 public TextClassificationSessionId getSessionId() { 521 return mSessionId; 522 } 523 524 SelectionEvent setSessionId(TextClassificationSessionId id) { 525 mSessionId = id; 526 return this; 527 } 528 529 /** 530 * Returns the start index of this events relative to the index of the start selection 531 * event in the selection session. 532 */ 533 public int getStart() { 534 return mStart; 535 } 536 537 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 */ 546 public int getEnd() { 547 return mEnd; 548 } 549 550 SelectionEvent setEnd(int end) { 551 mEnd = end; 552 return this; 553 } 554 555 /** 556 * Returns the start index of this events relative to the index of the smart selection 557 * event in the selection session. 558 */ 559 public int getSmartStart() { 560 return mSmartStart; 561 } 562 563 SelectionEvent setSmartStart(int start) { 564 this.mSmartStart = start; 565 return this; 566 } 567 568 /** 569 * Returns the end index of this events relative to the index of the smart selection 570 * event in the selection session. 571 */ 572 public int getSmartEnd() { 573 return mSmartEnd; 574 } 575 576 SelectionEvent setSmartEnd(int end) { 577 mSmartEnd = end; 578 return this; 579 } 580 581 boolean isTerminal() { 582 return isTerminal(mEventType); 583 } 584 585 /** 586 * Returns true if the eventType is a terminal event type. Otherwise returns false. 587 * A terminal event is an event that ends a selection interaction. 588 */ 589 public static boolean isTerminal(@EventType int eventType) { 590 switch (eventType) { 591 case ACTION_OVERTYPE: // fall through 592 case ACTION_COPY: // fall through 593 case ACTION_PASTE: // fall through 594 case ACTION_CUT: // fall through 595 case ACTION_SHARE: // fall through 596 case ACTION_SMART_SHARE: // fall through 597 case ACTION_DRAG: // fall through 598 case ACTION_ABANDON: // fall through 599 case ACTION_OTHER: // fall through 600 return true; 601 default: 602 return false; 603 } 604 } 605 606 @Override 607 public int hashCode() { 608 return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, 609 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId, 610 mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, 611 mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); 612 } 613 614 @Override 615 public boolean equals(Object obj) { 616 if (this == obj) { 617 return true; 618 } 619 if (!(obj instanceof SelectionEvent)) { 620 return false; 621 } 622 623 final SelectionEvent other = (SelectionEvent) obj; 624 return mAbsoluteStart == other.mAbsoluteStart 625 && mAbsoluteEnd == other.mAbsoluteEnd 626 && mEventType == other.mEventType 627 && Objects.equals(mEntityType, other.mEntityType) 628 && Objects.equals(mWidgetVersion, other.mWidgetVersion) 629 && Objects.equals(mPackageName, other.mPackageName) 630 && Objects.equals(mWidgetType, other.mWidgetType) 631 && mInvocationMethod == other.mInvocationMethod 632 && Objects.equals(mResultId, other.mResultId) 633 && mEventTime == other.mEventTime 634 && mDurationSinceSessionStart == other.mDurationSinceSessionStart 635 && mDurationSincePreviousEvent == other.mDurationSincePreviousEvent 636 && mEventIndex == other.mEventIndex 637 && Objects.equals(mSessionId, other.mSessionId) 638 && mStart == other.mStart 639 && mEnd == other.mEnd 640 && mSmartStart == other.mSmartStart 641 && mSmartEnd == other.mSmartEnd; 642 } 643 644 @Override 645 public String toString() { 646 return String.format(Locale.US, 647 "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, " 648 + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, " 649 + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, " 650 + "durationSincePreviousEvent=%d, eventIndex=%d," 651 + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}", 652 mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, 653 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, 654 mResultId, mEventTime, mDurationSinceSessionStart, 655 mDurationSincePreviousEvent, mEventIndex, 656 mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); 657 } 658 659 public static final Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() { 660 @Override 661 public SelectionEvent createFromParcel(Parcel in) { 662 return new SelectionEvent(in); 663 } 664 665 @Override 666 public SelectionEvent[] newArray(int size) { 667 return new SelectionEvent[size]; 668 } 669 }; 670}