blob: ae9f65ba61291cbb6bf1b2a086f55fd9b20c01d1 [file] [log] [blame]
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +00001/*
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
Abodunrinwa Tokif1d93992018-03-02 13:53:21 +000017package android.view.textclassifier;
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000018
19import android.annotation.NonNull;
Jan Althaus5a030942018-04-04 19:40:38 +020020import android.annotation.Nullable;
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000021import android.content.Context;
22import android.metrics.LogMaker;
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000023
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.internal.logging.MetricsLogger;
26import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000027
Jan Althaus5a030942018-04-04 19:40:38 +020028import java.text.BreakIterator;
Jan Althausef0156d2018-01-29 19:28:41 +010029import java.util.List;
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000030import java.util.Locale;
31import java.util.Objects;
Jan Althausef0156d2018-01-29 19:28:41 +010032import java.util.StringJoiner;
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000033
34/**
Jan Althaus5a030942018-04-04 19:40:38 +020035 * A helper for logging selection session events.
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000036 * @hide
37 */
Jan Althaus5a030942018-04-04 19:40:38 +020038public final class SelectionSessionLogger {
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000039
Jan Althaus5a030942018-04-04 19:40:38 +020040 private static final String LOG_TAG = "SelectionSessionLogger";
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000041 static final String CLASSIFIER_ID = "androidtc";
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000042
43 private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
44 private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
45 private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
46 private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
47 private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
48 private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
49 private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
50 private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
51 private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
52 private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
53 private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
54 private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
55
56 private static final String ZERO = "0";
57 private static final String UNKNOWN = "unknown";
58
59 private final MetricsLogger mMetricsLogger;
60
Jan Althaus5a030942018-04-04 19:40:38 +020061 public SelectionSessionLogger() {
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000062 mMetricsLogger = new MetricsLogger();
63 }
64
65 @VisibleForTesting
Jan Althaus5a030942018-04-04 19:40:38 +020066 public SelectionSessionLogger(@NonNull MetricsLogger metricsLogger) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +000067 mMetricsLogger = Objects.requireNonNull(metricsLogger);
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000068 }
69
Jan Althaus5a030942018-04-04 19:40:38 +020070 /** Emits a selection event to the logs. */
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000071 public void writeEvent(@NonNull SelectionEvent event) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +000072 Objects.requireNonNull(event);
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000073 final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
74 .setType(getLogType(event))
Jan Althaus92c6dec2018-02-02 09:20:14 +010075 .setSubtype(getLogSubType(event))
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000076 .setPackageName(event.getPackageName())
77 .addTaggedData(START_EVENT_DELTA, event.getDurationSinceSessionStart())
78 .addTaggedData(PREV_EVENT_DELTA, event.getDurationSincePreviousEvent())
79 .addTaggedData(INDEX, event.getEventIndex())
80 .addTaggedData(WIDGET_TYPE, event.getWidgetType())
81 .addTaggedData(WIDGET_VERSION, event.getWidgetVersion())
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000082 .addTaggedData(ENTITY_TYPE, event.getEntityType())
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000083 .addTaggedData(EVENT_START, event.getStart())
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +010084 .addTaggedData(EVENT_END, event.getEnd());
Abodunrinwa Toki6c564672019-05-09 02:03:40 +010085 if (isPlatformLocalTextClassifierSmartSelection(event.getResultId())) {
86 // Ensure result id and smart indices are only set for events with smart selection from
87 // the platform's textclassifier.
88 log.addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getResultId()))
89 .addTaggedData(SMART_START, event.getSmartStart())
90 .addTaggedData(SMART_END, event.getSmartEnd());
91 }
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +010092 if (event.getSessionId() != null) {
Tony Mak7835d9e2020-02-28 16:00:47 +000093 log.addTaggedData(SESSION_ID, event.getSessionId().getValue());
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +010094 }
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +000095 mMetricsLogger.write(log);
96 debugLog(log);
97 }
98
99 private static int getLogType(SelectionEvent event) {
100 switch (event.getEventType()) {
101 case SelectionEvent.ACTION_OVERTYPE:
102 return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE;
103 case SelectionEvent.ACTION_COPY:
104 return MetricsEvent.ACTION_TEXT_SELECTION_COPY;
105 case SelectionEvent.ACTION_PASTE:
106 return MetricsEvent.ACTION_TEXT_SELECTION_PASTE;
107 case SelectionEvent.ACTION_CUT:
108 return MetricsEvent.ACTION_TEXT_SELECTION_CUT;
109 case SelectionEvent.ACTION_SHARE:
110 return MetricsEvent.ACTION_TEXT_SELECTION_SHARE;
111 case SelectionEvent.ACTION_SMART_SHARE:
112 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
113 case SelectionEvent.ACTION_DRAG:
114 return MetricsEvent.ACTION_TEXT_SELECTION_DRAG;
115 case SelectionEvent.ACTION_ABANDON:
116 return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON;
117 case SelectionEvent.ACTION_OTHER:
118 return MetricsEvent.ACTION_TEXT_SELECTION_OTHER;
119 case SelectionEvent.ACTION_SELECT_ALL:
120 return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL;
121 case SelectionEvent.ACTION_RESET:
122 return MetricsEvent.ACTION_TEXT_SELECTION_RESET;
123 case SelectionEvent.EVENT_SELECTION_STARTED:
124 return MetricsEvent.ACTION_TEXT_SELECTION_START;
125 case SelectionEvent.EVENT_SELECTION_MODIFIED:
126 return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY;
127 case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:
128 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE;
129 case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
130 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI;
131 case SelectionEvent.EVENT_AUTO_SELECTION:
132 return MetricsEvent.ACTION_TEXT_SELECTION_AUTO;
133 default:
134 return MetricsEvent.VIEW_UNKNOWN;
135 }
136 }
137
Jan Althaus92c6dec2018-02-02 09:20:14 +0100138 private static int getLogSubType(SelectionEvent event) {
139 switch (event.getInvocationMethod()) {
140 case SelectionEvent.INVOCATION_MANUAL:
141 return MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL;
142 case SelectionEvent.INVOCATION_LINK:
143 return MetricsEvent.TEXT_SELECTION_INVOCATION_LINK;
144 default:
145 return MetricsEvent.TEXT_SELECTION_INVOCATION_UNKNOWN;
146 }
147 }
148
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000149 private static String getLogTypeString(int logType) {
150 switch (logType) {
151 case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE:
152 return "OVERTYPE";
153 case MetricsEvent.ACTION_TEXT_SELECTION_COPY:
154 return "COPY";
155 case MetricsEvent.ACTION_TEXT_SELECTION_PASTE:
156 return "PASTE";
157 case MetricsEvent.ACTION_TEXT_SELECTION_CUT:
158 return "CUT";
159 case MetricsEvent.ACTION_TEXT_SELECTION_SHARE:
160 return "SHARE";
161 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
162 return "SMART_SHARE";
163 case MetricsEvent.ACTION_TEXT_SELECTION_DRAG:
164 return "DRAG";
165 case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON:
166 return "ABANDON";
167 case MetricsEvent.ACTION_TEXT_SELECTION_OTHER:
168 return "OTHER";
169 case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL:
170 return "SELECT_ALL";
171 case MetricsEvent.ACTION_TEXT_SELECTION_RESET:
172 return "RESET";
173 case MetricsEvent.ACTION_TEXT_SELECTION_START:
174 return "SELECTION_STARTED";
175 case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY:
176 return "SELECTION_MODIFIED";
177 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE:
178 return "SMART_SELECTION_SINGLE";
179 case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI:
180 return "SMART_SELECTION_MULTI";
181 case MetricsEvent.ACTION_TEXT_SELECTION_AUTO:
182 return "AUTO_SELECTION";
183 default:
184 return UNKNOWN;
185 }
186 }
187
Jan Althaus92c6dec2018-02-02 09:20:14 +0100188 private static String getLogSubTypeString(int logSubType) {
189 switch (logSubType) {
190 case MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL:
191 return "MANUAL";
192 case MetricsEvent.TEXT_SELECTION_INVOCATION_LINK:
193 return "LINK";
194 default:
195 return UNKNOWN;
196 }
197 }
198
Abodunrinwa Toki6c564672019-05-09 02:03:40 +0100199 static boolean isPlatformLocalTextClassifierSmartSelection(String signature) {
200 return SelectionSessionLogger.CLASSIFIER_ID.equals(
201 SelectionSessionLogger.SignatureParser.getClassifierId(signature));
202 }
203
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000204 private static void debugLog(LogMaker log) {
Tony Makafd54672019-01-04 15:56:44 +0000205 if (!Log.ENABLE_FULL_LOGGING) {
206 return;
207 }
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000208 final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
209 final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
210 final String widget = widgetVersion.isEmpty()
211 ? widgetType : widgetType + "-" + widgetVersion;
212 final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
213 if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
214 String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
215 sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
216 Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
217 }
218
219 final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
220 final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
221 final String type = getLogTypeString(log.getType());
Jan Althaus92c6dec2018-02-02 09:20:14 +0100222 final String subType = getLogSubTypeString(log.getSubtype());
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000223 final int smartStart = Integer.parseInt(
224 Objects.toString(log.getTaggedData(SMART_START), ZERO));
225 final int smartEnd = Integer.parseInt(
226 Objects.toString(log.getTaggedData(SMART_END), ZERO));
227 final int eventStart = Integer.parseInt(
228 Objects.toString(log.getTaggedData(EVENT_START), ZERO));
229 final int eventEnd = Integer.parseInt(
230 Objects.toString(log.getTaggedData(EVENT_END), ZERO));
231
Tony Makafd54672019-01-04 15:56:44 +0000232 Log.v(LOG_TAG,
Jan Althaus5a030942018-04-04 19:40:38 +0200233 String.format(Locale.US, "%2d: %s/%s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
234 index, type, subType, entity, eventStart, eventEnd, smartStart, smartEnd,
235 widget, model));
236 }
237
238 /**
239 * Returns a token iterator for tokenizing text for logging purposes.
240 */
241 public static BreakIterator getTokenIterator(@NonNull Locale locale) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000242 return BreakIterator.getWordInstance(Objects.requireNonNull(locale));
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000243 }
244
245 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100246 * Creates a string id that may be used to identify a TextClassifier result.
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000247 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100248 public static String createId(
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000249 String text, int start, int end, Context context, int modelVersion,
Jan Althausef0156d2018-01-29 19:28:41 +0100250 List<Locale> locales) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000251 Objects.requireNonNull(text);
252 Objects.requireNonNull(context);
253 Objects.requireNonNull(locales);
Jan Althausef0156d2018-01-29 19:28:41 +0100254 final StringJoiner localesJoiner = new StringJoiner(",");
255 for (Locale locale : locales) {
256 localesJoiner.add(locale.toLanguageTag());
257 }
258 final String modelName = String.format(Locale.US, "%s_v%d", localesJoiner.toString(),
259 modelVersion);
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000260 final int hash = Objects.hash(text, start, end, context.getPackageName());
261 return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash);
262 }
263
264 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100265 * Helper for creating and parsing string ids for
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000266 * {@link android.view.textclassifier.TextClassifierImpl}.
267 */
268 @VisibleForTesting
269 public static final class SignatureParser {
270
271 static String createSignature(String classifierId, String modelName, int hash) {
272 return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash);
273 }
274
Jan Althaus5a030942018-04-04 19:40:38 +0200275 static String getClassifierId(@Nullable String signature) {
276 if (signature == null) {
277 return "";
278 }
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000279 final int end = signature.indexOf("|");
280 if (end >= 0) {
281 return signature.substring(0, end);
282 }
283 return "";
284 }
285
Jan Althaus5a030942018-04-04 19:40:38 +0200286 static String getModelName(@Nullable String signature) {
287 if (signature == null) {
288 return "";
289 }
Jan Althausef0156d2018-01-29 19:28:41 +0100290 final int start = signature.indexOf("|") + 1;
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000291 final int end = signature.indexOf("|", start);
Jan Althausef0156d2018-01-29 19:28:41 +0100292 if (start >= 1 && end >= start) {
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000293 return signature.substring(start, end);
294 }
295 return "";
296 }
297
Jan Althaus5a030942018-04-04 19:40:38 +0200298 static int getHash(@Nullable String signature) {
299 if (signature == null) {
300 return 0;
301 }
Abodunrinwa Toki3bb44362017-12-05 07:33:41 +0000302 final int index1 = signature.indexOf("|");
303 final int index2 = signature.indexOf("|", index1);
304 if (index2 > 0) {
305 return Integer.parseInt(signature.substring(index2));
306 }
307 return 0;
308 }
309 }
310}