1 /* 2 * Copyright 2018 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.icu.util.ULocale; 23 import android.os.Bundle; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.Arrays; 32 import java.util.Objects; 33 34 /** 35 * This class represents events that are sent by components to the {@link TextClassifier} to report 36 * something of note that relates to a feature powered by the TextClassifier. The TextClassifier may 37 * log these events or use them to improve future responses to queries. 38 * <p> 39 * Each category of events has its their own subclass. Events of each type have an associated 40 * set of related properties. You can find their specification in the subclasses. 41 */ 42 public abstract class TextClassifierEvent implements Parcelable { 43 44 private static final int PARCEL_TOKEN_TEXT_SELECTION_EVENT = 1; 45 private static final int PARCEL_TOKEN_TEXT_LINKIFY_EVENT = 2; 46 private static final int PARCEL_TOKEN_CONVERSATION_ACTION_EVENT = 3; 47 private static final int PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT = 4; 48 49 /** @hide **/ 50 @Retention(RetentionPolicy.SOURCE) 51 @IntDef({CATEGORY_SELECTION, CATEGORY_LINKIFY, 52 CATEGORY_CONVERSATION_ACTIONS, CATEGORY_LANGUAGE_DETECTION}) 53 public @interface Category { 54 // For custom event categories, use range 1000+. 55 } 56 57 /** 58 * Smart selection 59 * 60 * @see TextSelectionEvent 61 */ 62 public static final int CATEGORY_SELECTION = 1; 63 /** 64 * Linkify 65 * 66 * @see TextLinkifyEvent 67 */ 68 public static final int CATEGORY_LINKIFY = 2; 69 /** 70 * Conversation actions 71 * 72 * @see ConversationActionsEvent 73 */ 74 public static final int CATEGORY_CONVERSATION_ACTIONS = 3; 75 /** 76 * Language detection 77 * 78 * @see LanguageDetectionEvent 79 */ 80 public static final int CATEGORY_LANGUAGE_DETECTION = 4; 81 82 /** @hide */ 83 @Retention(RetentionPolicy.SOURCE) 84 @IntDef({TYPE_SELECTION_STARTED, TYPE_SELECTION_MODIFIED, 85 TYPE_SMART_SELECTION_SINGLE, TYPE_SMART_SELECTION_MULTI, TYPE_AUTO_SELECTION, 86 TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION, 87 TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION, 88 TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL, 89 TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED, TYPE_LINKS_GENERATED, 90 TYPE_READ_CLIPBOARD}) 91 public @interface Type { 92 // For custom event types, use range 1,000,000+. 93 } 94 95 // All these event type constants are required to match with those defined in 96 // textclassifier_enums.proto. 97 /** User started a new selection. */ 98 public static final int TYPE_SELECTION_STARTED = 1; 99 /** User modified an existing selection. */ 100 public static final int TYPE_SELECTION_MODIFIED = 2; 101 /** Smart selection triggered for a single token (word). */ 102 public static final int TYPE_SMART_SELECTION_SINGLE = 3; 103 /** Smart selection triggered spanning multiple tokens (words). */ 104 public static final int TYPE_SMART_SELECTION_MULTI = 4; 105 /** Something else other than user or the default TextClassifier triggered a selection. */ 106 public static final int TYPE_AUTO_SELECTION = 5; 107 /** Smart actions shown to the user. */ 108 public static final int TYPE_ACTIONS_SHOWN = 6; 109 /** User clicked a link. */ 110 public static final int TYPE_LINK_CLICKED = 7; 111 /** User typed over the selection. */ 112 public static final int TYPE_OVERTYPE = 8; 113 /** User clicked on Copy action. */ 114 public static final int TYPE_COPY_ACTION = 9; 115 /** User clicked on Paste action. */ 116 public static final int TYPE_PASTE_ACTION = 10; 117 /** User clicked on Cut action. */ 118 public static final int TYPE_CUT_ACTION = 11; 119 /** User clicked on Share action. */ 120 public static final int TYPE_SHARE_ACTION = 12; 121 /** User clicked on a Smart action. */ 122 public static final int TYPE_SMART_ACTION = 13; 123 /** User dragged+dropped the selection. */ 124 public static final int TYPE_SELECTION_DRAG = 14; 125 /** Selection is destroyed. */ 126 public static final int TYPE_SELECTION_DESTROYED = 15; 127 /** User clicked on a custom action. */ 128 public static final int TYPE_OTHER_ACTION = 16; 129 /** User clicked on Select All action */ 130 public static final int TYPE_SELECT_ALL = 17; 131 /** User reset the smart selection. */ 132 public static final int TYPE_SELECTION_RESET = 18; 133 /** User composed a reply. */ 134 public static final int TYPE_MANUAL_REPLY = 19; 135 /** TextClassifier generated some actions */ 136 public static final int TYPE_ACTIONS_GENERATED = 20; 137 /** Some text links were generated.*/ 138 public static final int TYPE_LINKS_GENERATED = 21; 139 /** 140 * Read a clipboard. 141 * TODO: Make this public. 142 * 143 * @hide 144 */ 145 public static final int TYPE_READ_CLIPBOARD = 22; 146 147 @Category 148 private final int mEventCategory; 149 @Type 150 private final int mEventType; 151 @Nullable 152 private final String[] mEntityTypes; 153 @Nullable 154 private TextClassificationContext mEventContext; 155 @Nullable 156 private final String mResultId; 157 private final int mEventIndex; 158 private final float[] mScores; 159 @Nullable 160 private final String mModelName; 161 private final int[] mActionIndices; 162 @Nullable 163 private final ULocale mLocale; 164 private final Bundle mExtras; 165 166 /** 167 * Session id holder to help with converting this event to the legacy SelectionEvent. 168 * @hide 169 */ 170 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 171 @Nullable 172 public TextClassificationSessionId mHiddenTempSessionId; 173 TextClassifierEvent(Builder builder)174 private TextClassifierEvent(Builder builder) { 175 mEventCategory = builder.mEventCategory; 176 mEventType = builder.mEventType; 177 mEntityTypes = builder.mEntityTypes; 178 mEventContext = builder.mEventContext; 179 mResultId = builder.mResultId; 180 mEventIndex = builder.mEventIndex; 181 mScores = builder.mScores; 182 mModelName = builder.mModelName; 183 mActionIndices = builder.mActionIndices; 184 mLocale = builder.mLocale; 185 mExtras = builder.mExtras == null ? Bundle.EMPTY : builder.mExtras; 186 } 187 TextClassifierEvent(Parcel in)188 private TextClassifierEvent(Parcel in) { 189 mEventCategory = in.readInt(); 190 mEventType = in.readInt(); 191 mEntityTypes = in.readStringArray(); 192 mEventContext = in.readParcelable(null, android.view.textclassifier.TextClassificationContext.class); 193 mResultId = in.readString(); 194 mEventIndex = in.readInt(); 195 int scoresLength = in.readInt(); 196 mScores = new float[scoresLength]; 197 in.readFloatArray(mScores); 198 mModelName = in.readString(); 199 mActionIndices = in.createIntArray(); 200 final String languageTag = in.readString(); 201 mLocale = languageTag == null ? null : ULocale.forLanguageTag(languageTag); 202 mExtras = in.readBundle(); 203 } 204 205 @Override describeContents()206 public int describeContents() { 207 return 0; 208 } 209 210 @NonNull 211 public static final Creator<TextClassifierEvent> CREATOR = new Creator<TextClassifierEvent>() { 212 @Override 213 public TextClassifierEvent createFromParcel(Parcel in) { 214 int token = in.readInt(); 215 if (token == PARCEL_TOKEN_TEXT_SELECTION_EVENT) { 216 return new TextSelectionEvent(in); 217 } 218 if (token == PARCEL_TOKEN_TEXT_LINKIFY_EVENT) { 219 return new TextLinkifyEvent(in); 220 } 221 if (token == PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT) { 222 return new LanguageDetectionEvent(in); 223 } 224 if (token == PARCEL_TOKEN_CONVERSATION_ACTION_EVENT) { 225 return new ConversationActionsEvent(in); 226 } 227 throw new IllegalStateException("Unexpected input event type token in parcel."); 228 } 229 230 @Override 231 public TextClassifierEvent[] newArray(int size) { 232 return new TextClassifierEvent[size]; 233 } 234 }; 235 236 @Override writeToParcel(Parcel dest, int flags)237 public void writeToParcel(Parcel dest, int flags) { 238 dest.writeInt(getParcelToken()); 239 dest.writeInt(mEventCategory); 240 dest.writeInt(mEventType); 241 dest.writeStringArray(mEntityTypes); 242 dest.writeParcelable(mEventContext, flags); 243 dest.writeString(mResultId); 244 dest.writeInt(mEventIndex); 245 dest.writeInt(mScores.length); 246 dest.writeFloatArray(mScores); 247 dest.writeString(mModelName); 248 dest.writeIntArray(mActionIndices); 249 dest.writeString(mLocale == null ? null : mLocale.toLanguageTag()); 250 dest.writeBundle(mExtras); 251 } 252 getParcelToken()253 private int getParcelToken() { 254 if (this instanceof TextSelectionEvent) { 255 return PARCEL_TOKEN_TEXT_SELECTION_EVENT; 256 } 257 if (this instanceof TextLinkifyEvent) { 258 return PARCEL_TOKEN_TEXT_LINKIFY_EVENT; 259 } 260 if (this instanceof LanguageDetectionEvent) { 261 return PARCEL_TOKEN_LANGUAGE_DETECTION_EVENT; 262 } 263 if (this instanceof ConversationActionsEvent) { 264 return PARCEL_TOKEN_CONVERSATION_ACTION_EVENT; 265 } 266 throw new IllegalArgumentException("Unexpected type: " + this.getClass().getSimpleName()); 267 } 268 269 /** 270 * Returns the event category. e.g. {@link #CATEGORY_SELECTION}. 271 */ 272 @Category getEventCategory()273 public int getEventCategory() { 274 return mEventCategory; 275 } 276 277 /** 278 * Returns the event type. e.g. {@link #TYPE_SELECTION_STARTED}. 279 */ 280 @Type getEventType()281 public int getEventType() { 282 return mEventType; 283 } 284 285 /** 286 * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}. 287 * 288 * @see Builder#setEntityTypes(String...) for supported types. 289 */ 290 @NonNull getEntityTypes()291 public String[] getEntityTypes() { 292 return mEntityTypes; 293 } 294 295 /** 296 * Returns the event context. 297 */ 298 @Nullable getEventContext()299 public TextClassificationContext getEventContext() { 300 return mEventContext; 301 } 302 303 /** 304 * Sets the event context. 305 * <p> 306 * Package-private for SystemTextClassifier's use. 307 */ setEventContext(@ullable TextClassificationContext eventContext)308 void setEventContext(@Nullable TextClassificationContext eventContext) { 309 mEventContext = eventContext; 310 } 311 312 /** 313 * Returns the id of the text classifier result related to this event. 314 */ 315 @Nullable getResultId()316 public String getResultId() { 317 return mResultId; 318 } 319 320 /** 321 * Returns the index of this event in the series of event it belongs to. 322 */ getEventIndex()323 public int getEventIndex() { 324 return mEventIndex; 325 } 326 327 /** 328 * Returns the scores of the suggestions. 329 */ 330 @NonNull getScores()331 public float[] getScores() { 332 return mScores; 333 } 334 335 /** 336 * Returns the model name. 337 */ 338 @Nullable getModelName()339 public String getModelName() { 340 return mModelName; 341 } 342 343 /** 344 * Returns the indices of the actions relating to this event. 345 * Actions are usually returned by the text classifier in priority order with the most 346 * preferred action at index 0. This list gives an indication of the position of the actions 347 * that are being reported. 348 * 349 * @see Builder#setActionIndices(int...) 350 */ 351 @NonNull getActionIndices()352 public int[] getActionIndices() { 353 return mActionIndices; 354 } 355 356 /** 357 * Returns the detected locale. 358 */ 359 @Nullable getLocale()360 public ULocale getLocale() { 361 return mLocale; 362 } 363 364 /** 365 * Returns a bundle containing non-structured extra information about this event. 366 * 367 * <p><b>NOTE: </b>Do not modify this bundle. 368 */ 369 @NonNull getExtras()370 public Bundle getExtras() { 371 return mExtras; 372 } 373 374 @Override toString()375 public String toString() { 376 StringBuilder out = new StringBuilder(128); 377 out.append(this.getClass().getSimpleName()); 378 out.append("{"); 379 out.append("mEventCategory=").append(mEventCategory); 380 out.append(", mEventType=").append(mEventType); 381 out.append(", mEntityTypes=").append(Arrays.toString(mEntityTypes)); 382 out.append(", mEventContext=").append(mEventContext); 383 out.append(", mResultId=").append(mResultId); 384 out.append(", mEventIndex=").append(mEventIndex); 385 out.append(", mExtras=").append(mExtras); 386 out.append(", mScores=").append(Arrays.toString(mScores)); 387 out.append(", mModelName=").append(mModelName); 388 out.append(", mActionIndices=").append(Arrays.toString(mActionIndices)); 389 toString(out); 390 out.append("}"); 391 return out.toString(); 392 } 393 394 /** 395 * Overrides this to append extra fields to the output of {@link #toString()}. 396 * <p> 397 * Extra fields should be formatted like this: ", {field_name}={field_value}". 398 */ toString(StringBuilder out)399 void toString(StringBuilder out) {} 400 401 /** 402 * Returns a {@link SelectionEvent} equivalent of this event; or {@code null} if it can not be 403 * converted to a {@link SelectionEvent}. 404 * @hide 405 */ 406 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 407 @Nullable toSelectionEvent()408 public final SelectionEvent toSelectionEvent() { 409 final int invocationMethod; 410 switch (getEventCategory()) { 411 case TextClassifierEvent.CATEGORY_SELECTION: 412 invocationMethod = SelectionEvent.INVOCATION_MANUAL; 413 break; 414 case TextClassifierEvent.CATEGORY_LINKIFY: 415 invocationMethod = SelectionEvent.INVOCATION_LINK; 416 break; 417 default: 418 // Cannot be converted to a SelectionEvent. 419 return null; 420 } 421 422 final String entityType = getEntityTypes().length > 0 423 ? getEntityTypes()[0] : TextClassifier.TYPE_UNKNOWN; 424 final SelectionEvent out = new SelectionEvent( 425 /* absoluteStart= */ 0, 426 /* absoluteEnd= */ 0, 427 /* eventType= */0, 428 entityType, 429 SelectionEvent.INVOCATION_UNKNOWN, 430 SelectionEvent.NO_SIGNATURE); 431 out.setInvocationMethod(invocationMethod); 432 433 final TextClassificationContext eventContext = getEventContext(); 434 if (eventContext != null) { 435 out.setTextClassificationSessionContext(getEventContext()); 436 } 437 out.setSessionId(mHiddenTempSessionId); 438 final String resultId = getResultId(); 439 out.setResultId(resultId == null ? SelectionEvent.NO_SIGNATURE : resultId); 440 out.setEventIndex(getEventIndex()); 441 442 443 final int eventType; 444 switch (getEventType()) { 445 case TextClassifierEvent.TYPE_SELECTION_STARTED: 446 eventType = SelectionEvent.EVENT_SELECTION_STARTED; 447 break; 448 case TextClassifierEvent.TYPE_SELECTION_MODIFIED: 449 eventType = SelectionEvent.EVENT_SELECTION_MODIFIED; 450 break; 451 case TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE: 452 eventType = SelectionEvent.EVENT_SMART_SELECTION_SINGLE; 453 break; 454 case TextClassifierEvent.TYPE_SMART_SELECTION_MULTI: 455 eventType = SelectionEvent.EVENT_SMART_SELECTION_MULTI; 456 break; 457 case TextClassifierEvent.TYPE_AUTO_SELECTION: 458 eventType = SelectionEvent.EVENT_AUTO_SELECTION; 459 break; 460 case TextClassifierEvent.TYPE_OVERTYPE: 461 eventType = SelectionEvent.ACTION_OVERTYPE; 462 break; 463 case TextClassifierEvent.TYPE_COPY_ACTION: 464 eventType = SelectionEvent.ACTION_COPY; 465 break; 466 case TextClassifierEvent.TYPE_PASTE_ACTION: 467 eventType = SelectionEvent.ACTION_PASTE; 468 break; 469 case TextClassifierEvent.TYPE_CUT_ACTION: 470 eventType = SelectionEvent.ACTION_CUT; 471 break; 472 case TextClassifierEvent.TYPE_SHARE_ACTION: 473 eventType = SelectionEvent.ACTION_SHARE; 474 break; 475 case TextClassifierEvent.TYPE_SMART_ACTION: 476 eventType = SelectionEvent.ACTION_SMART_SHARE; 477 break; 478 case TextClassifierEvent.TYPE_SELECTION_DRAG: 479 eventType = SelectionEvent.ACTION_DRAG; 480 break; 481 case TextClassifierEvent.TYPE_SELECTION_DESTROYED: 482 eventType = SelectionEvent.ACTION_ABANDON; 483 break; 484 case TextClassifierEvent.TYPE_OTHER_ACTION: 485 eventType = SelectionEvent.ACTION_OTHER; 486 break; 487 case TextClassifierEvent.TYPE_SELECT_ALL: 488 eventType = SelectionEvent.ACTION_SELECT_ALL; 489 break; 490 case TextClassifierEvent.TYPE_SELECTION_RESET: 491 eventType = SelectionEvent.ACTION_RESET; 492 break; 493 default: 494 eventType = 0; 495 break; 496 } 497 out.setEventType(eventType); 498 499 if (this instanceof TextClassifierEvent.TextSelectionEvent) { 500 final TextClassifierEvent.TextSelectionEvent selEvent = 501 (TextClassifierEvent.TextSelectionEvent) this; 502 // TODO: Ideally, we should have these fields in events of type 503 // TextClassifierEvent.TextLinkifyEvent events too but we're now past the API deadline 504 // and will have to do with these fields being set only in TextSelectionEvent events. 505 // Fix this at the next API bump. 506 out.setStart(selEvent.getRelativeWordStartIndex()); 507 out.setEnd(selEvent.getRelativeWordEndIndex()); 508 out.setSmartStart(selEvent.getRelativeSuggestedWordStartIndex()); 509 out.setSmartEnd(selEvent.getRelativeSuggestedWordEndIndex()); 510 } 511 512 return out; 513 } 514 515 /** 516 * Builder to build a text classifier event. 517 * 518 * @param <T> The subclass to be built. 519 */ 520 public abstract static class Builder<T extends Builder<T>> { 521 522 private final int mEventCategory; 523 private final int mEventType; 524 private String[] mEntityTypes = new String[0]; 525 @Nullable 526 private TextClassificationContext mEventContext; 527 @Nullable 528 private String mResultId; 529 private int mEventIndex; 530 private float[] mScores = new float[0]; 531 @Nullable 532 private String mModelName; 533 private int[] mActionIndices = new int[0]; 534 @Nullable 535 private ULocale mLocale; 536 @Nullable 537 private Bundle mExtras; 538 539 /** 540 * Creates a builder for building {@link TextClassifierEvent}s. 541 * 542 * @param eventCategory The event category. e.g. {@link #CATEGORY_SELECTION} 543 * @param eventType The event type. e.g. {@link #TYPE_SELECTION_STARTED} 544 */ Builder(@ategory int eventCategory, @Type int eventType)545 private Builder(@Category int eventCategory, @Type int eventType) { 546 mEventCategory = eventCategory; 547 mEventType = eventType; 548 } 549 550 /** 551 * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}. 552 * <p> 553 * Supported types: 554 * <p>See {@link TextClassifier.EntityType} 555 * <p>See {@link ConversationAction.ActionType} 556 * <p>See {@link ULocale#toLanguageTag()} 557 */ 558 @NonNull setEntityTypes(@onNull String... entityTypes)559 public T setEntityTypes(@NonNull String... entityTypes) { 560 Objects.requireNonNull(entityTypes); 561 mEntityTypes = new String[entityTypes.length]; 562 System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length); 563 return self(); 564 } 565 566 /** 567 * Sets the event context. 568 */ 569 @NonNull setEventContext(@ullable TextClassificationContext eventContext)570 public T setEventContext(@Nullable TextClassificationContext eventContext) { 571 mEventContext = eventContext; 572 return self(); 573 } 574 575 /** 576 * Sets the id of the text classifier result related to this event. 577 */ 578 @NonNull setResultId(@ullable String resultId)579 public T setResultId(@Nullable String resultId) { 580 mResultId = resultId; 581 return self(); 582 } 583 584 /** 585 * Sets the index of this event in the series of events it belongs to. 586 */ 587 @NonNull setEventIndex(int eventIndex)588 public T setEventIndex(int eventIndex) { 589 mEventIndex = eventIndex; 590 return self(); 591 } 592 593 /** 594 * Sets the scores of the suggestions. 595 */ 596 @NonNull setScores(@onNull float... scores)597 public T setScores(@NonNull float... scores) { 598 Objects.requireNonNull(scores); 599 mScores = new float[scores.length]; 600 System.arraycopy(scores, 0, mScores, 0, scores.length); 601 return self(); 602 } 603 604 /** 605 * Sets the model name string. 606 */ 607 @NonNull setModelName(@ullable String modelVersion)608 public T setModelName(@Nullable String modelVersion) { 609 mModelName = modelVersion; 610 return self(); 611 } 612 613 /** 614 * Sets the indices of the actions involved in this event. Actions are usually returned by 615 * the text classifier in priority order with the most preferred action at index 0. 616 * These indices give an indication of the position of the actions that are being reported. 617 * <p> 618 * E.g. 619 * <pre> 620 * // 3 smart actions are shown at index 0, 1, 2 respectively in response to a link click. 621 * new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_ACTIONS_SHOWN) 622 * .setEventIndex(0, 1, 2) 623 * ... 624 * .build(); 625 * 626 * ... 627 * 628 * // Smart action at index 1 is activated. 629 * new TextClassifierEvent.Builder(CATEGORY_LINKIFY, TYPE_SMART_ACTION) 630 * .setEventIndex(1) 631 * ... 632 * .build(); 633 * </pre> 634 * 635 * @see TextClassification#getActions() 636 */ 637 @NonNull setActionIndices(@onNull int... actionIndices)638 public T setActionIndices(@NonNull int... actionIndices) { 639 mActionIndices = new int[actionIndices.length]; 640 System.arraycopy(actionIndices, 0, mActionIndices, 0, actionIndices.length); 641 return self(); 642 } 643 644 /** 645 * Sets the detected locale. 646 */ 647 @NonNull setLocale(@ullable ULocale locale)648 public T setLocale(@Nullable ULocale locale) { 649 mLocale = locale; 650 return self(); 651 } 652 653 /** 654 * Sets a bundle containing non-structured extra information about the event. 655 * 656 * <p><b>NOTE: </b>Prefer to set only immutable values on the bundle otherwise, avoid 657 * updating the internals of this bundle as it may have unexpected consequences on the 658 * clients of the built event object. For similar reasons, avoid depending on mutable 659 * objects in this bundle. 660 */ 661 @NonNull setExtras(@onNull Bundle extras)662 public T setExtras(@NonNull Bundle extras) { 663 mExtras = Objects.requireNonNull(extras); 664 return self(); 665 } 666 self()667 abstract T self(); 668 } 669 670 /** 671 * This class represents events that are related to the smart text selection feature. 672 * <p> 673 * <pre> 674 * // User started a selection. e.g. "York" in text "New York City, NY". 675 * new TextSelectionEvent.Builder(TYPE_SELECTION_STARTED) 676 * .setEventContext(classificationContext) 677 * .setEventIndex(0) 678 * .build(); 679 * 680 * // System smart-selects a recognized entity. e.g. "New York City". 681 * new TextSelectionEvent.Builder(TYPE_SMART_SELECTION_MULTI) 682 * .setEventContext(classificationContext) 683 * .setResultId(textSelection.getId()) 684 * .setRelativeWordStartIndex(-1) // Goes back one word to "New" from "York". 685 * .setRelativeWordEndIndex(2) // Goes forward 2 words from "York" to start of ",". 686 * .setEntityTypes(textClassification.getEntity(0)) 687 * .setScore(textClassification.getConfidenceScore(entityType)) 688 * .setEventIndex(1) 689 * .build(); 690 * 691 * // User resets the selection to the original selection. i.e. "York". 692 * new TextSelectionEvent.Builder(TYPE_SELECTION_RESET) 693 * .setEventContext(classificationContext) 694 * .setResultId(textSelection.getId()) 695 * .setRelativeSuggestedWordStartIndex(-1) // Repeated from above. 696 * .setRelativeSuggestedWordEndIndex(2) // Repeated from above. 697 * .setRelativeWordStartIndex(0) // Original selection is always at (0, 1]. 698 * .setRelativeWordEndIndex(1) 699 * .setEntityTypes(textClassification.getEntity(0)) 700 * .setScore(textClassification.getConfidenceScore(entityType)) 701 * .setEventIndex(2) 702 * .build(); 703 * 704 * // User modified the selection. e.g. "New". 705 * new TextSelectionEvent.Builder(TYPE_SELECTION_MODIFIED) 706 * .setEventContext(classificationContext) 707 * .setResultId(textSelection.getId()) 708 * .setRelativeSuggestedWordStartIndex(-1) // Repeated from above. 709 * .setRelativeSuggestedWordEndIndex(2) // Repeated from above. 710 * .setRelativeWordStartIndex(-1) // Goes backward one word from "York" to 711 * "New". 712 * .setRelativeWordEndIndex(0) // Goes backward one word to exclude "York". 713 * .setEntityTypes(textClassification.getEntity(0)) 714 * .setScore(textClassification.getConfidenceScore(entityType)) 715 * .setEventIndex(3) 716 * .build(); 717 * 718 * // Smart (contextual) actions (at indices, 0, 1, 2) presented to the user. 719 * // e.g. "Map", "Ride share", "Explore". 720 * new TextSelectionEvent.Builder(TYPE_ACTIONS_SHOWN) 721 * .setEventContext(classificationContext) 722 * .setResultId(textClassification.getId()) 723 * .setEntityTypes(textClassification.getEntity(0)) 724 * .setScore(textClassification.getConfidenceScore(entityType)) 725 * .setActionIndices(0, 1, 2) 726 * .setEventIndex(4) 727 * .build(); 728 * 729 * // User chooses the "Copy" action. 730 * new TextSelectionEvent.Builder(TYPE_COPY_ACTION) 731 * .setEventContext(classificationContext) 732 * .setResultId(textClassification.getId()) 733 * .setEntityTypes(textClassification.getEntity(0)) 734 * .setScore(textClassification.getConfidenceScore(entityType)) 735 * .setEventIndex(5) 736 * .build(); 737 * 738 * // User chooses smart action at index 1. i.e. "Ride share". 739 * new TextSelectionEvent.Builder(TYPE_SMART_ACTION) 740 * .setEventContext(classificationContext) 741 * .setResultId(textClassification.getId()) 742 * .setEntityTypes(textClassification.getEntity(0)) 743 * .setScore(textClassification.getConfidenceScore(entityType)) 744 * .setActionIndices(1) 745 * .setEventIndex(5) 746 * .build(); 747 * 748 * // Selection dismissed. 749 * new TextSelectionEvent.Builder(TYPE_SELECTION_DESTROYED) 750 * .setEventContext(classificationContext) 751 * .setResultId(textClassification.getId()) 752 * .setEntityTypes(textClassification.getEntity(0)) 753 * .setScore(textClassification.getConfidenceScore(entityType)) 754 * .setEventIndex(6) 755 * .build(); 756 * </pre> 757 * <p> 758 */ 759 public static final class TextSelectionEvent extends TextClassifierEvent implements Parcelable { 760 761 @NonNull 762 public static final Creator<TextSelectionEvent> CREATOR = 763 new Creator<TextSelectionEvent>() { 764 @Override 765 public TextSelectionEvent createFromParcel(Parcel in) { 766 in.readInt(); // skip token, we already know this is a TextSelectionEvent 767 return new TextSelectionEvent(in); 768 } 769 770 @Override 771 public TextSelectionEvent[] newArray(int size) { 772 return new TextSelectionEvent[size]; 773 } 774 }; 775 776 final int mRelativeWordStartIndex; 777 final int mRelativeWordEndIndex; 778 final int mRelativeSuggestedWordStartIndex; 779 final int mRelativeSuggestedWordEndIndex; 780 TextSelectionEvent(TextSelectionEvent.Builder builder)781 private TextSelectionEvent(TextSelectionEvent.Builder builder) { 782 super(builder); 783 mRelativeWordStartIndex = builder.mRelativeWordStartIndex; 784 mRelativeWordEndIndex = builder.mRelativeWordEndIndex; 785 mRelativeSuggestedWordStartIndex = builder.mRelativeSuggestedWordStartIndex; 786 mRelativeSuggestedWordEndIndex = builder.mRelativeSuggestedWordEndIndex; 787 } 788 TextSelectionEvent(Parcel in)789 private TextSelectionEvent(Parcel in) { 790 super(in); 791 mRelativeWordStartIndex = in.readInt(); 792 mRelativeWordEndIndex = in.readInt(); 793 mRelativeSuggestedWordStartIndex = in.readInt(); 794 mRelativeSuggestedWordEndIndex = in.readInt(); 795 } 796 797 @Override writeToParcel(Parcel dest, int flags)798 public void writeToParcel(Parcel dest, int flags) { 799 super.writeToParcel(dest, flags); 800 dest.writeInt(mRelativeWordStartIndex); 801 dest.writeInt(mRelativeWordEndIndex); 802 dest.writeInt(mRelativeSuggestedWordStartIndex); 803 dest.writeInt(mRelativeSuggestedWordEndIndex); 804 } 805 806 /** 807 * Returns the relative word index of the start of the selection. 808 */ getRelativeWordStartIndex()809 public int getRelativeWordStartIndex() { 810 return mRelativeWordStartIndex; 811 } 812 813 /** 814 * Returns the relative word (exclusive) index of the end of the selection. 815 */ getRelativeWordEndIndex()816 public int getRelativeWordEndIndex() { 817 return mRelativeWordEndIndex; 818 } 819 820 /** 821 * Returns the relative word index of the start of the smart selection. 822 */ getRelativeSuggestedWordStartIndex()823 public int getRelativeSuggestedWordStartIndex() { 824 return mRelativeSuggestedWordStartIndex; 825 } 826 827 /** 828 * Returns the relative word (exclusive) index of the end of the 829 * smart selection. 830 */ getRelativeSuggestedWordEndIndex()831 public int getRelativeSuggestedWordEndIndex() { 832 return mRelativeSuggestedWordEndIndex; 833 } 834 835 @Override toString(StringBuilder out)836 void toString(StringBuilder out) { 837 out.append(", getRelativeWordStartIndex=").append(mRelativeWordStartIndex); 838 out.append(", getRelativeWordEndIndex=").append(mRelativeWordEndIndex); 839 out.append(", getRelativeSuggestedWordStartIndex=") 840 .append(mRelativeSuggestedWordStartIndex); 841 out.append(", getRelativeSuggestedWordEndIndex=") 842 .append(mRelativeSuggestedWordEndIndex); 843 } 844 845 /** 846 * Builder class for {@link TextSelectionEvent}. 847 */ 848 public static final class Builder extends 849 TextClassifierEvent.Builder<TextSelectionEvent.Builder> { 850 int mRelativeWordStartIndex; 851 int mRelativeWordEndIndex; 852 int mRelativeSuggestedWordStartIndex; 853 int mRelativeSuggestedWordEndIndex; 854 855 /** 856 * Creates a builder for building {@link TextSelectionEvent}s. 857 * 858 * @param eventType The event type. e.g. {@link #TYPE_SELECTION_STARTED} 859 */ Builder(@ype int eventType)860 public Builder(@Type int eventType) { 861 super(CATEGORY_SELECTION, eventType); 862 } 863 864 /** 865 * Sets the relative word index of the start of the selection. 866 */ 867 @NonNull setRelativeWordStartIndex(int relativeWordStartIndex)868 public Builder setRelativeWordStartIndex(int relativeWordStartIndex) { 869 mRelativeWordStartIndex = relativeWordStartIndex; 870 return this; 871 } 872 873 /** 874 * Sets the relative word (exclusive) index of the end of the 875 * selection. 876 */ 877 @NonNull setRelativeWordEndIndex(int relativeWordEndIndex)878 public Builder setRelativeWordEndIndex(int relativeWordEndIndex) { 879 mRelativeWordEndIndex = relativeWordEndIndex; 880 return this; 881 } 882 883 /** 884 * Sets the relative word index of the start of the smart 885 * selection. 886 */ 887 @NonNull setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex)888 public Builder setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex) { 889 mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex; 890 return this; 891 } 892 893 /** 894 * Sets the relative word (exclusive) index of the end of the 895 * smart selection. 896 */ 897 @NonNull setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex)898 public Builder setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex) { 899 mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex; 900 return this; 901 } 902 903 @Override self()904 TextSelectionEvent.Builder self() { 905 return this; 906 } 907 908 /** 909 * Builds and returns a {@link TextSelectionEvent}. 910 */ 911 @NonNull build()912 public TextSelectionEvent build() { 913 return new TextSelectionEvent(this); 914 } 915 } 916 } 917 918 /** 919 * This class represents events that are related to the smart linkify feature. 920 * <p> 921 * <pre> 922 * // User clicked on a link. 923 * new TextLinkifyEvent.Builder(TYPE_LINK_CLICKED) 924 * .setEventContext(classificationContext) 925 * .setResultId(textClassification.getId()) 926 * .setEntityTypes(textClassification.getEntity(0)) 927 * .setScore(textClassification.getConfidenceScore(entityType)) 928 * .setEventIndex(0) 929 * .build(); 930 * 931 * // Smart (contextual) actions presented to the user in response to a link click. 932 * new TextLinkifyEvent.Builder(TYPE_ACTIONS_SHOWN) 933 * .setEventContext(classificationContext) 934 * .setResultId(textClassification.getId()) 935 * .setEntityTypes(textClassification.getEntity(0)) 936 * .setScore(textClassification.getConfidenceScore(entityType)) 937 * .setActionIndices(range(textClassification.getActions().size())) 938 * .setEventIndex(1) 939 * .build(); 940 * 941 * // User chooses smart action at index 0. 942 * new TextLinkifyEvent.Builder(TYPE_SMART_ACTION) 943 * .setEventContext(classificationContext) 944 * .setResultId(textClassification.getId()) 945 * .setEntityTypes(textClassification.getEntity(0)) 946 * .setScore(textClassification.getConfidenceScore(entityType)) 947 * .setActionIndices(0) 948 * .setEventIndex(2) 949 * .build(); 950 * </pre> 951 */ 952 public static final class TextLinkifyEvent extends TextClassifierEvent implements Parcelable { 953 954 @NonNull 955 public static final Creator<TextLinkifyEvent> CREATOR = 956 new Creator<TextLinkifyEvent>() { 957 @Override 958 public TextLinkifyEvent createFromParcel(Parcel in) { 959 in.readInt(); // skip token, we already know this is a TextLinkifyEvent 960 return new TextLinkifyEvent(in); 961 } 962 963 @Override 964 public TextLinkifyEvent[] newArray(int size) { 965 return new TextLinkifyEvent[size]; 966 } 967 }; 968 TextLinkifyEvent(Parcel in)969 private TextLinkifyEvent(Parcel in) { 970 super(in); 971 } 972 TextLinkifyEvent(TextLinkifyEvent.Builder builder)973 private TextLinkifyEvent(TextLinkifyEvent.Builder builder) { 974 super(builder); 975 } 976 977 /** 978 * Builder class for {@link TextLinkifyEvent}. 979 */ 980 public static final class Builder 981 extends TextClassifierEvent.Builder<TextLinkifyEvent.Builder> { 982 /** 983 * Creates a builder for building {@link TextLinkifyEvent}s. 984 * 985 * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION} 986 */ Builder(@ype int eventType)987 public Builder(@Type int eventType) { 988 super(TextClassifierEvent.CATEGORY_LINKIFY, eventType); 989 } 990 991 @Override self()992 Builder self() { 993 return this; 994 } 995 996 /** 997 * Builds and returns a {@link TextLinkifyEvent}. 998 */ 999 @NonNull build()1000 public TextLinkifyEvent build() { 1001 return new TextLinkifyEvent(this); 1002 } 1003 } 1004 } 1005 1006 /** 1007 * This class represents events that are related to the language detection feature. 1008 * <p> 1009 * <pre> 1010 * // Translate action shown for foreign text. 1011 * new LanguageDetectionEvent.Builder(TYPE_ACTIONS_SHOWN) 1012 * .setEventContext(classificationContext) 1013 * .setResultId(textClassification.getId()) 1014 * .setEntityTypes(language) 1015 * .setScore(score) 1016 * .setActionIndices(textClassification.getActions().indexOf(translateAction)) 1017 * .setEventIndex(0) 1018 * .build(); 1019 * 1020 * // Translate action selected. 1021 * new LanguageDetectionEvent.Builder(TYPE_SMART_ACTION) 1022 * .setEventContext(classificationContext) 1023 * .setResultId(textClassification.getId()) 1024 * .setEntityTypes(language) 1025 * .setScore(score) 1026 * .setActionIndices(textClassification.getActions().indexOf(translateAction)) 1027 * .setEventIndex(1) 1028 * .build(); 1029 */ 1030 public static final class LanguageDetectionEvent extends TextClassifierEvent 1031 implements Parcelable { 1032 1033 @NonNull 1034 public static final Creator<LanguageDetectionEvent> CREATOR = 1035 new Creator<LanguageDetectionEvent>() { 1036 @Override 1037 public LanguageDetectionEvent createFromParcel(Parcel in) { 1038 // skip token, we already know this is a LanguageDetectionEvent. 1039 in.readInt(); 1040 return new LanguageDetectionEvent(in); 1041 } 1042 1043 @Override 1044 public LanguageDetectionEvent[] newArray(int size) { 1045 return new LanguageDetectionEvent[size]; 1046 } 1047 }; 1048 LanguageDetectionEvent(Parcel in)1049 private LanguageDetectionEvent(Parcel in) { 1050 super(in); 1051 } 1052 LanguageDetectionEvent(LanguageDetectionEvent.Builder builder)1053 private LanguageDetectionEvent(LanguageDetectionEvent.Builder builder) { 1054 super(builder); 1055 } 1056 1057 /** 1058 * Builder class for {@link LanguageDetectionEvent}. 1059 */ 1060 public static final class Builder 1061 extends TextClassifierEvent.Builder<LanguageDetectionEvent.Builder> { 1062 1063 /** 1064 * Creates a builder for building {@link TextSelectionEvent}s. 1065 * 1066 * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION} 1067 */ Builder(@ype int eventType)1068 public Builder(@Type int eventType) { 1069 super(TextClassifierEvent.CATEGORY_LANGUAGE_DETECTION, eventType); 1070 } 1071 1072 @Override self()1073 Builder self() { 1074 return this; 1075 } 1076 1077 /** 1078 * Builds and returns a {@link LanguageDetectionEvent}. 1079 */ 1080 @NonNull build()1081 public LanguageDetectionEvent build() { 1082 return new LanguageDetectionEvent(this); 1083 } 1084 } 1085 } 1086 1087 /** 1088 * This class represents events that are related to the conversation actions feature. 1089 * <p> 1090 * <pre> 1091 * // Conversation (contextual) actions/replies generated. 1092 * new ConversationActionsEvent.Builder(TYPE_ACTIONS_GENERATED) 1093 * .setEventContext(classificationContext) 1094 * .setResultId(conversationActions.getId()) 1095 * .setEntityTypes(getTypes(conversationActions)) 1096 * .setActionIndices(range(conversationActions.getActions().size())) 1097 * .setEventIndex(0) 1098 * .build(); 1099 * 1100 * // Conversation actions/replies presented to user. 1101 * new ConversationActionsEvent.Builder(TYPE_ACTIONS_SHOWN) 1102 * .setEventContext(classificationContext) 1103 * .setResultId(conversationActions.getId()) 1104 * .setEntityTypes(getTypes(conversationActions)) 1105 * .setActionIndices(range(conversationActions.getActions().size())) 1106 * .setEventIndex(1) 1107 * .build(); 1108 * 1109 * // User clicked the "Reply" button to compose their custom reply. 1110 * new ConversationActionsEvent.Builder(TYPE_MANUAL_REPLY) 1111 * .setEventContext(classificationContext) 1112 * .setResultId(conversationActions.getId()) 1113 * .setEventIndex(2) 1114 * .build(); 1115 * 1116 * // User selected a smart (contextual) action/reply. 1117 * new ConversationActionsEvent.Builder(TYPE_SMART_ACTION) 1118 * .setEventContext(classificationContext) 1119 * .setResultId(conversationActions.getId()) 1120 * .setEntityTypes(conversationActions.get(1).getType()) 1121 * .setScore(conversationAction.get(1).getConfidenceScore()) 1122 * .setActionIndices(1) 1123 * .setEventIndex(2) 1124 * .build(); 1125 * </pre> 1126 */ 1127 public static final class ConversationActionsEvent extends TextClassifierEvent 1128 implements Parcelable { 1129 1130 @NonNull 1131 public static final Creator<ConversationActionsEvent> CREATOR = 1132 new Creator<ConversationActionsEvent>() { 1133 @Override 1134 public ConversationActionsEvent createFromParcel(Parcel in) { 1135 // skip token, we already know this is a ConversationActionsEvent. 1136 in.readInt(); 1137 return new ConversationActionsEvent(in); 1138 } 1139 1140 @Override 1141 public ConversationActionsEvent[] newArray(int size) { 1142 return new ConversationActionsEvent[size]; 1143 } 1144 }; 1145 ConversationActionsEvent(Parcel in)1146 private ConversationActionsEvent(Parcel in) { 1147 super(in); 1148 } 1149 ConversationActionsEvent(ConversationActionsEvent.Builder builder)1150 private ConversationActionsEvent(ConversationActionsEvent.Builder builder) { 1151 super(builder); 1152 } 1153 1154 /** 1155 * Builder class for {@link ConversationActionsEvent}. 1156 */ 1157 public static final class Builder 1158 extends TextClassifierEvent.Builder<ConversationActionsEvent.Builder> { 1159 /** 1160 * Creates a builder for building {@link TextSelectionEvent}s. 1161 * 1162 * @param eventType The event type. e.g. {@link #TYPE_SMART_ACTION} 1163 */ Builder(@ype int eventType)1164 public Builder(@Type int eventType) { 1165 super(TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, eventType); 1166 } 1167 1168 @Override self()1169 Builder self() { 1170 return this; 1171 } 1172 1173 /** 1174 * Builds and returns a {@link ConversationActionsEvent}. 1175 */ 1176 @NonNull build()1177 public ConversationActionsEvent build() { 1178 return new ConversationActionsEvent(this); 1179 } 1180 } 1181 } 1182 } 1183