blob: 157c43597a44e3a31aa7b172969a9337608f3c3c [file] [log] [blame]
Abodunrinwa Toki692b1962017-08-15 15:05:11 +01001/*
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.logging;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
Mathew Inwooda570dee2018-08-17 14:56:00 +010022import android.annotation.UnsupportedAppUsage;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010023import android.content.Context;
24import android.metrics.LogMaker;
25import android.util.Log;
26import android.view.textclassifier.TextClassification;
27import android.view.textclassifier.TextClassifier;
28import android.view.textclassifier.TextSelection;
29
Tony Makb393eea2019-07-05 15:23:39 +010030import com.android.internal.annotations.VisibleForTesting;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010031import com.android.internal.logging.MetricsLogger;
32import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
33import com.android.internal.util.Preconditions;
34
35import java.lang.annotation.Retention;
36import java.lang.annotation.RetentionPolicy;
37import java.util.Objects;
38import java.util.UUID;
39
40/**
41 * A selection event tracker.
42 * @hide
43 */
44//TODO: Do not allow any crashes from this class.
45public final class SmartSelectionEventTracker {
46
Jan Althaus786a39d2017-09-15 10:41:16 +020047 private static final String LOG_TAG = "SmartSelectEventTracker";
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010048 private static final boolean DEBUG_LOG_ENABLED = true;
49
Jan Althaus786a39d2017-09-15 10:41:16 +020050 private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
51 private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
52 private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
Jan Althausae5eb832017-11-06 12:31:59 +010053 private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
Jan Althaus5d0a14b2017-11-15 11:20:58 +010054 private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
Jan Althausae5eb832017-11-06 12:31:59 +010055 private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
56 private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
57 private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
58 private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
59 private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
60 private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
Jan Althaus786a39d2017-09-15 10:41:16 +020061 private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010062
63 private static final String ZERO = "0";
64 private static final String TEXTVIEW = "textview";
65 private static final String EDITTEXT = "edittext";
Jan Althausb3c6ece2017-11-14 15:40:16 +010066 private static final String UNSELECTABLE_TEXTVIEW = "nosel-textview";
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010067 private static final String WEBVIEW = "webview";
68 private static final String EDIT_WEBVIEW = "edit-webview";
Jan Althausb3c6ece2017-11-14 15:40:16 +010069 private static final String CUSTOM_TEXTVIEW = "customview";
70 private static final String CUSTOM_EDITTEXT = "customedit";
71 private static final String CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010072 private static final String UNKNOWN = "unknown";
73
74 @Retention(RetentionPolicy.SOURCE)
75 @IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW,
76 WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW})
77 public @interface WidgetType {
Jan Althausb3c6ece2017-11-14 15:40:16 +010078 int UNSPECIFIED = 0;
79 int TEXTVIEW = 1;
80 int WEBVIEW = 2;
81 int EDITTEXT = 3;
82 int EDIT_WEBVIEW = 4;
83 int UNSELECTABLE_TEXTVIEW = 5;
84 int CUSTOM_TEXTVIEW = 6;
85 int CUSTOM_EDITTEXT = 7;
86 int CUSTOM_UNSELECTABLE_TEXTVIEW = 8;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010087 }
88
89 private final MetricsLogger mMetricsLogger = new MetricsLogger();
90 private final int mWidgetType;
Jan Althaus5d0a14b2017-11-15 11:20:58 +010091 @Nullable private final String mWidgetVersion;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010092 private final Context mContext;
93
94 @Nullable private String mSessionId;
95 private final int[] mSmartIndices = new int[2];
96 private final int[] mPrevIndices = new int[2];
97 private int mOrigStart;
98 private int mIndex;
99 private long mSessionStartTime;
100 private long mLastEventTime;
101 private boolean mSmartSelectionTriggered;
Jan Althausae5eb832017-11-06 12:31:59 +0100102 private String mModelName;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100103
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100104 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100105 public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
106 mWidgetType = widgetType;
Jan Althaus5d0a14b2017-11-15 11:20:58 +0100107 mWidgetVersion = null;
108 mContext = Preconditions.checkNotNull(context);
109 }
110
111 public SmartSelectionEventTracker(
112 @NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) {
113 mWidgetType = widgetType;
114 mWidgetVersion = widgetVersion;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100115 mContext = Preconditions.checkNotNull(context);
116 }
117
118 /**
119 * Logs a selection event.
120 *
121 * @param event the selection event
122 */
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100123 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100124 public void logEvent(@NonNull SelectionEvent event) {
125 Preconditions.checkNotNull(event);
126
Jan Althaus786a39d2017-09-15 10:41:16 +0200127 if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null
128 && DEBUG_LOG_ENABLED) {
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100129 Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
130 return;
131 }
132
133 final long now = System.currentTimeMillis();
134 switch (event.mEventType) {
135 case SelectionEvent.EventType.SELECTION_STARTED:
136 mSessionId = startNewSession();
137 Preconditions.checkArgument(event.mEnd == event.mStart + 1);
138 mOrigStart = event.mStart;
139 mSessionStartTime = now;
140 break;
141 case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
142 case SelectionEvent.EventType.SMART_SELECTION_MULTI:
143 mSmartSelectionTriggered = true;
Jan Althausae5eb832017-11-06 12:31:59 +0100144 mModelName = getModelName(event);
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100145 mSmartIndices[0] = event.mStart;
146 mSmartIndices[1] = event.mEnd;
147 break;
148 case SelectionEvent.EventType.SELECTION_MODIFIED: // fall through
149 case SelectionEvent.EventType.AUTO_SELECTION:
150 if (mPrevIndices[0] == event.mStart && mPrevIndices[1] == event.mEnd) {
151 // Selection did not change. Ignore event.
152 return;
153 }
154 }
155 writeEvent(event, now);
156
157 if (event.isTerminal()) {
158 endSession();
159 }
160 }
161
162 private void writeEvent(SelectionEvent event, long now) {
Jan Althaus786a39d2017-09-15 10:41:16 +0200163 final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime;
164 final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100165 .setType(getLogType(event))
Jan Althausae5eb832017-11-06 12:31:59 +0100166 .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100167 .setPackageName(mContext.getPackageName())
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100168 .addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
Jan Althaus786a39d2017-09-15 10:41:16 +0200169 .addTaggedData(PREV_EVENT_DELTA, prevEventDelta)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100170 .addTaggedData(INDEX, mIndex)
Jan Althausae5eb832017-11-06 12:31:59 +0100171 .addTaggedData(WIDGET_TYPE, getWidgetTypeName())
Jan Althaus5d0a14b2017-11-15 11:20:58 +0100172 .addTaggedData(WIDGET_VERSION, mWidgetVersion)
Jan Althausae5eb832017-11-06 12:31:59 +0100173 .addTaggedData(MODEL_NAME, mModelName)
174 .addTaggedData(ENTITY_TYPE, event.mEntityType)
175 .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0]))
176 .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1]))
177 .addTaggedData(EVENT_START, getRangeDelta(event.mStart))
178 .addTaggedData(EVENT_END, getRangeDelta(event.mEnd))
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100179 .addTaggedData(SESSION_ID, mSessionId);
180 mMetricsLogger.write(log);
181 debugLog(log);
182 mLastEventTime = now;
183 mPrevIndices[0] = event.mStart;
184 mPrevIndices[1] = event.mEnd;
185 mIndex++;
186 }
187
188 private String startNewSession() {
189 endSession();
190 mSessionId = createSessionId();
191 return mSessionId;
192 }
193
194 private void endSession() {
195 // Reset fields.
196 mOrigStart = 0;
197 mSmartIndices[0] = mSmartIndices[1] = 0;
198 mPrevIndices[0] = mPrevIndices[1] = 0;
199 mIndex = 0;
200 mSessionStartTime = 0;
201 mLastEventTime = 0;
202 mSmartSelectionTriggered = false;
Jan Althausae5eb832017-11-06 12:31:59 +0100203 mModelName = getModelName(null);
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100204 mSessionId = null;
205 }
206
Jan Althaus786a39d2017-09-15 10:41:16 +0200207 private static int getLogType(SelectionEvent event) {
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100208 switch (event.mEventType) {
Jan Althaus786a39d2017-09-15 10:41:16 +0200209 case SelectionEvent.ActionType.OVERTYPE:
210 return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE;
211 case SelectionEvent.ActionType.COPY:
212 return MetricsEvent.ACTION_TEXT_SELECTION_COPY;
213 case SelectionEvent.ActionType.PASTE:
214 return MetricsEvent.ACTION_TEXT_SELECTION_PASTE;
215 case SelectionEvent.ActionType.CUT:
216 return MetricsEvent.ACTION_TEXT_SELECTION_CUT;
217 case SelectionEvent.ActionType.SHARE:
218 return MetricsEvent.ACTION_TEXT_SELECTION_SHARE;
219 case SelectionEvent.ActionType.SMART_SHARE:
220 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
221 case SelectionEvent.ActionType.DRAG:
222 return MetricsEvent.ACTION_TEXT_SELECTION_DRAG;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100223 case SelectionEvent.ActionType.ABANDON:
Jan Althaus786a39d2017-09-15 10:41:16 +0200224 return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON;
225 case SelectionEvent.ActionType.OTHER:
226 return MetricsEvent.ACTION_TEXT_SELECTION_OTHER;
227 case SelectionEvent.ActionType.SELECT_ALL:
228 return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL;
229 case SelectionEvent.ActionType.RESET:
230 return MetricsEvent.ACTION_TEXT_SELECTION_RESET;
231 case SelectionEvent.EventType.SELECTION_STARTED:
232 return MetricsEvent.ACTION_TEXT_SELECTION_START;
233 case SelectionEvent.EventType.SELECTION_MODIFIED:
234 return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY;
235 case SelectionEvent.EventType.SMART_SELECTION_SINGLE:
236 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE;
237 case SelectionEvent.EventType.SMART_SELECTION_MULTI:
238 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI;
239 case SelectionEvent.EventType.AUTO_SELECTION:
240 return MetricsEvent.ACTION_TEXT_SELECTION_AUTO;
241 default:
242 return MetricsEvent.VIEW_UNKNOWN;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100243 }
244 }
245
Jan Althaus786a39d2017-09-15 10:41:16 +0200246 private static String getLogTypeString(int logType) {
247 switch (logType) {
248 case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE:
249 return "OVERTYPE";
250 case MetricsEvent.ACTION_TEXT_SELECTION_COPY:
251 return "COPY";
252 case MetricsEvent.ACTION_TEXT_SELECTION_PASTE:
253 return "PASTE";
254 case MetricsEvent.ACTION_TEXT_SELECTION_CUT:
255 return "CUT";
256 case MetricsEvent.ACTION_TEXT_SELECTION_SHARE:
257 return "SHARE";
258 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
259 return "SMART_SHARE";
260 case MetricsEvent.ACTION_TEXT_SELECTION_DRAG:
261 return "DRAG";
262 case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON:
263 return "ABANDON";
264 case MetricsEvent.ACTION_TEXT_SELECTION_OTHER:
265 return "OTHER";
266 case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL:
267 return "SELECT_ALL";
268 case MetricsEvent.ACTION_TEXT_SELECTION_RESET:
269 return "RESET";
270 case MetricsEvent.ACTION_TEXT_SELECTION_START:
271 return "SELECTION_STARTED";
272 case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY:
273 return "SELECTION_MODIFIED";
274 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE:
275 return "SMART_SELECTION_SINGLE";
276 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI:
277 return "SMART_SELECTION_MULTI";
278 case MetricsEvent.ACTION_TEXT_SELECTION_AUTO:
279 return "AUTO_SELECTION";
280 default:
281 return UNKNOWN;
282 }
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100283 }
284
Jan Althausae5eb832017-11-06 12:31:59 +0100285 private int getRangeDelta(int offset) {
286 return offset - mOrigStart;
Jan Althaus786a39d2017-09-15 10:41:16 +0200287 }
288
Jan Althausae5eb832017-11-06 12:31:59 +0100289 private int getSmartRangeDelta(int offset) {
290 return mSmartSelectionTriggered ? getRangeDelta(offset) : 0;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100291 }
292
Jan Althausae5eb832017-11-06 12:31:59 +0100293 private String getWidgetTypeName() {
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100294 switch (mWidgetType) {
295 case WidgetType.TEXTVIEW:
Jan Althausae5eb832017-11-06 12:31:59 +0100296 return TEXTVIEW;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100297 case WidgetType.WEBVIEW:
Jan Althausae5eb832017-11-06 12:31:59 +0100298 return WEBVIEW;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100299 case WidgetType.EDITTEXT:
Jan Althausae5eb832017-11-06 12:31:59 +0100300 return EDITTEXT;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100301 case WidgetType.EDIT_WEBVIEW:
Jan Althausae5eb832017-11-06 12:31:59 +0100302 return EDIT_WEBVIEW;
Jan Althausb3c6ece2017-11-14 15:40:16 +0100303 case WidgetType.UNSELECTABLE_TEXTVIEW:
304 return UNSELECTABLE_TEXTVIEW;
305 case WidgetType.CUSTOM_TEXTVIEW:
306 return CUSTOM_TEXTVIEW;
307 case WidgetType.CUSTOM_EDITTEXT:
308 return CUSTOM_EDITTEXT;
309 case WidgetType.CUSTOM_UNSELECTABLE_TEXTVIEW:
310 return CUSTOM_UNSELECTABLE_TEXTVIEW;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100311 default:
Jan Althausae5eb832017-11-06 12:31:59 +0100312 return UNKNOWN;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100313 }
Jan Althausae5eb832017-11-06 12:31:59 +0100314 }
315
316 private String getModelName(@Nullable SelectionEvent event) {
317 return event == null
Jan Althaus786a39d2017-09-15 10:41:16 +0200318 ? SelectionEvent.NO_VERSION_TAG
319 : Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100320 }
321
322 private static String createSessionId() {
323 return UUID.randomUUID().toString();
324 }
325
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100326 private static void debugLog(LogMaker log) {
327 if (!DEBUG_LOG_ENABLED) return;
328
Jan Althaus5d0a14b2017-11-15 11:20:58 +0100329 final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
330 final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
331 final String widget = widgetVersion.isEmpty()
332 ? widgetType : widgetType + "-" + widgetVersion;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100333 final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
Jan Althaus786a39d2017-09-15 10:41:16 +0200334 if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
335 String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
336 sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
Jan Althausae5eb832017-11-06 12:31:59 +0100337 Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100338 }
339
Jan Althausae5eb832017-11-06 12:31:59 +0100340 final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
341 final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
Jan Althaus786a39d2017-09-15 10:41:16 +0200342 final String type = getLogTypeString(log.getType());
Jan Althausae5eb832017-11-06 12:31:59 +0100343 final int smartStart = Integer.parseInt(
344 Objects.toString(log.getTaggedData(SMART_START), ZERO));
345 final int smartEnd = Integer.parseInt(
346 Objects.toString(log.getTaggedData(SMART_END), ZERO));
347 final int eventStart = Integer.parseInt(
348 Objects.toString(log.getTaggedData(EVENT_START), ZERO));
349 final int eventEnd = Integer.parseInt(
350 Objects.toString(log.getTaggedData(EVENT_END), ZERO));
Jan Althaus786a39d2017-09-15 10:41:16 +0200351
Jan Althausae5eb832017-11-06 12:31:59 +0100352 Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
353 index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model));
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100354 }
355
356 /**
357 * A selection event.
358 * Specify index parameters as word token indices.
359 */
360 public static final class SelectionEvent {
361
362 /**
363 * Use this to specify an indeterminate positive index.
364 */
Jan Althausae5eb832017-11-06 12:31:59 +0100365 public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100366
367 /**
368 * Use this to specify an indeterminate negative index.
369 */
Jan Althausae5eb832017-11-06 12:31:59 +0100370 public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100371
372 private static final String NO_VERSION_TAG = "";
373
374 @Retention(RetentionPolicy.SOURCE)
375 @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT,
376 ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON,
377 ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET})
378 public @interface ActionType {
379 /** User typed over the selection. */
380 int OVERTYPE = 100;
381 /** User copied the selection. */
382 int COPY = 101;
383 /** User pasted over the selection. */
384 int PASTE = 102;
385 /** User cut the selection. */
386 int CUT = 103;
387 /** User shared the selection. */
388 int SHARE = 104;
389 /** User clicked the textAssist menu item. */
390 int SMART_SHARE = 105;
391 /** User dragged+dropped the selection. */
392 int DRAG = 106;
393 /** User abandoned the selection. */
394 int ABANDON = 107;
395 /** User performed an action on the selection. */
396 int OTHER = 108;
397
398 /* Non-terminal actions. */
399 /** User activated Select All */
400 int SELECT_ALL = 200;
401 /** User reset the smart selection. */
402 int RESET = 201;
403 }
404
405 @Retention(RetentionPolicy.SOURCE)
406 @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT,
407 ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON,
408 ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET,
409 EventType.SELECTION_STARTED, EventType.SELECTION_MODIFIED,
410 EventType.SMART_SELECTION_SINGLE, EventType.SMART_SELECTION_MULTI,
411 EventType.AUTO_SELECTION})
412 private @interface EventType {
413 /** User started a new selection. */
414 int SELECTION_STARTED = 1;
415 /** User modified an existing selection. */
416 int SELECTION_MODIFIED = 2;
417 /** Smart selection triggered for a single token (word). */
418 int SMART_SELECTION_SINGLE = 3;
419 /** Smart selection triggered spanning multiple tokens (words). */
420 int SMART_SELECTION_MULTI = 4;
421 /** Something else other than User or the default TextClassifier triggered a selection. */
422 int AUTO_SELECTION = 5;
423 }
424
425 private final int mStart;
426 private final int mEnd;
427 private @EventType int mEventType;
428 private final @TextClassifier.EntityType String mEntityType;
429 private final String mVersionTag;
430
431 private SelectionEvent(
432 int start, int end, int eventType,
433 @TextClassifier.EntityType String entityType, String versionTag) {
434 Preconditions.checkArgument(end >= start, "end cannot be less than start");
435 mStart = start;
436 mEnd = end;
437 mEventType = eventType;
438 mEntityType = Preconditions.checkNotNull(entityType);
439 mVersionTag = Preconditions.checkNotNull(versionTag);
440 }
441
442 /**
443 * Creates a "selection started" event.
444 *
445 * @param start the word index of the selected word
446 */
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100447 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100448 public static SelectionEvent selectionStarted(int start) {
449 return new SelectionEvent(
450 start, start + 1, EventType.SELECTION_STARTED,
451 TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG);
452 }
453
454 /**
455 * Creates a "selection modified" event.
456 * Use when the user modifies the selection.
457 *
458 * @param start the start word (inclusive) index of the selection
459 * @param end the end word (exclusive) index of the selection
460 */
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100461 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100462 public static SelectionEvent selectionModified(int start, int end) {
463 return new SelectionEvent(
464 start, end, EventType.SELECTION_MODIFIED,
465 TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG);
466 }
467
468 /**
469 * Creates a "selection modified" event.
470 * Use when the user modifies the selection and the selection's entity type is known.
471 *
472 * @param start the start word (inclusive) index of the selection
473 * @param end the end word (exclusive) index of the selection
474 * @param classification the TextClassification object returned by the TextClassifier that
475 * classified the selected text
476 */
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100477 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100478 public static SelectionEvent selectionModified(
479 int start, int end, @NonNull TextClassification classification) {
480 final String entityType = classification.getEntityCount() > 0
481 ? classification.getEntity(0)
482 : TextClassifier.TYPE_UNKNOWN;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100483 final String versionTag = getVersionInfo(classification.getId());
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100484 return new SelectionEvent(
485 start, end, EventType.SELECTION_MODIFIED, entityType, versionTag);
486 }
487
488 /**
489 * Creates a "selection modified" event.
490 * Use when a TextClassifier modifies the selection.
491 *
492 * @param start the start word (inclusive) index of the selection
493 * @param end the end word (exclusive) index of the selection
494 * @param selection the TextSelection object returned by the TextClassifier for the
495 * specified selection
496 */
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100497 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100498 public static SelectionEvent selectionModified(
499 int start, int end, @NonNull TextSelection selection) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100500 final boolean smartSelection = getSourceClassifier(selection.getId())
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100501 .equals(TextClassifier.DEFAULT_LOG_TAG);
502 final int eventType;
503 if (smartSelection) {
504 eventType = end - start > 1
505 ? EventType.SMART_SELECTION_MULTI
506 : EventType.SMART_SELECTION_SINGLE;
507
508 } else {
509 eventType = EventType.AUTO_SELECTION;
510 }
511 final String entityType = selection.getEntityCount() > 0
512 ? selection.getEntity(0)
513 : TextClassifier.TYPE_UNKNOWN;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100514 final String versionTag = getVersionInfo(selection.getId());
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100515 return new SelectionEvent(start, end, eventType, entityType, versionTag);
516 }
517
518 /**
519 * Creates an event specifying an action taken on a selection.
520 * Use when the user clicks on an action to act on the selected text.
521 *
522 * @param start the start word (inclusive) index of the selection
523 * @param end the end word (exclusive) index of the selection
524 * @param actionType the action that was performed on the selection
525 */
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100526 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100527 public static SelectionEvent selectionAction(
528 int start, int end, @ActionType int actionType) {
529 return new SelectionEvent(
530 start, end, actionType, TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG);
531 }
532
533 /**
534 * Creates an event specifying an action taken on a selection.
535 * Use when the user clicks on an action to act on the selected text and the selection's
536 * entity type is known.
537 *
538 * @param start the start word (inclusive) index of the selection
539 * @param end the end word (exclusive) index of the selection
540 * @param actionType the action that was performed on the selection
541 * @param classification the TextClassification object returned by the TextClassifier that
542 * classified the selected text
543 */
Mathew Inwood5ca23f62019-07-03 12:20:45 +0100544 @UnsupportedAppUsage(trackingBug = 136637107)
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100545 public static SelectionEvent selectionAction(
546 int start, int end, @ActionType int actionType,
547 @NonNull TextClassification classification) {
548 final String entityType = classification.getEntityCount() > 0
549 ? classification.getEntity(0)
550 : TextClassifier.TYPE_UNKNOWN;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100551 final String versionTag = getVersionInfo(classification.getId());
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100552 return new SelectionEvent(start, end, actionType, entityType, versionTag);
553 }
554
Tony Makb393eea2019-07-05 15:23:39 +0100555 @VisibleForTesting
556 public static String getVersionInfo(String signature) {
557 final int start = signature.indexOf("|") + 1;
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000558 final int end = signature.indexOf("|", start);
Tony Makb393eea2019-07-05 15:23:39 +0100559 if (start >= 1 && end >= start) {
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000560 return signature.substring(start, end);
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100561 }
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000562 return "";
563 }
564
565 private static String getSourceClassifier(String signature) {
566 final int end = signature.indexOf("|");
567 if (end >= 0) {
568 return signature.substring(0, end);
569 }
570 return "";
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100571 }
572
573 private boolean isTerminal() {
574 switch (mEventType) {
575 case ActionType.OVERTYPE: // fall through
576 case ActionType.COPY: // fall through
577 case ActionType.PASTE: // fall through
578 case ActionType.CUT: // fall through
579 case ActionType.SHARE: // fall through
580 case ActionType.SMART_SHARE: // fall through
581 case ActionType.DRAG: // fall through
582 case ActionType.ABANDON: // fall through
Jan Althaus34aa2942017-09-22 19:55:17 +0200583 case ActionType.OTHER: // fall through
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100584 return true;
585 default:
586 return false;
587 }
588 }
589 }
590}