1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view.textclassifier.logging; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.content.Context; 24 import android.metrics.LogMaker; 25 import android.util.Log; 26 import android.view.textclassifier.TextClassification; 27 import android.view.textclassifier.TextClassifier; 28 import android.view.textclassifier.TextSelection; 29 30 import com.android.internal.logging.MetricsLogger; 31 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 32 import com.android.internal.util.Preconditions; 33 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.util.Objects; 37 import java.util.UUID; 38 39 /** 40 * A selection event tracker. 41 * @hide 42 */ 43 //TODO: Do not allow any crashes from this class. 44 public final class SmartSelectionEventTracker { 45 46 private static final String LOG_TAG = "SmartSelectEventTracker"; 47 private static final boolean DEBUG_LOG_ENABLED = true; 48 49 private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; 50 private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; 51 private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX; 52 private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; 53 private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; 54 private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; 55 private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; 56 private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START; 57 private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END; 58 private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START; 59 private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END; 60 private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID; 61 62 private static final String ZERO = "0"; 63 private static final String TEXTVIEW = "textview"; 64 private static final String EDITTEXT = "edittext"; 65 private static final String UNSELECTABLE_TEXTVIEW = "nosel-textview"; 66 private static final String WEBVIEW = "webview"; 67 private static final String EDIT_WEBVIEW = "edit-webview"; 68 private static final String CUSTOM_TEXTVIEW = "customview"; 69 private static final String CUSTOM_EDITTEXT = "customedit"; 70 private static final String CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; 71 private static final String UNKNOWN = "unknown"; 72 73 @Retention(RetentionPolicy.SOURCE) 74 @IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW, 75 WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW}) 76 public @interface WidgetType { 77 int UNSPECIFIED = 0; 78 int TEXTVIEW = 1; 79 int WEBVIEW = 2; 80 int EDITTEXT = 3; 81 int EDIT_WEBVIEW = 4; 82 int UNSELECTABLE_TEXTVIEW = 5; 83 int CUSTOM_TEXTVIEW = 6; 84 int CUSTOM_EDITTEXT = 7; 85 int CUSTOM_UNSELECTABLE_TEXTVIEW = 8; 86 } 87 88 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 89 private final int mWidgetType; 90 @Nullable private final String mWidgetVersion; 91 private final Context mContext; 92 93 @Nullable private String mSessionId; 94 private final int[] mSmartIndices = new int[2]; 95 private final int[] mPrevIndices = new int[2]; 96 private int mOrigStart; 97 private int mIndex; 98 private long mSessionStartTime; 99 private long mLastEventTime; 100 private boolean mSmartSelectionTriggered; 101 private String mModelName; 102 103 @UnsupportedAppUsage SmartSelectionEventTracker(@onNull Context context, @WidgetType int widgetType)104 public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) { 105 mWidgetType = widgetType; 106 mWidgetVersion = null; 107 mContext = Preconditions.checkNotNull(context); 108 } 109 SmartSelectionEventTracker( @onNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion)110 public SmartSelectionEventTracker( 111 @NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) { 112 mWidgetType = widgetType; 113 mWidgetVersion = widgetVersion; 114 mContext = Preconditions.checkNotNull(context); 115 } 116 117 /** 118 * Logs a selection event. 119 * 120 * @param event the selection event 121 */ 122 @UnsupportedAppUsage logEvent(@onNull SelectionEvent event)123 public void logEvent(@NonNull SelectionEvent event) { 124 Preconditions.checkNotNull(event); 125 126 if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null 127 && DEBUG_LOG_ENABLED) { 128 Log.d(LOG_TAG, "Selection session not yet started. Ignoring event"); 129 return; 130 } 131 132 final long now = System.currentTimeMillis(); 133 switch (event.mEventType) { 134 case SelectionEvent.EventType.SELECTION_STARTED: 135 mSessionId = startNewSession(); 136 Preconditions.checkArgument(event.mEnd == event.mStart + 1); 137 mOrigStart = event.mStart; 138 mSessionStartTime = now; 139 break; 140 case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through 141 case SelectionEvent.EventType.SMART_SELECTION_MULTI: 142 mSmartSelectionTriggered = true; 143 mModelName = getModelName(event); 144 mSmartIndices[0] = event.mStart; 145 mSmartIndices[1] = event.mEnd; 146 break; 147 case SelectionEvent.EventType.SELECTION_MODIFIED: // fall through 148 case SelectionEvent.EventType.AUTO_SELECTION: 149 if (mPrevIndices[0] == event.mStart && mPrevIndices[1] == event.mEnd) { 150 // Selection did not change. Ignore event. 151 return; 152 } 153 } 154 writeEvent(event, now); 155 156 if (event.isTerminal()) { 157 endSession(); 158 } 159 } 160 writeEvent(SelectionEvent event, long now)161 private void writeEvent(SelectionEvent event, long now) { 162 final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime; 163 final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION) 164 .setType(getLogType(event)) 165 .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL) 166 .setPackageName(mContext.getPackageName()) 167 .addTaggedData(START_EVENT_DELTA, now - mSessionStartTime) 168 .addTaggedData(PREV_EVENT_DELTA, prevEventDelta) 169 .addTaggedData(INDEX, mIndex) 170 .addTaggedData(WIDGET_TYPE, getWidgetTypeName()) 171 .addTaggedData(WIDGET_VERSION, mWidgetVersion) 172 .addTaggedData(MODEL_NAME, mModelName) 173 .addTaggedData(ENTITY_TYPE, event.mEntityType) 174 .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0])) 175 .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1])) 176 .addTaggedData(EVENT_START, getRangeDelta(event.mStart)) 177 .addTaggedData(EVENT_END, getRangeDelta(event.mEnd)) 178 .addTaggedData(SESSION_ID, mSessionId); 179 mMetricsLogger.write(log); 180 debugLog(log); 181 mLastEventTime = now; 182 mPrevIndices[0] = event.mStart; 183 mPrevIndices[1] = event.mEnd; 184 mIndex++; 185 } 186 startNewSession()187 private String startNewSession() { 188 endSession(); 189 mSessionId = createSessionId(); 190 return mSessionId; 191 } 192 endSession()193 private void endSession() { 194 // Reset fields. 195 mOrigStart = 0; 196 mSmartIndices[0] = mSmartIndices[1] = 0; 197 mPrevIndices[0] = mPrevIndices[1] = 0; 198 mIndex = 0; 199 mSessionStartTime = 0; 200 mLastEventTime = 0; 201 mSmartSelectionTriggered = false; 202 mModelName = getModelName(null); 203 mSessionId = null; 204 } 205 getLogType(SelectionEvent event)206 private static int getLogType(SelectionEvent event) { 207 switch (event.mEventType) { 208 case SelectionEvent.ActionType.OVERTYPE: 209 return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE; 210 case SelectionEvent.ActionType.COPY: 211 return MetricsEvent.ACTION_TEXT_SELECTION_COPY; 212 case SelectionEvent.ActionType.PASTE: 213 return MetricsEvent.ACTION_TEXT_SELECTION_PASTE; 214 case SelectionEvent.ActionType.CUT: 215 return MetricsEvent.ACTION_TEXT_SELECTION_CUT; 216 case SelectionEvent.ActionType.SHARE: 217 return MetricsEvent.ACTION_TEXT_SELECTION_SHARE; 218 case SelectionEvent.ActionType.SMART_SHARE: 219 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; 220 case SelectionEvent.ActionType.DRAG: 221 return MetricsEvent.ACTION_TEXT_SELECTION_DRAG; 222 case SelectionEvent.ActionType.ABANDON: 223 return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON; 224 case SelectionEvent.ActionType.OTHER: 225 return MetricsEvent.ACTION_TEXT_SELECTION_OTHER; 226 case SelectionEvent.ActionType.SELECT_ALL: 227 return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL; 228 case SelectionEvent.ActionType.RESET: 229 return MetricsEvent.ACTION_TEXT_SELECTION_RESET; 230 case SelectionEvent.EventType.SELECTION_STARTED: 231 return MetricsEvent.ACTION_TEXT_SELECTION_START; 232 case SelectionEvent.EventType.SELECTION_MODIFIED: 233 return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY; 234 case SelectionEvent.EventType.SMART_SELECTION_SINGLE: 235 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE; 236 case SelectionEvent.EventType.SMART_SELECTION_MULTI: 237 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI; 238 case SelectionEvent.EventType.AUTO_SELECTION: 239 return MetricsEvent.ACTION_TEXT_SELECTION_AUTO; 240 default: 241 return MetricsEvent.VIEW_UNKNOWN; 242 } 243 } 244 getLogTypeString(int logType)245 private static String getLogTypeString(int logType) { 246 switch (logType) { 247 case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE: 248 return "OVERTYPE"; 249 case MetricsEvent.ACTION_TEXT_SELECTION_COPY: 250 return "COPY"; 251 case MetricsEvent.ACTION_TEXT_SELECTION_PASTE: 252 return "PASTE"; 253 case MetricsEvent.ACTION_TEXT_SELECTION_CUT: 254 return "CUT"; 255 case MetricsEvent.ACTION_TEXT_SELECTION_SHARE: 256 return "SHARE"; 257 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE: 258 return "SMART_SHARE"; 259 case MetricsEvent.ACTION_TEXT_SELECTION_DRAG: 260 return "DRAG"; 261 case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON: 262 return "ABANDON"; 263 case MetricsEvent.ACTION_TEXT_SELECTION_OTHER: 264 return "OTHER"; 265 case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL: 266 return "SELECT_ALL"; 267 case MetricsEvent.ACTION_TEXT_SELECTION_RESET: 268 return "RESET"; 269 case MetricsEvent.ACTION_TEXT_SELECTION_START: 270 return "SELECTION_STARTED"; 271 case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY: 272 return "SELECTION_MODIFIED"; 273 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE: 274 return "SMART_SELECTION_SINGLE"; 275 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI: 276 return "SMART_SELECTION_MULTI"; 277 case MetricsEvent.ACTION_TEXT_SELECTION_AUTO: 278 return "AUTO_SELECTION"; 279 default: 280 return UNKNOWN; 281 } 282 } 283 getRangeDelta(int offset)284 private int getRangeDelta(int offset) { 285 return offset - mOrigStart; 286 } 287 getSmartRangeDelta(int offset)288 private int getSmartRangeDelta(int offset) { 289 return mSmartSelectionTriggered ? getRangeDelta(offset) : 0; 290 } 291 getWidgetTypeName()292 private String getWidgetTypeName() { 293 switch (mWidgetType) { 294 case WidgetType.TEXTVIEW: 295 return TEXTVIEW; 296 case WidgetType.WEBVIEW: 297 return WEBVIEW; 298 case WidgetType.EDITTEXT: 299 return EDITTEXT; 300 case WidgetType.EDIT_WEBVIEW: 301 return EDIT_WEBVIEW; 302 case WidgetType.UNSELECTABLE_TEXTVIEW: 303 return UNSELECTABLE_TEXTVIEW; 304 case WidgetType.CUSTOM_TEXTVIEW: 305 return CUSTOM_TEXTVIEW; 306 case WidgetType.CUSTOM_EDITTEXT: 307 return CUSTOM_EDITTEXT; 308 case WidgetType.CUSTOM_UNSELECTABLE_TEXTVIEW: 309 return CUSTOM_UNSELECTABLE_TEXTVIEW; 310 default: 311 return UNKNOWN; 312 } 313 } 314 getModelName(@ullable SelectionEvent event)315 private String getModelName(@Nullable SelectionEvent event) { 316 return event == null 317 ? SelectionEvent.NO_VERSION_TAG 318 : Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG); 319 } 320 createSessionId()321 private static String createSessionId() { 322 return UUID.randomUUID().toString(); 323 } 324 debugLog(LogMaker log)325 private static void debugLog(LogMaker log) { 326 if (!DEBUG_LOG_ENABLED) return; 327 328 final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN); 329 final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), ""); 330 final String widget = widgetVersion.isEmpty() 331 ? widgetType : widgetType + "-" + widgetVersion; 332 final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO)); 333 if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) { 334 String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), ""); 335 sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1); 336 Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId)); 337 } 338 339 final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN); 340 final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN); 341 final String type = getLogTypeString(log.getType()); 342 final int smartStart = Integer.parseInt( 343 Objects.toString(log.getTaggedData(SMART_START), ZERO)); 344 final int smartEnd = Integer.parseInt( 345 Objects.toString(log.getTaggedData(SMART_END), ZERO)); 346 final int eventStart = Integer.parseInt( 347 Objects.toString(log.getTaggedData(EVENT_START), ZERO)); 348 final int eventEnd = Integer.parseInt( 349 Objects.toString(log.getTaggedData(EVENT_END), ZERO)); 350 351 Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)", 352 index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model)); 353 } 354 355 /** 356 * A selection event. 357 * Specify index parameters as word token indices. 358 */ 359 public static final class SelectionEvent { 360 361 /** 362 * Use this to specify an indeterminate positive index. 363 */ 364 public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE; 365 366 /** 367 * Use this to specify an indeterminate negative index. 368 */ 369 public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE; 370 371 private static final String NO_VERSION_TAG = ""; 372 373 @Retention(RetentionPolicy.SOURCE) 374 @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT, 375 ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON, 376 ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET}) 377 public @interface ActionType { 378 /** User typed over the selection. */ 379 int OVERTYPE = 100; 380 /** User copied the selection. */ 381 int COPY = 101; 382 /** User pasted over the selection. */ 383 int PASTE = 102; 384 /** User cut the selection. */ 385 int CUT = 103; 386 /** User shared the selection. */ 387 int SHARE = 104; 388 /** User clicked the textAssist menu item. */ 389 int SMART_SHARE = 105; 390 /** User dragged+dropped the selection. */ 391 int DRAG = 106; 392 /** User abandoned the selection. */ 393 int ABANDON = 107; 394 /** User performed an action on the selection. */ 395 int OTHER = 108; 396 397 /* Non-terminal actions. */ 398 /** User activated Select All */ 399 int SELECT_ALL = 200; 400 /** User reset the smart selection. */ 401 int RESET = 201; 402 } 403 404 @Retention(RetentionPolicy.SOURCE) 405 @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT, 406 ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON, 407 ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET, 408 EventType.SELECTION_STARTED, EventType.SELECTION_MODIFIED, 409 EventType.SMART_SELECTION_SINGLE, EventType.SMART_SELECTION_MULTI, 410 EventType.AUTO_SELECTION}) 411 private @interface EventType { 412 /** User started a new selection. */ 413 int SELECTION_STARTED = 1; 414 /** User modified an existing selection. */ 415 int SELECTION_MODIFIED = 2; 416 /** Smart selection triggered for a single token (word). */ 417 int SMART_SELECTION_SINGLE = 3; 418 /** Smart selection triggered spanning multiple tokens (words). */ 419 int SMART_SELECTION_MULTI = 4; 420 /** Something else other than User or the default TextClassifier triggered a selection. */ 421 int AUTO_SELECTION = 5; 422 } 423 424 private final int mStart; 425 private final int mEnd; 426 private @EventType int mEventType; 427 private final @TextClassifier.EntityType String mEntityType; 428 private final String mVersionTag; 429 SelectionEvent( int start, int end, int eventType, @TextClassifier.EntityType String entityType, String versionTag)430 private SelectionEvent( 431 int start, int end, int eventType, 432 @TextClassifier.EntityType String entityType, String versionTag) { 433 Preconditions.checkArgument(end >= start, "end cannot be less than start"); 434 mStart = start; 435 mEnd = end; 436 mEventType = eventType; 437 mEntityType = Preconditions.checkNotNull(entityType); 438 mVersionTag = Preconditions.checkNotNull(versionTag); 439 } 440 441 /** 442 * Creates a "selection started" event. 443 * 444 * @param start the word index of the selected word 445 */ 446 @UnsupportedAppUsage selectionStarted(int start)447 public static SelectionEvent selectionStarted(int start) { 448 return new SelectionEvent( 449 start, start + 1, EventType.SELECTION_STARTED, 450 TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG); 451 } 452 453 /** 454 * Creates a "selection modified" event. 455 * Use when the user modifies the selection. 456 * 457 * @param start the start word (inclusive) index of the selection 458 * @param end the end word (exclusive) index of the selection 459 */ 460 @UnsupportedAppUsage selectionModified(int start, int end)461 public static SelectionEvent selectionModified(int start, int end) { 462 return new SelectionEvent( 463 start, end, EventType.SELECTION_MODIFIED, 464 TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG); 465 } 466 467 /** 468 * Creates a "selection modified" event. 469 * Use when the user modifies the selection and the selection's entity type is known. 470 * 471 * @param start the start word (inclusive) index of the selection 472 * @param end the end word (exclusive) index of the selection 473 * @param classification the TextClassification object returned by the TextClassifier that 474 * classified the selected text 475 */ 476 @UnsupportedAppUsage selectionModified( int start, int end, @NonNull TextClassification classification)477 public static SelectionEvent selectionModified( 478 int start, int end, @NonNull TextClassification classification) { 479 final String entityType = classification.getEntityCount() > 0 480 ? classification.getEntity(0) 481 : TextClassifier.TYPE_UNKNOWN; 482 final String versionTag = getVersionInfo(classification.getId()); 483 return new SelectionEvent( 484 start, end, EventType.SELECTION_MODIFIED, entityType, versionTag); 485 } 486 487 /** 488 * Creates a "selection modified" event. 489 * Use when a TextClassifier modifies the selection. 490 * 491 * @param start the start word (inclusive) index of the selection 492 * @param end the end word (exclusive) index of the selection 493 * @param selection the TextSelection object returned by the TextClassifier for the 494 * specified selection 495 */ 496 @UnsupportedAppUsage selectionModified( int start, int end, @NonNull TextSelection selection)497 public static SelectionEvent selectionModified( 498 int start, int end, @NonNull TextSelection selection) { 499 final boolean smartSelection = getSourceClassifier(selection.getId()) 500 .equals(TextClassifier.DEFAULT_LOG_TAG); 501 final int eventType; 502 if (smartSelection) { 503 eventType = end - start > 1 504 ? EventType.SMART_SELECTION_MULTI 505 : EventType.SMART_SELECTION_SINGLE; 506 507 } else { 508 eventType = EventType.AUTO_SELECTION; 509 } 510 final String entityType = selection.getEntityCount() > 0 511 ? selection.getEntity(0) 512 : TextClassifier.TYPE_UNKNOWN; 513 final String versionTag = getVersionInfo(selection.getId()); 514 return new SelectionEvent(start, end, eventType, entityType, versionTag); 515 } 516 517 /** 518 * Creates an event specifying an action taken on a selection. 519 * Use when the user clicks on an action to act on the selected text. 520 * 521 * @param start the start word (inclusive) index of the selection 522 * @param end the end word (exclusive) index of the selection 523 * @param actionType the action that was performed on the selection 524 */ 525 @UnsupportedAppUsage selectionAction( int start, int end, @ActionType int actionType)526 public static SelectionEvent selectionAction( 527 int start, int end, @ActionType int actionType) { 528 return new SelectionEvent( 529 start, end, actionType, TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG); 530 } 531 532 /** 533 * Creates an event specifying an action taken on a selection. 534 * Use when the user clicks on an action to act on the selected text and the selection's 535 * entity type is known. 536 * 537 * @param start the start word (inclusive) index of the selection 538 * @param end the end word (exclusive) index of the selection 539 * @param actionType the action that was performed on the selection 540 * @param classification the TextClassification object returned by the TextClassifier that 541 * classified the selected text 542 */ 543 @UnsupportedAppUsage selectionAction( int start, int end, @ActionType int actionType, @NonNull TextClassification classification)544 public static SelectionEvent selectionAction( 545 int start, int end, @ActionType int actionType, 546 @NonNull TextClassification classification) { 547 final String entityType = classification.getEntityCount() > 0 548 ? classification.getEntity(0) 549 : TextClassifier.TYPE_UNKNOWN; 550 final String versionTag = getVersionInfo(classification.getId()); 551 return new SelectionEvent(start, end, actionType, entityType, versionTag); 552 } 553 getVersionInfo(String signature)554 private static String getVersionInfo(String signature) { 555 final int start = signature.indexOf("|"); 556 final int end = signature.indexOf("|", start); 557 if (start >= 0 && end >= start) { 558 return signature.substring(start, end); 559 } 560 return ""; 561 } 562 getSourceClassifier(String signature)563 private static String getSourceClassifier(String signature) { 564 final int end = signature.indexOf("|"); 565 if (end >= 0) { 566 return signature.substring(0, end); 567 } 568 return ""; 569 } 570 isTerminal()571 private boolean isTerminal() { 572 switch (mEventType) { 573 case ActionType.OVERTYPE: // fall through 574 case ActionType.COPY: // fall through 575 case ActionType.PASTE: // fall through 576 case ActionType.CUT: // fall through 577 case ActionType.SHARE: // fall through 578 case ActionType.SMART_SHARE: // fall through 579 case ActionType.DRAG: // fall through 580 case ActionType.ABANDON: // fall through 581 case ActionType.OTHER: // fall through 582 return true; 583 default: 584 return false; 585 } 586 } 587 } 588 } 589