diff options
Diffstat (limited to 'android/view/textclassifier/SelectionSessionLogger.java')
-rw-r--r-- | android/view/textclassifier/SelectionSessionLogger.java | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/android/view/textclassifier/SelectionSessionLogger.java b/android/view/textclassifier/SelectionSessionLogger.java new file mode 100644 index 00000000..f2fb63eb --- /dev/null +++ b/android/view/textclassifier/SelectionSessionLogger.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.metrics.LogMaker; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.Preconditions; + +import java.text.BreakIterator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.StringJoiner; + +/** + * A helper for logging selection session events. + * @hide + */ +public final class SelectionSessionLogger { + + private static final String LOG_TAG = "SelectionSessionLogger"; + private static final boolean DEBUG_LOG_ENABLED = false; + static final String CLASSIFIER_ID = "androidtc"; + + private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; + private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; + private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX; + private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; + private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; + private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; + private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; + private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START; + private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END; + private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START; + private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END; + private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID; + + private static final String ZERO = "0"; + private static final String UNKNOWN = "unknown"; + + private final MetricsLogger mMetricsLogger; + + public SelectionSessionLogger() { + mMetricsLogger = new MetricsLogger(); + } + + @VisibleForTesting + public SelectionSessionLogger(@NonNull MetricsLogger metricsLogger) { + mMetricsLogger = Preconditions.checkNotNull(metricsLogger); + } + + /** Emits a selection event to the logs. */ + public void writeEvent(@NonNull SelectionEvent event) { + Preconditions.checkNotNull(event); + final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION) + .setType(getLogType(event)) + .setSubtype(getLogSubType(event)) + .setPackageName(event.getPackageName()) + .addTaggedData(START_EVENT_DELTA, event.getDurationSinceSessionStart()) + .addTaggedData(PREV_EVENT_DELTA, event.getDurationSincePreviousEvent()) + .addTaggedData(INDEX, event.getEventIndex()) + .addTaggedData(WIDGET_TYPE, event.getWidgetType()) + .addTaggedData(WIDGET_VERSION, event.getWidgetVersion()) + .addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getResultId())) + .addTaggedData(ENTITY_TYPE, event.getEntityType()) + .addTaggedData(SMART_START, event.getSmartStart()) + .addTaggedData(SMART_END, event.getSmartEnd()) + .addTaggedData(EVENT_START, event.getStart()) + .addTaggedData(EVENT_END, event.getEnd()) + .addTaggedData(SESSION_ID, event.getSessionId().flattenToString()); + mMetricsLogger.write(log); + debugLog(log); + } + + private static int getLogType(SelectionEvent event) { + switch (event.getEventType()) { + case SelectionEvent.ACTION_OVERTYPE: + return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE; + case SelectionEvent.ACTION_COPY: + return MetricsEvent.ACTION_TEXT_SELECTION_COPY; + case SelectionEvent.ACTION_PASTE: + return MetricsEvent.ACTION_TEXT_SELECTION_PASTE; + case SelectionEvent.ACTION_CUT: + return MetricsEvent.ACTION_TEXT_SELECTION_CUT; + case SelectionEvent.ACTION_SHARE: + return MetricsEvent.ACTION_TEXT_SELECTION_SHARE; + case SelectionEvent.ACTION_SMART_SHARE: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; + case SelectionEvent.ACTION_DRAG: + return MetricsEvent.ACTION_TEXT_SELECTION_DRAG; + case SelectionEvent.ACTION_ABANDON: + return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON; + case SelectionEvent.ACTION_OTHER: + return MetricsEvent.ACTION_TEXT_SELECTION_OTHER; + case SelectionEvent.ACTION_SELECT_ALL: + return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL; + case SelectionEvent.ACTION_RESET: + return MetricsEvent.ACTION_TEXT_SELECTION_RESET; + case SelectionEvent.EVENT_SELECTION_STARTED: + return MetricsEvent.ACTION_TEXT_SELECTION_START; + case SelectionEvent.EVENT_SELECTION_MODIFIED: + return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY; + case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE; + case SelectionEvent.EVENT_SMART_SELECTION_MULTI: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI; + case SelectionEvent.EVENT_AUTO_SELECTION: + return MetricsEvent.ACTION_TEXT_SELECTION_AUTO; + default: + return MetricsEvent.VIEW_UNKNOWN; + } + } + + private static int getLogSubType(SelectionEvent event) { + switch (event.getInvocationMethod()) { + case SelectionEvent.INVOCATION_MANUAL: + return MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL; + case SelectionEvent.INVOCATION_LINK: + return MetricsEvent.TEXT_SELECTION_INVOCATION_LINK; + default: + return MetricsEvent.TEXT_SELECTION_INVOCATION_UNKNOWN; + } + } + + private static String getLogTypeString(int logType) { + switch (logType) { + case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE: + return "OVERTYPE"; + case MetricsEvent.ACTION_TEXT_SELECTION_COPY: + return "COPY"; + case MetricsEvent.ACTION_TEXT_SELECTION_PASTE: + return "PASTE"; + case MetricsEvent.ACTION_TEXT_SELECTION_CUT: + return "CUT"; + case MetricsEvent.ACTION_TEXT_SELECTION_SHARE: + return "SHARE"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE: + return "SMART_SHARE"; + case MetricsEvent.ACTION_TEXT_SELECTION_DRAG: + return "DRAG"; + case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON: + return "ABANDON"; + case MetricsEvent.ACTION_TEXT_SELECTION_OTHER: + return "OTHER"; + case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL: + return "SELECT_ALL"; + case MetricsEvent.ACTION_TEXT_SELECTION_RESET: + return "RESET"; + case MetricsEvent.ACTION_TEXT_SELECTION_START: + return "SELECTION_STARTED"; + case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY: + return "SELECTION_MODIFIED"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE: + return "SMART_SELECTION_SINGLE"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI: + return "SMART_SELECTION_MULTI"; + case MetricsEvent.ACTION_TEXT_SELECTION_AUTO: + return "AUTO_SELECTION"; + default: + return UNKNOWN; + } + } + + private static String getLogSubTypeString(int logSubType) { + switch (logSubType) { + case MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL: + return "MANUAL"; + case MetricsEvent.TEXT_SELECTION_INVOCATION_LINK: + return "LINK"; + default: + return UNKNOWN; + } + } + + private static void debugLog(LogMaker log) { + if (!DEBUG_LOG_ENABLED) return; + + final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN); + final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), ""); + final String widget = widgetVersion.isEmpty() + ? widgetType : widgetType + "-" + widgetVersion; + final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO)); + if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) { + String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), ""); + sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1); + Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId)); + } + + final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN); + final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN); + final String type = getLogTypeString(log.getType()); + final String subType = getLogSubTypeString(log.getSubtype()); + final int smartStart = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_START), ZERO)); + final int smartEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_END), ZERO)); + final int eventStart = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_START), ZERO)); + final int eventEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_END), ZERO)); + + Log.d(LOG_TAG, + String.format(Locale.US, "%2d: %s/%s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)", + index, type, subType, entity, eventStart, eventEnd, smartStart, smartEnd, + widget, model)); + } + + /** + * Returns a token iterator for tokenizing text for logging purposes. + */ + public static BreakIterator getTokenIterator(@NonNull Locale locale) { + return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale)); + } + + /** + * Creates a string id that may be used to identify a TextClassifier result. + */ + public static String createId( + String text, int start, int end, Context context, int modelVersion, + List<Locale> locales) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(locales); + final StringJoiner localesJoiner = new StringJoiner(","); + for (Locale locale : locales) { + localesJoiner.add(locale.toLanguageTag()); + } + final String modelName = String.format(Locale.US, "%s_v%d", localesJoiner.toString(), + modelVersion); + final int hash = Objects.hash(text, start, end, context.getPackageName()); + return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash); + } + + /** + * Helper for creating and parsing string ids for + * {@link android.view.textclassifier.TextClassifierImpl}. + */ + @VisibleForTesting + public static final class SignatureParser { + + static String createSignature(String classifierId, String modelName, int hash) { + return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash); + } + + static String getClassifierId(@Nullable String signature) { + if (signature == null) { + return ""; + } + final int end = signature.indexOf("|"); + if (end >= 0) { + return signature.substring(0, end); + } + return ""; + } + + static String getModelName(@Nullable String signature) { + if (signature == null) { + return ""; + } + final int start = signature.indexOf("|") + 1; + final int end = signature.indexOf("|", start); + if (start >= 1 && end >= start) { + return signature.substring(start, end); + } + return ""; + } + + static int getHash(@Nullable String signature) { + if (signature == null) { + return 0; + } + final int index1 = signature.indexOf("|"); + final int index2 = signature.indexOf("|", index1); + if (index2 > 0) { + return Integer.parseInt(signature.substring(index2)); + } + return 0; + } + } +} |