Home | History | Annotate | Download | only in textclassifier
      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;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.Context;
     22 import android.metrics.LogMaker;
     23 
     24 import com.android.internal.annotations.VisibleForTesting;
     25 import com.android.internal.logging.MetricsLogger;
     26 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     27 import com.android.internal.util.Preconditions;
     28 
     29 import java.text.BreakIterator;
     30 import java.util.List;
     31 import java.util.Locale;
     32 import java.util.Objects;
     33 import java.util.StringJoiner;
     34 
     35 /**
     36  * A helper for logging selection session events.
     37  * @hide
     38  */
     39 public final class SelectionSessionLogger {
     40 
     41     private static final String LOG_TAG = "SelectionSessionLogger";
     42     private static final boolean DEBUG_LOG_ENABLED = false;
     43     static final String CLASSIFIER_ID = "androidtc";
     44 
     45     private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
     46     private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
     47     private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
     48     private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
     49     private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
     50     private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
     51     private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
     52     private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
     53     private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
     54     private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
     55     private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
     56     private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
     57 
     58     private static final String ZERO = "0";
     59     private static final String UNKNOWN = "unknown";
     60 
     61     private final MetricsLogger mMetricsLogger;
     62 
     63     public SelectionSessionLogger() {
     64         mMetricsLogger = new MetricsLogger();
     65     }
     66 
     67     @VisibleForTesting
     68     public SelectionSessionLogger(@NonNull MetricsLogger metricsLogger) {
     69         mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
     70     }
     71 
     72     /** Emits a selection event to the logs. */
     73     public void writeEvent(@NonNull SelectionEvent event) {
     74         Preconditions.checkNotNull(event);
     75         final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
     76                 .setType(getLogType(event))
     77                 .setSubtype(getLogSubType(event))
     78                 .setPackageName(event.getPackageName())
     79                 .addTaggedData(START_EVENT_DELTA, event.getDurationSinceSessionStart())
     80                 .addTaggedData(PREV_EVENT_DELTA, event.getDurationSincePreviousEvent())
     81                 .addTaggedData(INDEX, event.getEventIndex())
     82                 .addTaggedData(WIDGET_TYPE, event.getWidgetType())
     83                 .addTaggedData(WIDGET_VERSION, event.getWidgetVersion())
     84                 .addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getResultId()))
     85                 .addTaggedData(ENTITY_TYPE, event.getEntityType())
     86                 .addTaggedData(SMART_START, event.getSmartStart())
     87                 .addTaggedData(SMART_END, event.getSmartEnd())
     88                 .addTaggedData(EVENT_START, event.getStart())
     89                 .addTaggedData(EVENT_END, event.getEnd());
     90         if (event.getSessionId() != null) {
     91             log.addTaggedData(SESSION_ID, event.getSessionId().flattenToString());
     92         }
     93         mMetricsLogger.write(log);
     94         debugLog(log);
     95     }
     96 
     97     private static int getLogType(SelectionEvent event) {
     98         switch (event.getEventType()) {
     99             case SelectionEvent.ACTION_OVERTYPE:
    100                 return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE;
    101             case SelectionEvent.ACTION_COPY:
    102                 return MetricsEvent.ACTION_TEXT_SELECTION_COPY;
    103             case SelectionEvent.ACTION_PASTE:
    104                 return MetricsEvent.ACTION_TEXT_SELECTION_PASTE;
    105             case SelectionEvent.ACTION_CUT:
    106                 return MetricsEvent.ACTION_TEXT_SELECTION_CUT;
    107             case SelectionEvent.ACTION_SHARE:
    108                 return MetricsEvent.ACTION_TEXT_SELECTION_SHARE;
    109             case SelectionEvent.ACTION_SMART_SHARE:
    110                 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
    111             case SelectionEvent.ACTION_DRAG:
    112                 return MetricsEvent.ACTION_TEXT_SELECTION_DRAG;
    113             case SelectionEvent.ACTION_ABANDON:
    114                 return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON;
    115             case SelectionEvent.ACTION_OTHER:
    116                 return MetricsEvent.ACTION_TEXT_SELECTION_OTHER;
    117             case SelectionEvent.ACTION_SELECT_ALL:
    118                 return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL;
    119             case SelectionEvent.ACTION_RESET:
    120                 return MetricsEvent.ACTION_TEXT_SELECTION_RESET;
    121             case SelectionEvent.EVENT_SELECTION_STARTED:
    122                 return MetricsEvent.ACTION_TEXT_SELECTION_START;
    123             case SelectionEvent.EVENT_SELECTION_MODIFIED:
    124                 return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY;
    125             case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:
    126                 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE;
    127             case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
    128                 return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI;
    129             case SelectionEvent.EVENT_AUTO_SELECTION:
    130                 return MetricsEvent.ACTION_TEXT_SELECTION_AUTO;
    131             default:
    132                 return MetricsEvent.VIEW_UNKNOWN;
    133         }
    134     }
    135 
    136     private static int getLogSubType(SelectionEvent event) {
    137         switch (event.getInvocationMethod()) {
    138             case SelectionEvent.INVOCATION_MANUAL:
    139                 return MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL;
    140             case SelectionEvent.INVOCATION_LINK:
    141                 return MetricsEvent.TEXT_SELECTION_INVOCATION_LINK;
    142             default:
    143                 return MetricsEvent.TEXT_SELECTION_INVOCATION_UNKNOWN;
    144         }
    145     }
    146 
    147     private static String getLogTypeString(int logType) {
    148         switch (logType) {
    149             case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE:
    150                 return "OVERTYPE";
    151             case MetricsEvent.ACTION_TEXT_SELECTION_COPY:
    152                 return "COPY";
    153             case MetricsEvent.ACTION_TEXT_SELECTION_PASTE:
    154                 return "PASTE";
    155             case MetricsEvent.ACTION_TEXT_SELECTION_CUT:
    156                 return "CUT";
    157             case MetricsEvent.ACTION_TEXT_SELECTION_SHARE:
    158                 return "SHARE";
    159             case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
    160                 return "SMART_SHARE";
    161             case MetricsEvent.ACTION_TEXT_SELECTION_DRAG:
    162                 return "DRAG";
    163             case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON:
    164                 return "ABANDON";
    165             case MetricsEvent.ACTION_TEXT_SELECTION_OTHER:
    166                 return "OTHER";
    167             case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL:
    168                 return "SELECT_ALL";
    169             case MetricsEvent.ACTION_TEXT_SELECTION_RESET:
    170                 return "RESET";
    171             case MetricsEvent.ACTION_TEXT_SELECTION_START:
    172                 return "SELECTION_STARTED";
    173             case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY:
    174                 return "SELECTION_MODIFIED";
    175             case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE:
    176                 return "SMART_SELECTION_SINGLE";
    177             case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI:
    178                 return "SMART_SELECTION_MULTI";
    179             case MetricsEvent.ACTION_TEXT_SELECTION_AUTO:
    180                 return "AUTO_SELECTION";
    181             default:
    182                 return UNKNOWN;
    183         }
    184     }
    185 
    186     private static String getLogSubTypeString(int logSubType) {
    187         switch (logSubType) {
    188             case MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL:
    189                 return "MANUAL";
    190             case MetricsEvent.TEXT_SELECTION_INVOCATION_LINK:
    191                 return "LINK";
    192             default:
    193                 return UNKNOWN;
    194         }
    195     }
    196 
    197     private static void debugLog(LogMaker log) {
    198         if (!DEBUG_LOG_ENABLED) return;
    199 
    200         final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
    201         final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
    202         final String widget = widgetVersion.isEmpty()
    203                 ? widgetType : widgetType + "-" + widgetVersion;
    204         final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
    205         if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
    206             String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
    207             sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
    208             Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
    209         }
    210 
    211         final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
    212         final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
    213         final String type = getLogTypeString(log.getType());
    214         final String subType = getLogSubTypeString(log.getSubtype());
    215         final int smartStart = Integer.parseInt(
    216                 Objects.toString(log.getTaggedData(SMART_START), ZERO));
    217         final int smartEnd = Integer.parseInt(
    218                 Objects.toString(log.getTaggedData(SMART_END), ZERO));
    219         final int eventStart = Integer.parseInt(
    220                 Objects.toString(log.getTaggedData(EVENT_START), ZERO));
    221         final int eventEnd = Integer.parseInt(
    222                 Objects.toString(log.getTaggedData(EVENT_END), ZERO));
    223 
    224         Log.d(LOG_TAG,
    225                 String.format(Locale.US, "%2d: %s/%s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
    226                         index, type, subType, entity, eventStart, eventEnd, smartStart, smartEnd,
    227                         widget, model));
    228     }
    229 
    230     /**
    231      * Returns a token iterator for tokenizing text for logging purposes.
    232      */
    233     public static BreakIterator getTokenIterator(@NonNull Locale locale) {
    234         return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale));
    235     }
    236 
    237     /**
    238      * Creates a string id that may be used to identify a TextClassifier result.
    239      */
    240     public static String createId(
    241             String text, int start, int end, Context context, int modelVersion,
    242             List<Locale> locales) {
    243         Preconditions.checkNotNull(text);
    244         Preconditions.checkNotNull(context);
    245         Preconditions.checkNotNull(locales);
    246         final StringJoiner localesJoiner = new StringJoiner(",");
    247         for (Locale locale : locales) {
    248             localesJoiner.add(locale.toLanguageTag());
    249         }
    250         final String modelName = String.format(Locale.US, "%s_v%d", localesJoiner.toString(),
    251                 modelVersion);
    252         final int hash = Objects.hash(text, start, end, context.getPackageName());
    253         return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash);
    254     }
    255 
    256     /**
    257      * Helper for creating and parsing string ids for
    258      * {@link android.view.textclassifier.TextClassifierImpl}.
    259      */
    260     @VisibleForTesting
    261     public static final class SignatureParser {
    262 
    263         static String createSignature(String classifierId, String modelName, int hash) {
    264             return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash);
    265         }
    266 
    267         static String getClassifierId(@Nullable String signature) {
    268             if (signature == null) {
    269                 return "";
    270             }
    271             final int end = signature.indexOf("|");
    272             if (end >= 0) {
    273                 return signature.substring(0, end);
    274             }
    275             return "";
    276         }
    277 
    278         static String getModelName(@Nullable String signature) {
    279             if (signature == null) {
    280                 return "";
    281             }
    282             final int start = signature.indexOf("|") + 1;
    283             final int end = signature.indexOf("|", start);
    284             if (start >= 1 && end >= start) {
    285                 return signature.substring(start, end);
    286             }
    287             return "";
    288         }
    289 
    290         static int getHash(@Nullable String signature) {
    291             if (signature == null) {
    292                 return 0;
    293             }
    294             final int index1 = signature.indexOf("|");
    295             final int index2 = signature.indexOf("|", index1);
    296             if (index2 > 0) {
    297                 return Integer.parseInt(signature.substring(index2));
    298             }
    299             return 0;
    300         }
    301     }
    302 }
    303