• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.intentresolver.logging;
18 
19 import android.content.Intent;
20 import android.metrics.LogMaker;
21 import android.net.Uri;
22 import android.provider.MediaStore;
23 import android.util.HashedStringCache;
24 import android.util.Log;
25 
26 import androidx.annotation.Nullable;
27 
28 import com.android.intentresolver.ChooserActivity;
29 import com.android.intentresolver.contentpreview.ContentPreviewType;
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.logging.InstanceId;
32 import com.android.internal.logging.InstanceIdSequence;
33 import com.android.internal.logging.MetricsLogger;
34 import com.android.internal.logging.UiEvent;
35 import com.android.internal.logging.UiEventLogger;
36 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
37 import com.android.internal.util.FrameworkStatsLog;
38 
39 import javax.inject.Inject;
40 
41 /**
42  * Helper for writing Sharesheet atoms to statsd log.
43  */
44 public class EventLogImpl implements EventLog {
45     private static final String TAG = "ChooserActivity";
46     private static final boolean DEBUG = true;
47 
48     private static final int SHARESHEET_INSTANCE_ID_MAX = (1 << 13);
49 
50     private final InstanceId mInstanceId;
51 
52     private final UiEventLogger mUiEventLogger;
53     private final FrameworkStatsLogger mFrameworkStatsLogger;
54     private final MetricsLogger mMetricsLogger;
55 
newIdSequence()56     public static InstanceIdSequence newIdSequence() {
57         return new InstanceIdSequence(SHARESHEET_INSTANCE_ID_MAX);
58     }
59 
60     @Inject
EventLogImpl(UiEventLogger uiEventLogger, FrameworkStatsLogger frameworkLogger, MetricsLogger metricsLogger, InstanceId instanceId)61     public EventLogImpl(UiEventLogger uiEventLogger, FrameworkStatsLogger frameworkLogger,
62                         MetricsLogger metricsLogger, InstanceId instanceId) {
63         mUiEventLogger = uiEventLogger;
64         mFrameworkStatsLogger = frameworkLogger;
65         mMetricsLogger = metricsLogger;
66         mInstanceId = instanceId;
67     }
68 
69 
70     /** Records metrics for the start time of the {@link ChooserActivity}. */
71     @Override
logChooserActivityShown( boolean isWorkProfile, String targetMimeType, long systemCost)72     public void logChooserActivityShown(
73             boolean isWorkProfile, String targetMimeType, long systemCost) {
74         mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
75                 .setSubtype(
76                         isWorkProfile ? MetricsEvent.MANAGED_PROFILE : MetricsEvent.PARENT_PROFILE)
77                 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, targetMimeType)
78                 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
79     }
80 
81     /** Logs a UiEventReported event for the system sharesheet completing initial start-up. */
82     @Override
logShareStarted( String packageName, String mimeType, int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType, String intent, int customActionCount, boolean modifyShareActionProvided)83     public void logShareStarted(
84             String packageName,
85             String mimeType,
86             int appProvidedDirect,
87             int appProvidedApp,
88             boolean isWorkprofile,
89             int previewType,
90             String intent,
91             int customActionCount,
92             boolean modifyShareActionProvided) {
93         mFrameworkStatsLogger.write(FrameworkStatsLog.SHARESHEET_STARTED,
94                 /* event_id = 1 */ SharesheetStartedEvent.SHARE_STARTED.getId(),
95                 /* package_name = 2 */ packageName,
96                 /* instance_id = 3 */ mInstanceId.getId(),
97                 /* mime_type = 4 */ mimeType,
98                 /* num_app_provided_direct_targets = 5 */ appProvidedDirect,
99                 /* num_app_provided_app_targets = 6 */ appProvidedApp,
100                 /* is_workprofile = 7 */ isWorkprofile,
101                 /* previewType = 8 */ typeFromPreviewInt(previewType),
102                 /* intentType = 9 */ typeFromIntentString(intent),
103                 /* num_provided_custom_actions = 10 */ customActionCount,
104                 /* modify_share_action_provided = 11 */ modifyShareActionProvided);
105     }
106 
107     /**
108      * Log that a custom action has been tapped by the user.
109      *
110      * @param positionPicked index of the custom action within the list of custom actions.
111      */
112     @Override
logCustomActionSelected(int positionPicked)113     public void logCustomActionSelected(int positionPicked) {
114         mFrameworkStatsLogger.write(FrameworkStatsLog.RANKING_SELECTED,
115                 /* event_id = 1 */
116                 SharesheetTargetSelectedEvent.SHARESHEET_CUSTOM_ACTION_SELECTED.getId(),
117                 /* package_name = 2 */ null,
118                 /* instance_id = 3 */ mInstanceId.getId(),
119                 /* position_picked = 4 */ positionPicked,
120                 /* is_pinned = 5 */ false);
121     }
122 
123     /**
124      * Logs a UiEventReported event for the system sharesheet when the user selects a target.
125      * TODO: document parameters and/or consider breaking up by targetType so we don't have to
126      * support an overly-generic signature.
127      */
128     @Override
logShareTargetSelected( int targetType, String packageName, int positionPicked, int directTargetAlsoRanked, int numCallerProvided, @Nullable HashedStringCache.HashResult directTargetHashed, boolean isPinned, boolean successfullySelected, long selectionCost)129     public void logShareTargetSelected(
130             int targetType,
131             String packageName,
132             int positionPicked,
133             int directTargetAlsoRanked,
134             int numCallerProvided,
135             @Nullable HashedStringCache.HashResult directTargetHashed,
136             boolean isPinned,
137             boolean successfullySelected,
138             long selectionCost) {
139         mFrameworkStatsLogger.write(FrameworkStatsLog.RANKING_SELECTED,
140                 /* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(),
141                 /* package_name = 2 */ packageName,
142                 /* instance_id = 3 */ mInstanceId.getId(),
143                 /* position_picked = 4 */ positionPicked,
144                 /* is_pinned = 5 */ isPinned);
145 
146         int category = getTargetSelectionCategory(targetType);
147         if (category != 0) {
148             LogMaker targetLogMaker = new LogMaker(category).setSubtype(positionPicked);
149             if (directTargetHashed != null) {
150                 targetLogMaker.addTaggedData(
151                         MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
152                 targetLogMaker.addTaggedData(
153                                 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
154                                 directTargetHashed.saltGeneration);
155                 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
156                                 directTargetAlsoRanked);
157             }
158             targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, numCallerProvided);
159             mMetricsLogger.write(targetLogMaker);
160         }
161 
162         if (successfullySelected) {
163             if (DEBUG) {
164                 Log.d(TAG, "User Selection Time Cost is " + selectionCost);
165                 Log.d(TAG, "position of selected app/service/caller is " + positionPicked);
166             }
167             MetricsLogger.histogram(
168                     null, "user_selection_cost_for_smart_sharing", (int) selectionCost);
169             MetricsLogger.histogram(null, "app_position_for_smart_sharing", positionPicked);
170         }
171     }
172 
173     /** Log when direct share targets were received. */
174     @Override
logDirectShareTargetReceived(int category, int latency)175     public void logDirectShareTargetReceived(int category, int latency) {
176         mMetricsLogger.write(new LogMaker(category).setSubtype(latency));
177     }
178 
179     /**
180      * Log when we display a preview UI of the specified {@code previewType} as part of our
181      * Sharesheet session.
182      */
183     @Override
logActionShareWithPreview(int previewType)184     public void logActionShareWithPreview(int previewType) {
185         mMetricsLogger.write(
186                 new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW).setSubtype(previewType));
187     }
188 
189     /** Log when the user selects an action button with the specified {@code targetType}. */
190     @Override
logActionSelected(int targetType)191     public void logActionSelected(int targetType) {
192         if (targetType == SELECTION_TYPE_COPY) {
193             LogMaker targetLogMaker = new LogMaker(
194                     MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
195             mMetricsLogger.write(targetLogMaker);
196         }
197         mFrameworkStatsLogger.write(FrameworkStatsLog.RANKING_SELECTED,
198                 /* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(),
199                 /* package_name = 2 */ "",
200                 /* instance_id = 3 */ mInstanceId.getId(),
201                 /* position_picked = 4 */ -1,
202                 /* is_pinned = 5 */ false);
203     }
204 
205     /** Log a warning that we couldn't display the content preview from the supplied {@code uri}. */
206     @Override
logContentPreviewWarning(Uri uri)207     public void logContentPreviewWarning(Uri uri) {
208         // The ContentResolver already logs the exception. Log something more informative.
209         Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
210                 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
211                 + "and set your Intent's clipData and flags in accordance with that method's "
212                 + "documentation");
213 
214     }
215 
216     /** Logs a UiEventReported event for the system sharesheet being triggered by the user. */
217     @Override
logSharesheetTriggered()218     public void logSharesheetTriggered() {
219         log(SharesheetStandardEvent.SHARESHEET_TRIGGERED, mInstanceId);
220     }
221 
222     /** Logs a UiEventReported event for the system sharesheet completing loading app targets. */
223     @Override
logSharesheetAppLoadComplete()224     public void logSharesheetAppLoadComplete() {
225         log(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE, mInstanceId);
226     }
227 
228     /**
229      * Logs a UiEventReported event for the system sharesheet completing loading service targets.
230      */
231     @Override
logSharesheetDirectLoadComplete()232     public void logSharesheetDirectLoadComplete() {
233         log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE, mInstanceId);
234     }
235 
236     /**
237      * Logs a UiEventReported event for the system sharesheet timing out loading service targets.
238      */
239     @Override
logSharesheetDirectLoadTimeout()240     public void logSharesheetDirectLoadTimeout() {
241         log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT, mInstanceId);
242     }
243 
244     /**
245      * Logs a UiEventReported event for the system sharesheet switching
246      * between work and main profile.
247      */
248     @Override
logSharesheetProfileChanged()249     public void logSharesheetProfileChanged() {
250         log(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED, mInstanceId);
251     }
252 
253     /** Logs a UiEventReported event for the system sharesheet getting expanded or collapsed. */
254     @Override
logSharesheetExpansionChanged(boolean isCollapsed)255     public void logSharesheetExpansionChanged(boolean isCollapsed) {
256         log(isCollapsed ? SharesheetStandardEvent.SHARESHEET_COLLAPSED :
257                 SharesheetStandardEvent.SHARESHEET_EXPANDED, mInstanceId);
258     }
259 
260     /**
261      * Logs a UiEventReported event for the system sharesheet app share ranking timing out.
262      */
263     @Override
logSharesheetAppShareRankingTimeout()264     public void logSharesheetAppShareRankingTimeout() {
265         log(SharesheetStandardEvent.SHARESHEET_APP_SHARE_RANKING_TIMEOUT, mInstanceId);
266     }
267 
268     /**
269      * Logs a UiEventReported event for the system sharesheet when direct share row is empty.
270      */
271     @Override
logSharesheetEmptyDirectShareRow()272     public void logSharesheetEmptyDirectShareRow() {
273         log(SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW, mInstanceId);
274     }
275 
276     /**
277      * Logs a UiEventReported event for a given share activity
278      * @param event
279      * @param instanceId
280      */
log(UiEventLogger.UiEventEnum event, InstanceId instanceId)281     private void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
282         mUiEventLogger.logWithInstanceId(
283                 event,
284                 0,
285                 null,
286                 instanceId);
287     }
288 
289     /**
290      * The UiEvent enums that this class can log.
291      */
292     enum SharesheetStartedEvent implements UiEventLogger.UiEventEnum {
293         @UiEvent(doc = "Basic system Sharesheet has started and is visible.")
294         SHARE_STARTED(228);
295 
296         private final int mId;
SharesheetStartedEvent(int id)297         SharesheetStartedEvent(int id) {
298             mId = id;
299         }
300         @Override
getId()301         public int getId() {
302             return mId;
303         }
304     }
305 
306     /**
307      * The UiEvent enums that this class can log.
308      */
309     enum SharesheetTargetSelectedEvent implements UiEventLogger.UiEventEnum {
310         INVALID(0),
311         @UiEvent(doc = "User selected a service target.")
312         SHARESHEET_SERVICE_TARGET_SELECTED(232),
313         @UiEvent(doc = "User selected an app target.")
314         SHARESHEET_APP_TARGET_SELECTED(233),
315         @UiEvent(doc = "User selected a standard target.")
316         SHARESHEET_STANDARD_TARGET_SELECTED(234),
317         @UiEvent(doc = "User selected the copy target.")
318         SHARESHEET_COPY_TARGET_SELECTED(235),
319         @UiEvent(doc = "User selected the nearby target.")
320         SHARESHEET_NEARBY_TARGET_SELECTED(626),
321         @UiEvent(doc = "User selected the edit target.")
322         SHARESHEET_EDIT_TARGET_SELECTED(669),
323         @UiEvent(doc = "User selected the modify share target.")
324         SHARESHEET_MODIFY_SHARE_SELECTED(1316),
325         @UiEvent(doc = "User selected a custom action.")
326         SHARESHEET_CUSTOM_ACTION_SELECTED(1317);
327 
328         private final int mId;
SharesheetTargetSelectedEvent(int id)329         SharesheetTargetSelectedEvent(int id) {
330             mId = id;
331         }
getId()332         @Override public int getId() {
333             return mId;
334         }
335 
fromTargetType(int targetType)336         public static SharesheetTargetSelectedEvent fromTargetType(int targetType) {
337             switch(targetType) {
338                 case SELECTION_TYPE_SERVICE:
339                     return SHARESHEET_SERVICE_TARGET_SELECTED;
340                 case SELECTION_TYPE_APP:
341                     return SHARESHEET_APP_TARGET_SELECTED;
342                 case SELECTION_TYPE_STANDARD:
343                     return SHARESHEET_STANDARD_TARGET_SELECTED;
344                 case SELECTION_TYPE_COPY:
345                     return SHARESHEET_COPY_TARGET_SELECTED;
346                 case SELECTION_TYPE_NEARBY:
347                     return SHARESHEET_NEARBY_TARGET_SELECTED;
348                 case SELECTION_TYPE_EDIT:
349                     return SHARESHEET_EDIT_TARGET_SELECTED;
350                 case SELECTION_TYPE_MODIFY_SHARE:
351                     return SHARESHEET_MODIFY_SHARE_SELECTED;
352                 case SELECTION_TYPE_CUSTOM_ACTION:
353                     return SHARESHEET_CUSTOM_ACTION_SELECTED;
354                 default:
355                     return INVALID;
356             }
357         }
358     }
359 
360     /**
361      * The UiEvent enums that this class can log.
362      */
363     enum SharesheetStandardEvent implements UiEventLogger.UiEventEnum {
364         INVALID(0),
365         @UiEvent(doc = "User clicked share.")
366         SHARESHEET_TRIGGERED(227),
367         @UiEvent(doc = "User changed from work to personal profile or vice versa.")
368         SHARESHEET_PROFILE_CHANGED(229),
369         @UiEvent(doc = "User expanded target list.")
370         SHARESHEET_EXPANDED(230),
371         @UiEvent(doc = "User collapsed target list.")
372         SHARESHEET_COLLAPSED(231),
373         @UiEvent(doc = "Sharesheet app targets is fully populated.")
374         SHARESHEET_APP_LOAD_COMPLETE(322),
375         @UiEvent(doc = "Sharesheet direct targets is fully populated.")
376         SHARESHEET_DIRECT_LOAD_COMPLETE(323),
377         @UiEvent(doc = "Sharesheet direct targets timed out.")
378         SHARESHEET_DIRECT_LOAD_TIMEOUT(324),
379         @UiEvent(doc = "Sharesheet app share ranking timed out.")
380         SHARESHEET_APP_SHARE_RANKING_TIMEOUT(831),
381         @UiEvent(doc = "Sharesheet empty direct share row.")
382         SHARESHEET_EMPTY_DIRECT_SHARE_ROW(828),
383         @UiEvent(doc = "Shareousel payload item toggled")
384         SHARESHEET_PAYLOAD_TOGGLED(1662);
385 
386         private final int mId;
SharesheetStandardEvent(int id)387         SharesheetStandardEvent(int id) {
388             mId = id;
389         }
getId()390         @Override public int getId() {
391             return mId;
392         }
393     }
394 
395     /**
396      * Returns the enum used in sharesheet started atom to indicate what preview type was used.
397      */
typeFromPreviewInt(int previewType)398     private static int typeFromPreviewInt(int previewType) {
399         switch(previewType) {
400             case ContentPreviewType.CONTENT_PREVIEW_IMAGE:
401                 return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_IMAGE;
402             case ContentPreviewType.CONTENT_PREVIEW_FILE:
403                 return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE;
404             case ContentPreviewType.CONTENT_PREVIEW_TEXT:
405             default:
406                 return FrameworkStatsLog
407                         .SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TYPE_UNKNOWN;
408         }
409     }
410 
411     /**
412      * Returns the enum used in sharesheet started atom to indicate what intent triggers the
413      * ChooserActivity.
414      */
typeFromIntentString(String intent)415     private static int typeFromIntentString(String intent) {
416         if (intent == null) {
417             return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT;
418         }
419         switch (intent) {
420             case Intent.ACTION_VIEW:
421                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_VIEW;
422             case Intent.ACTION_EDIT:
423                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_EDIT;
424             case Intent.ACTION_SEND:
425                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND;
426             case Intent.ACTION_SENDTO:
427                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO;
428             case Intent.ACTION_SEND_MULTIPLE:
429                 return FrameworkStatsLog
430                         .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND_MULTIPLE;
431             case MediaStore.ACTION_IMAGE_CAPTURE:
432                 return FrameworkStatsLog
433                         .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_IMAGE_CAPTURE;
434             case Intent.ACTION_MAIN:
435                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_MAIN;
436             default:
437                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT;
438         }
439     }
440 
441     @VisibleForTesting
getTargetSelectionCategory(int targetType)442     static int getTargetSelectionCategory(int targetType) {
443         switch (targetType) {
444             case SELECTION_TYPE_SERVICE:
445                 return MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
446             case SELECTION_TYPE_APP:
447                 return MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
448             case SELECTION_TYPE_STANDARD:
449                 return MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
450             default:
451                 return 0;
452         }
453     }
454 }
455