• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.util.StatsEvent;
23 import android.util.StatsLog;
24 import android.view.textclassifier.TextClassifier;
25 import com.android.textclassifier.common.base.TcLog;
26 import com.android.textclassifier.common.logging.ResultIdUtils;
27 import com.android.textclassifier.common.logging.TextClassificationContext;
28 import com.android.textclassifier.common.logging.TextClassificationSessionId;
29 import com.android.textclassifier.common.logging.TextClassifierEvent;
30 import com.google.common.base.Preconditions;
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.hash.Hashing;
33 import java.util.List;
34 import javax.annotation.Nullable;
35 
36 /** Logs {@link android.view.textclassifier.TextClassifierEvent}. */
37 public final class TextClassifierEventLogger {
38   private static final String TAG = "TCEventLogger";
39   // These constants are defined in atoms.proto.
40   private static final int TEXT_SELECTION_EVENT_ATOM_ID = 219;
41   static final int TEXT_LINKIFY_EVENT_ATOM_ID = 220;
42   private static final int CONVERSATION_ACTIONS_EVENT_ATOM_ID = 221;
43   private static final int LANGUAGE_DETECTION_EVENT_ATOM_ID = 222;
44 
45   /** Emits a text classifier event to the logs. */
writeEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent event)46   public void writeEvent(
47       @Nullable TextClassificationSessionId sessionId, TextClassifierEvent event) {
48     Preconditions.checkNotNull(event);
49     if (TcLog.ENABLE_FULL_LOGGING) {
50       TcLog.v(
51           TAG,
52           String.format(
53               "TextClassifierEventLogger.writeEvent: sessionId=%s,event=%s", sessionId, event));
54     }
55     if (event instanceof TextClassifierEvent.TextSelectionEvent) {
56       logTextSelectionEvent(sessionId, (TextClassifierEvent.TextSelectionEvent) event);
57     } else if (event instanceof TextClassifierEvent.TextLinkifyEvent) {
58       logTextLinkifyEvent(sessionId, (TextClassifierEvent.TextLinkifyEvent) event);
59     } else if (event instanceof TextClassifierEvent.ConversationActionsEvent) {
60       logConversationActionsEvent(sessionId, (TextClassifierEvent.ConversationActionsEvent) event);
61     } else if (event instanceof TextClassifierEvent.LanguageDetectionEvent) {
62       logLanguageDetectionEvent(sessionId, (TextClassifierEvent.LanguageDetectionEvent) event);
63     } else {
64       TcLog.w(TAG, "Unexpected events, category=" + event.getEventCategory());
65     }
66   }
67 
logTextSelectionEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent.TextSelectionEvent event)68   private static void logTextSelectionEvent(
69       @Nullable TextClassificationSessionId sessionId,
70       TextClassifierEvent.TextSelectionEvent event) {
71     ImmutableList<String> modelNames = getModelNames(event);
72     StatsEvent statsEvent =
73         StatsEvent.newBuilder()
74             .setAtomId(TEXT_SELECTION_EVENT_ATOM_ID)
75             .writeString(sessionId == null ? null : sessionId.getValue())
76             .writeInt(getEventType(event))
77             .writeString(getItemAt(modelNames, /* index= */ 0, /* defaultValue= */ null))
78             .writeInt(getWidgetType(event))
79             .writeInt(event.getEventIndex())
80             .writeString(getItemAt(event.getEntityTypes(), /* index= */ 0))
81             .writeInt(event.getRelativeWordStartIndex())
82             .writeInt(event.getRelativeWordEndIndex())
83             .writeInt(event.getRelativeSuggestedWordStartIndex())
84             .writeInt(event.getRelativeSuggestedWordEndIndex())
85             .writeString(getPackageName(event))
86             .writeString(getItemAt(modelNames, /* index= */ 1, /* defaultValue= */ null))
87             .usePooledBuffer()
88             .build();
89     StatsLog.write(statsEvent);
90   }
91 
getEventType(TextClassifierEvent.TextSelectionEvent event)92   private static int getEventType(TextClassifierEvent.TextSelectionEvent event) {
93     if (event.getEventType() == TextClassifierEvent.TYPE_AUTO_SELECTION) {
94       if (ResultIdUtils.isFromDefaultTextClassifier(event.getResultId())) {
95         return event.getRelativeWordEndIndex() - event.getRelativeWordStartIndex() > 1
96             ? TextClassifierEvent.TYPE_SMART_SELECTION_MULTI
97             : TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE;
98       }
99     }
100     return event.getEventType();
101   }
102 
logTextLinkifyEvent( TextClassificationSessionId sessionId, TextClassifierEvent.TextLinkifyEvent event)103   private static void logTextLinkifyEvent(
104       TextClassificationSessionId sessionId, TextClassifierEvent.TextLinkifyEvent event) {
105     ImmutableList<String> modelNames = getModelNames(event);
106     StatsEvent statsEvent =
107         StatsEvent.newBuilder()
108             .setAtomId(TEXT_LINKIFY_EVENT_ATOM_ID)
109             .writeString(sessionId == null ? null : sessionId.getValue())
110             .writeInt(event.getEventType())
111             .writeString(getItemAt(modelNames, /* index= */ 0, /* defaultValue= */ null))
112             .writeInt(getWidgetType(event))
113             .writeInt(event.getEventIndex())
114             .writeString(getItemAt(event.getEntityTypes(), /* index= */ 0))
115             .writeInt(/* numOfLinks */ 0)
116             .writeInt(/* linkedTextLength */ 0)
117             .writeInt(/* textLength */ 0)
118             .writeLong(/* latencyInMillis */ 0L)
119             .writeString(getPackageName(event))
120             .writeString(getItemAt(modelNames, /* index= */ 1, /* defaultValue= */ null))
121             .usePooledBuffer()
122             .build();
123     StatsLog.write(statsEvent);
124   }
125 
logConversationActionsEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent.ConversationActionsEvent event)126   private static void logConversationActionsEvent(
127       @Nullable TextClassificationSessionId sessionId,
128       TextClassifierEvent.ConversationActionsEvent event) {
129     String resultId = nullToEmpty(event.getResultId());
130     ImmutableList<String> modelNames = ResultIdUtils.getModelNames(resultId);
131     StatsEvent statsEvent =
132         StatsEvent.newBuilder()
133             .setAtomId(CONVERSATION_ACTIONS_EVENT_ATOM_ID)
134             // TODO: Update ExtServices to set the session id.
135             .writeString(
136                 sessionId == null
137                     ? Hashing.goodFastHash(64).hashString(resultId, UTF_8).toString()
138                     : sessionId.getValue())
139             .writeInt(event.getEventType())
140             .writeString(getItemAt(modelNames, /* index= */ 0, /* defaultValue= */ null))
141             .writeInt(getWidgetType(event))
142             .writeString(getItemAt(event.getEntityTypes(), /* index= */ 0))
143             .writeString(getItemAt(event.getEntityTypes(), /* index= */ 1))
144             .writeString(getItemAt(event.getEntityTypes(), /* index= */ 2))
145             .writeFloat(getFloatAt(event.getScores(), /* index= */ 0))
146             .writeString(getPackageName(event))
147             .writeString(getItemAt(modelNames, /* index= */ 1, /* defaultValue= */ null))
148             .writeString(getItemAt(modelNames, /* index= */ 2, /* defaultValue= */ null))
149             .usePooledBuffer()
150             .build();
151     StatsLog.write(statsEvent);
152   }
153 
logLanguageDetectionEvent( @ullable TextClassificationSessionId sessionId, TextClassifierEvent.LanguageDetectionEvent event)154   private static void logLanguageDetectionEvent(
155       @Nullable TextClassificationSessionId sessionId,
156       TextClassifierEvent.LanguageDetectionEvent event) {
157     StatsEvent statsEvent =
158         StatsEvent.newBuilder()
159             .setAtomId(LANGUAGE_DETECTION_EVENT_ATOM_ID)
160             .writeString(sessionId == null ? null : sessionId.getValue())
161             .writeInt(event.getEventType())
162             .writeString(getItemAt(getModelNames(event), /* index= */ 0, /* defaultValue= */ null))
163             .writeInt(getWidgetType(event))
164             .writeString(getItemAt(event.getEntityTypes(), /* index= */ 0))
165             .writeFloat(getFloatAt(event.getScores(), /* index= */ 0))
166             .writeInt(getIntAt(event.getActionIndices(), /* index= */ 0))
167             .writeString(getPackageName(event))
168             .usePooledBuffer()
169             .build();
170     StatsLog.write(statsEvent);
171   }
172 
173   @Nullable
getItemAt(List<T> list, int index, T defaultValue)174   private static <T> T getItemAt(List<T> list, int index, T defaultValue) {
175     if (list == null) {
176       return defaultValue;
177     }
178     if (index >= list.size()) {
179       return defaultValue;
180     }
181     return list.get(index);
182   }
183 
184   @Nullable
getItemAt(@ullable T[] array, int index)185   private static <T> T getItemAt(@Nullable T[] array, int index) {
186     if (array == null) {
187       return null;
188     }
189     if (index >= array.length) {
190       return null;
191     }
192     return array[index];
193   }
194 
getFloatAt(@ullable float[] array, int index)195   private static float getFloatAt(@Nullable float[] array, int index) {
196     if (array == null) {
197       return 0f;
198     }
199     if (index >= array.length) {
200       return 0f;
201     }
202     return array[index];
203   }
204 
getIntAt(@ullable int[] array, int index)205   private static int getIntAt(@Nullable int[] array, int index) {
206     if (array == null) {
207       return 0;
208     }
209     if (index >= array.length) {
210       return 0;
211     }
212     return array[index];
213   }
214 
getModelNames(TextClassifierEvent event)215   private static ImmutableList<String> getModelNames(TextClassifierEvent event) {
216     if (event.getModelName() != null) {
217       return ImmutableList.of(event.getModelName());
218     }
219     return ResultIdUtils.getModelNames(event.getResultId());
220   }
221 
222   @Nullable
getPackageName(TextClassifierEvent event)223   private static String getPackageName(TextClassifierEvent event) {
224     TextClassificationContext eventContext = event.getEventContext();
225     if (eventContext == null) {
226       return null;
227     }
228     return eventContext.getPackageName();
229   }
230 
getWidgetType(TextClassifierEvent event)231   private static int getWidgetType(TextClassifierEvent event) {
232     TextClassificationContext eventContext = event.getEventContext();
233     if (eventContext == null) {
234       return WidgetType.WIDGET_TYPE_UNKNOWN;
235     }
236     switch (eventContext.getWidgetType()) {
237       case TextClassifier.WIDGET_TYPE_UNKNOWN:
238         return WidgetType.WIDGET_TYPE_UNKNOWN;
239       case TextClassifier.WIDGET_TYPE_TEXTVIEW:
240         return WidgetType.WIDGET_TYPE_TEXTVIEW;
241       case TextClassifier.WIDGET_TYPE_EDITTEXT:
242         return WidgetType.WIDGET_TYPE_EDITTEXT;
243       case TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW:
244         return WidgetType.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
245       case TextClassifier.WIDGET_TYPE_WEBVIEW:
246         return WidgetType.WIDGET_TYPE_WEBVIEW;
247       case TextClassifier.WIDGET_TYPE_EDIT_WEBVIEW:
248         return WidgetType.WIDGET_TYPE_EDIT_WEBVIEW;
249       case TextClassifier.WIDGET_TYPE_CUSTOM_TEXTVIEW:
250         return WidgetType.WIDGET_TYPE_CUSTOM_TEXTVIEW;
251       case TextClassifier.WIDGET_TYPE_CUSTOM_EDITTEXT:
252         return WidgetType.WIDGET_TYPE_CUSTOM_EDITTEXT;
253       case TextClassifier.WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW:
254         return WidgetType.WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW;
255       case TextClassifier.WIDGET_TYPE_NOTIFICATION:
256         return WidgetType.WIDGET_TYPE_NOTIFICATION;
257       default: // fall out
258     }
259     return WidgetType.WIDGET_TYPE_UNKNOWN;
260   }
261 
262   /** Widget type constants for logging. */
263   public static final class WidgetType {
264     // Sync these constants with textclassifier_enums.proto.
265     public static final int WIDGET_TYPE_UNKNOWN = 0;
266     public static final int WIDGET_TYPE_TEXTVIEW = 1;
267     public static final int WIDGET_TYPE_EDITTEXT = 2;
268     public static final int WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = 3;
269     public static final int WIDGET_TYPE_WEBVIEW = 4;
270     public static final int WIDGET_TYPE_EDIT_WEBVIEW = 5;
271     public static final int WIDGET_TYPE_CUSTOM_TEXTVIEW = 6;
272     public static final int WIDGET_TYPE_CUSTOM_EDITTEXT = 7;
273     public static final int WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = 8;
274     public static final int WIDGET_TYPE_NOTIFICATION = 9;
275 
WidgetType()276     private WidgetType() {}
277   }
278 }
279