1 /*
2  * Copyright (C) 2018 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 com.android.textclassifier.common.statsd;
18 
19 import static com.google.common.base.Charsets.UTF_8;
20 import static com.google.common.base.Strings.nullToEmpty;
21 
22 import com.android.textclassifier.common.base.TcLog;
23 import com.android.textclassifier.common.logging.ResultIdUtils;
24 import com.android.textclassifier.common.logging.TextClassificationContext;
25 import com.android.textclassifier.common.logging.TextClassificationSessionId;
26 import com.android.textclassifier.common.logging.TextClassifierEvent;
27 import com.google.common.base.Preconditions;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.hash.Hashing;
30 import java.util.List;
31 import javax.annotation.Nullable;
32 
33 /** Logs {@link android.view.textclassifier.TextClassifierEvent}. */
34 public final class TextClassifierEventLogger {
35   private static final String TAG = "TCEventLogger";
36 
37   /** Emits a text classifier event to the logs. */
writeEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent event)38   public void writeEvent(
39       @Nullable TextClassificationSessionId sessionId, TextClassifierEvent event) {
40     Preconditions.checkNotNull(event);
41     if (TcLog.ENABLE_FULL_LOGGING) {
42       TcLog.v(
43           TAG,
44           String.format(
45               "TextClassifierEventLogger.writeEvent: sessionId=%s,event=%s", sessionId, event));
46     }
47     if (event instanceof TextClassifierEvent.TextSelectionEvent) {
48       logTextSelectionEvent(sessionId, (TextClassifierEvent.TextSelectionEvent) event);
49     } else if (event instanceof TextClassifierEvent.TextLinkifyEvent) {
50       logTextLinkifyEvent(sessionId, (TextClassifierEvent.TextLinkifyEvent) event);
51     } else if (event instanceof TextClassifierEvent.ConversationActionsEvent) {
52       logConversationActionsEvent(sessionId, (TextClassifierEvent.ConversationActionsEvent) event);
53     } else if (event instanceof TextClassifierEvent.LanguageDetectionEvent) {
54       logLanguageDetectionEvent(sessionId, (TextClassifierEvent.LanguageDetectionEvent) event);
55     } else {
56       TcLog.w(TAG, "Unexpected events, category=" + event.getEventCategory());
57     }
58   }
59 
logTextSelectionEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent.TextSelectionEvent event)60   private static void logTextSelectionEvent(
61       @Nullable TextClassificationSessionId sessionId,
62       TextClassifierEvent.TextSelectionEvent event) {
63     ImmutableList<String> modelNames = getModelNames(event);
64     TextClassifierStatsLog.write(
65         TextClassifierStatsLog.TEXT_SELECTION_EVENT,
66         sessionId == null ? null : sessionId.getValue(),
67         getEventType(event),
68         getItemAt(modelNames, /* index= */ 0, /* defaultValue= */ null),
69         getWidgetType(event),
70         event.getEventIndex(),
71         getItemAt(event.getEntityTypes(), /* index= */ 0),
72         event.getRelativeWordStartIndex(),
73         event.getRelativeWordEndIndex(),
74         event.getRelativeSuggestedWordStartIndex(),
75         event.getRelativeSuggestedWordEndIndex(),
76         getPackageName(event),
77         getItemAt(modelNames, /* index= */ 1, /* defaultValue= */ null));
78   }
79 
getEventType(TextClassifierEvent.TextSelectionEvent event)80   private static int getEventType(TextClassifierEvent.TextSelectionEvent event) {
81     if (event.getEventType() == TextClassifierEvent.TYPE_AUTO_SELECTION) {
82       if (ResultIdUtils.isFromDefaultTextClassifier(event.getResultId())) {
83         return event.getRelativeWordEndIndex() - event.getRelativeWordStartIndex() > 1
84             ? TextClassifierEvent.TYPE_SMART_SELECTION_MULTI
85             : TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE;
86       }
87     }
88     return event.getEventType();
89   }
90 
logTextLinkifyEvent( TextClassificationSessionId sessionId, TextClassifierEvent.TextLinkifyEvent event)91   private static void logTextLinkifyEvent(
92       TextClassificationSessionId sessionId, TextClassifierEvent.TextLinkifyEvent event) {
93     ImmutableList<String> modelNames = getModelNames(event);
94     TextClassifierStatsLog.write(
95         TextClassifierStatsLog.TEXT_LINKIFY_EVENT,
96         sessionId == null ? null : sessionId.getValue(),
97         event.getEventType(),
98         getItemAt(modelNames, /* index= */ 0, /* defaultValue= */ null),
99         getWidgetType(event),
100         event.getEventIndex(),
101         getItemAt(event.getEntityTypes(), /* index= */ 0),
102         /* numOfLinks */ 0,
103         /* linkedTextLength */ 0,
104         /* textLength */ 0,
105         /* latencyInMillis */ 0L,
106         getPackageName(event),
107         getItemAt(modelNames, /* index= */ 1, /* defaultValue= */ null));
108   }
109 
logConversationActionsEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent.ConversationActionsEvent event)110   private static void logConversationActionsEvent(
111       @Nullable TextClassificationSessionId sessionId,
112       TextClassifierEvent.ConversationActionsEvent event) {
113     String resultId = nullToEmpty(event.getResultId());
114     ImmutableList<String> modelNames = ResultIdUtils.getModelNames(resultId);
115     TextClassifierStatsLog.write(
116         TextClassifierStatsLog.CONVERSATION_ACTIONS_EVENT,
117         // TODO: Update ExtServices to set the session id.
118         sessionId == null
119             ? Hashing.goodFastHash(64).hashString(resultId, UTF_8).toString()
120             : sessionId.getValue(),
121         event.getEventType(),
122         getItemAt(modelNames, /* index= */ 0, /* defaultValue= */ null),
123         getWidgetType(event),
124         getItemAt(event.getEntityTypes(), /* index= */ 0),
125         getItemAt(event.getEntityTypes(), /* index= */ 1),
126         getItemAt(event.getEntityTypes(), /* index= */ 2),
127         getFloatAt(event.getScores(), /* index= */ 0),
128         getPackageName(event),
129         getItemAt(modelNames, /* index= */ 1, /* defaultValue= */ null),
130         getItemAt(modelNames, /* index= */ 2, /* defaultValue= */ null));
131   }
132 
logLanguageDetectionEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent.LanguageDetectionEvent event)133   private static void logLanguageDetectionEvent(
134       @Nullable TextClassificationSessionId sessionId,
135       TextClassifierEvent.LanguageDetectionEvent event) {
136     TextClassifierStatsLog.write(
137         TextClassifierStatsLog.LANGUAGE_DETECTION_EVENT,
138         sessionId == null ? null : sessionId.getValue(),
139         event.getEventType(),
140         getItemAt(getModelNames(event), /* index= */ 0, /* defaultValue= */ null),
141         getWidgetType(event),
142         getItemAt(event.getEntityTypes(), /* index= */ 0),
143         getFloatAt(event.getScores(), /* index= */ 0),
144         getIntAt(event.getActionIndices(), /* index= */ 0),
145         getPackageName(event));
146   }
147 
148   @Nullable
getItemAt(List<T> list, int index, T defaultValue)149   private static <T> T getItemAt(List<T> list, int index, T defaultValue) {
150     if (list == null) {
151       return defaultValue;
152     }
153     if (index >= list.size()) {
154       return defaultValue;
155     }
156     return list.get(index);
157   }
158 
159   @Nullable
getItemAt(@ullable T[] array, int index)160   private static <T> T getItemAt(@Nullable T[] array, int index) {
161     if (array == null) {
162       return null;
163     }
164     if (index >= array.length) {
165       return null;
166     }
167     return array[index];
168   }
169 
getFloatAt(@ullable float[] array, int index)170   private static float getFloatAt(@Nullable float[] array, int index) {
171     if (array == null) {
172       return 0f;
173     }
174     if (index >= array.length) {
175       return 0f;
176     }
177     return array[index];
178   }
179 
getIntAt(@ullable int[] array, int index)180   private static int getIntAt(@Nullable int[] array, int index) {
181     if (array == null) {
182       return 0;
183     }
184     if (index >= array.length) {
185       return 0;
186     }
187     return array[index];
188   }
189 
getModelNames(TextClassifierEvent event)190   private static ImmutableList<String> getModelNames(TextClassifierEvent event) {
191     if (event.getModelName() != null) {
192       return ImmutableList.of(event.getModelName());
193     }
194     return ResultIdUtils.getModelNames(event.getResultId());
195   }
196 
getWidgetType(TextClassifierEvent event)197   private static int getWidgetType(TextClassifierEvent event) {
198     TextClassificationContext eventContext = event.getEventContext();
199     if (eventContext == null) {
200       return TextClassifierStatsLog.TEXT_SELECTION_EVENT__WIDGET_TYPE__WIDGET_TYPE_UNKNOWN;
201     }
202     return WidgetTypeConverter.toLoggingValue(eventContext.getWidgetType());
203   }
204 
205   @Nullable
getPackageName(TextClassifierEvent event)206   private static String getPackageName(TextClassifierEvent event) {
207     TextClassificationContext eventContext = event.getEventContext();
208     if (eventContext == null) {
209       return null;
210     }
211     return eventContext.getPackageName();
212   }
213 }
214