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