• 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     @Override
logPayloadSelectionChanged()277     public void logPayloadSelectionChanged() {
278         log(SharesheetStandardEvent.SHARESHEET_PAYLOAD_TOGGLED, mInstanceId);
279     }
280 
281     /**
282      * Logs a UiEventReported event for a given share activity
283      * @param event
284      * @param instanceId
285      */
log(UiEventLogger.UiEventEnum event, InstanceId instanceId)286     private void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
287         mUiEventLogger.logWithInstanceId(
288                 event,
289                 0,
290                 null,
291                 instanceId);
292     }
293 
294     /**
295      * The UiEvent enums that this class can log.
296      */
297     enum SharesheetStartedEvent implements UiEventLogger.UiEventEnum {
298         @UiEvent(doc = "Basic system Sharesheet has started and is visible.")
299         SHARE_STARTED(228);
300 
301         private final int mId;
SharesheetStartedEvent(int id)302         SharesheetStartedEvent(int id) {
303             mId = id;
304         }
305         @Override
getId()306         public int getId() {
307             return mId;
308         }
309     }
310 
311     /**
312      * The UiEvent enums that this class can log.
313      */
314     enum SharesheetTargetSelectedEvent implements UiEventLogger.UiEventEnum {
315         INVALID(0),
316         @UiEvent(doc = "User selected a service target.")
317         SHARESHEET_SERVICE_TARGET_SELECTED(232),
318         @UiEvent(doc = "User selected an app target.")
319         SHARESHEET_APP_TARGET_SELECTED(233),
320         @UiEvent(doc = "User selected a standard target.")
321         SHARESHEET_STANDARD_TARGET_SELECTED(234),
322         @UiEvent(doc = "User selected the copy target.")
323         SHARESHEET_COPY_TARGET_SELECTED(235),
324         @UiEvent(doc = "User selected the nearby target.")
325         SHARESHEET_NEARBY_TARGET_SELECTED(626),
326         @UiEvent(doc = "User selected the edit target.")
327         SHARESHEET_EDIT_TARGET_SELECTED(669),
328         @UiEvent(doc = "User selected the modify share target.")
329         SHARESHEET_MODIFY_SHARE_SELECTED(1316),
330         @UiEvent(doc = "User selected a custom action.")
331         SHARESHEET_CUSTOM_ACTION_SELECTED(1317);
332 
333         private final int mId;
SharesheetTargetSelectedEvent(int id)334         SharesheetTargetSelectedEvent(int id) {
335             mId = id;
336         }
getId()337         @Override public int getId() {
338             return mId;
339         }
340 
fromTargetType(int targetType)341         public static SharesheetTargetSelectedEvent fromTargetType(int targetType) {
342             switch(targetType) {
343                 case SELECTION_TYPE_SERVICE:
344                     return SHARESHEET_SERVICE_TARGET_SELECTED;
345                 case SELECTION_TYPE_APP:
346                     return SHARESHEET_APP_TARGET_SELECTED;
347                 case SELECTION_TYPE_STANDARD:
348                     return SHARESHEET_STANDARD_TARGET_SELECTED;
349                 case SELECTION_TYPE_COPY:
350                     return SHARESHEET_COPY_TARGET_SELECTED;
351                 case SELECTION_TYPE_NEARBY:
352                     return SHARESHEET_NEARBY_TARGET_SELECTED;
353                 case SELECTION_TYPE_EDIT:
354                     return SHARESHEET_EDIT_TARGET_SELECTED;
355                 case SELECTION_TYPE_MODIFY_SHARE:
356                     return SHARESHEET_MODIFY_SHARE_SELECTED;
357                 case SELECTION_TYPE_CUSTOM_ACTION:
358                     return SHARESHEET_CUSTOM_ACTION_SELECTED;
359                 default:
360                     return INVALID;
361             }
362         }
363     }
364 
365     /**
366      * The UiEvent enums that this class can log.
367      */
368     enum SharesheetStandardEvent implements UiEventLogger.UiEventEnum {
369         INVALID(0),
370         @UiEvent(doc = "User clicked share.")
371         SHARESHEET_TRIGGERED(227),
372         @UiEvent(doc = "User changed from work to personal profile or vice versa.")
373         SHARESHEET_PROFILE_CHANGED(229),
374         @UiEvent(doc = "User expanded target list.")
375         SHARESHEET_EXPANDED(230),
376         @UiEvent(doc = "User collapsed target list.")
377         SHARESHEET_COLLAPSED(231),
378         @UiEvent(doc = "Sharesheet app targets is fully populated.")
379         SHARESHEET_APP_LOAD_COMPLETE(322),
380         @UiEvent(doc = "Sharesheet direct targets is fully populated.")
381         SHARESHEET_DIRECT_LOAD_COMPLETE(323),
382         @UiEvent(doc = "Sharesheet direct targets timed out.")
383         SHARESHEET_DIRECT_LOAD_TIMEOUT(324),
384         @UiEvent(doc = "Sharesheet app share ranking timed out.")
385         SHARESHEET_APP_SHARE_RANKING_TIMEOUT(831),
386         @UiEvent(doc = "Sharesheet empty direct share row.")
387         SHARESHEET_EMPTY_DIRECT_SHARE_ROW(828),
388         @UiEvent(doc = "Shareousel payload item toggled")
389         SHARESHEET_PAYLOAD_TOGGLED(1662);
390 
391         private final int mId;
SharesheetStandardEvent(int id)392         SharesheetStandardEvent(int id) {
393             mId = id;
394         }
getId()395         @Override public int getId() {
396             return mId;
397         }
398     }
399 
400     /**
401      * Returns the enum used in sharesheet started atom to indicate what preview type was used.
402      */
typeFromPreviewInt(int previewType)403     private static int typeFromPreviewInt(int previewType) {
404         switch(previewType) {
405             case ContentPreviewType.CONTENT_PREVIEW_IMAGE:
406                 return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_IMAGE;
407             case ContentPreviewType.CONTENT_PREVIEW_FILE:
408                 return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE;
409             case ContentPreviewType.CONTENT_PREVIEW_TEXT:
410             case ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION:
411                 return FrameworkStatsLog
412                         .SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TOGGLEABLE_MEDIA;
413             default:
414                 return FrameworkStatsLog
415                         .SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TYPE_UNKNOWN;
416         }
417     }
418 
419     /**
420      * Returns the enum used in sharesheet started atom to indicate what intent triggers the
421      * ChooserActivity.
422      */
typeFromIntentString(String intent)423     private static int typeFromIntentString(String intent) {
424         if (intent == null) {
425             return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT;
426         }
427         switch (intent) {
428             case Intent.ACTION_VIEW:
429                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_VIEW;
430             case Intent.ACTION_EDIT:
431                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_EDIT;
432             case Intent.ACTION_SEND:
433                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND;
434             case Intent.ACTION_SENDTO:
435                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO;
436             case Intent.ACTION_SEND_MULTIPLE:
437                 return FrameworkStatsLog
438                         .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND_MULTIPLE;
439             case MediaStore.ACTION_IMAGE_CAPTURE:
440                 return FrameworkStatsLog
441                         .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_IMAGE_CAPTURE;
442             case Intent.ACTION_MAIN:
443                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_MAIN;
444             default:
445                 return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT;
446         }
447     }
448 
449     @VisibleForTesting
getTargetSelectionCategory(int targetType)450     static int getTargetSelectionCategory(int targetType) {
451         switch (targetType) {
452             case SELECTION_TYPE_SERVICE:
453                 return MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
454             case SELECTION_TYPE_APP:
455                 return MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
456             case SELECTION_TYPE_STANDARD:
457                 return MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
458             default:
459                 return 0;
460         }
461     }
462 }
463