blob: f2fb63eb01f3fb10435e8fd6d908a1ab35252b63 [file] [log] [blame]
/*
* 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;
}
}
}