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