1 /* 2 * Copyright (C) 2016 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.documentsui; 18 19 import static android.os.Environment.STANDARD_DIRECTORIES; 20 import static com.android.documentsui.Shared.DEBUG; 21 22 import android.annotation.IntDef; 23 import android.annotation.Nullable; 24 import android.annotation.StringDef; 25 import android.app.Activity; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.ResolveInfo; 29 import android.net.Uri; 30 import android.provider.DocumentsContract; 31 import android.util.Log; 32 import android.view.KeyEvent; 33 34 import com.android.documentsui.State.ActionType; 35 import com.android.documentsui.model.DocumentInfo; 36 import com.android.documentsui.model.RootInfo; 37 import com.android.documentsui.services.FileOperationService; 38 import com.android.documentsui.services.FileOperationService.OpType; 39 import com.android.internal.logging.MetricsLogger; 40 import com.android.internal.logging.MetricsProto.MetricsEvent; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.List; 45 46 /** @hide */ 47 public final class Metrics { 48 private static final String TAG = "Metrics"; 49 50 // These are the native provider authorities that the metrics code is capable of recognizing and 51 // explicitly counting. 52 private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents"; 53 private static final String AUTHORITY_STORAGE = "com.android.externalstorage.documents"; 54 private static final String AUTHORITY_DOWNLOADS = "com.android.providers.downloads.documents"; 55 private static final String AUTHORITY_MTP = "com.android.mtp.documents"; 56 57 // These strings have to be whitelisted in tron. Do not change them. 58 private static final String COUNT_LAUNCH_ACTION = "docsui_launch_action"; 59 private static final String COUNT_ROOT_VISITED = "docsui_root_visited"; 60 private static final String COUNT_OPEN_MIME = "docsui_open_mime"; 61 private static final String COUNT_CREATE_MIME = "docsui_create_mime"; 62 private static final String COUNT_GET_CONTENT_MIME = "docsui_get_content_mime"; 63 private static final String COUNT_BROWSE_ROOT = "docsui_browse_root"; 64 @Deprecated private static final String COUNT_MANAGE_ROOT = "docsui_manage_root"; 65 @Deprecated private static final String COUNT_MULTI_WINDOW = "docsui_multi_window"; 66 private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system"; 67 private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external"; 68 private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled"; 69 private static final String COUNT_STARTUP_MS = "docsui_startup_ms"; 70 private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened"; 71 private static final String COUNT_USER_ACTION = "docsui_menu_action"; 72 73 // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any 74 // root that is not explicitly recognized by the Metrics code (see {@link 75 // #getSanitizedRootIndex}). Apps are also bucketed in this histogram. 76 // Do not change or rearrange these values, that will break historical data. Only add to the end 77 // of the list. 78 // Do not use negative numbers or zero; clearcut only handles positive integers. 79 private static final int ROOT_NONE = 1; 80 private static final int ROOT_OTHER = 2; 81 private static final int ROOT_AUDIO = 3; 82 private static final int ROOT_DEVICE_STORAGE = 4; 83 private static final int ROOT_DOWNLOADS = 5; 84 private static final int ROOT_HOME = 6; 85 private static final int ROOT_IMAGES = 7; 86 private static final int ROOT_RECENTS = 8; 87 private static final int ROOT_VIDEOS = 9; 88 private static final int ROOT_MTP = 10; 89 // Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they 90 // are logged analogously to roots. 91 private static final int ROOT_THIRD_PARTY_APP = 100; 92 93 @IntDef(flag = true, value = { 94 ROOT_NONE, 95 ROOT_OTHER, 96 ROOT_AUDIO, 97 ROOT_DEVICE_STORAGE, 98 ROOT_DOWNLOADS, 99 ROOT_HOME, 100 ROOT_IMAGES, 101 ROOT_RECENTS, 102 ROOT_VIDEOS, 103 ROOT_MTP, 104 ROOT_THIRD_PARTY_APP 105 }) 106 @Retention(RetentionPolicy.SOURCE) 107 public @interface Root {} 108 109 // Indices for bucketing mime types. 110 // Do not change or rearrange these values, that will break historical data. Only add to the end 111 // of the list. 112 // Do not use negative numbers or zero; clearcut only handles positive integers. 113 private static final int MIME_NONE = 1; // null mime 114 private static final int MIME_ANY = 2; // */* 115 private static final int MIME_APPLICATION = 3; // application/* 116 private static final int MIME_AUDIO = 4; // audio/* 117 private static final int MIME_IMAGE = 5; // image/* 118 private static final int MIME_MESSAGE = 6; // message/* 119 private static final int MIME_MULTIPART = 7; // multipart/* 120 private static final int MIME_TEXT = 8; // text/* 121 private static final int MIME_VIDEO = 9; // video/* 122 private static final int MIME_OTHER = 10; // anything not enumerated below 123 124 @IntDef(flag = true, value = { 125 MIME_NONE, 126 MIME_ANY, 127 MIME_APPLICATION, 128 MIME_AUDIO, 129 MIME_IMAGE, 130 MIME_MESSAGE, 131 MIME_MULTIPART, 132 MIME_TEXT, 133 MIME_VIDEO, 134 MIME_OTHER 135 }) 136 @Retention(RetentionPolicy.SOURCE) 137 public @interface Mime {} 138 139 // Codes representing different kinds of file operations. These are used for bucketing 140 // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms. 141 // Do not change or rearrange these values, that will break historical data. Only add to the 142 // list. 143 // Do not use negative numbers or zero; clearcut only handles positive integers. 144 private static final int FILEOP_OTHER = 1; // any file operation not listed below 145 private static final int FILEOP_COPY_INTRA_PROVIDER = 2; // Copy within a provider 146 private static final int FILEOP_COPY_SYSTEM_PROVIDER = 3; // Copy to a system provider. 147 private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 4; // Copy to a 3rd-party provider. 148 private static final int FILEOP_MOVE_INTRA_PROVIDER = 5; // Move within a provider. 149 private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 6; // Move to a system provider. 150 private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 7; // Move to a 3rd-party provider. 151 private static final int FILEOP_DELETE = 8; 152 private static final int FILEOP_RENAME = 9; 153 private static final int FILEOP_CREATE_DIR = 10; 154 private static final int FILEOP_OTHER_ERROR = 100; 155 private static final int FILEOP_DELETE_ERROR = 101; 156 private static final int FILEOP_MOVE_ERROR = 102; 157 private static final int FILEOP_COPY_ERROR = 103; 158 private static final int FILEOP_RENAME_ERROR = 104; 159 private static final int FILEOP_CREATE_DIR_ERROR = 105; 160 161 @IntDef(flag = true, value = { 162 FILEOP_OTHER, 163 FILEOP_COPY_INTRA_PROVIDER, 164 FILEOP_COPY_SYSTEM_PROVIDER, 165 FILEOP_COPY_EXTERNAL_PROVIDER, 166 FILEOP_MOVE_INTRA_PROVIDER, 167 FILEOP_MOVE_SYSTEM_PROVIDER, 168 FILEOP_MOVE_EXTERNAL_PROVIDER, 169 FILEOP_DELETE, 170 FILEOP_RENAME, 171 FILEOP_CREATE_DIR, 172 FILEOP_OTHER_ERROR, 173 FILEOP_COPY_ERROR, 174 FILEOP_MOVE_ERROR, 175 FILEOP_DELETE_ERROR, 176 FILEOP_RENAME_ERROR, 177 FILEOP_CREATE_DIR_ERROR 178 }) 179 @Retention(RetentionPolicy.SOURCE) 180 public @interface FileOp {} 181 182 // Codes representing different kinds of file operations. These are used for bucketing 183 // operations in the COUNT_FILEOP_CANCELED histogram. 184 // Do not change or rearrange these values, that will break historical data. Only add to the 185 // list. 186 // Do not use negative numbers or zero; clearcut only handles positive integers. 187 private static final int OPERATION_UNKNOWN = 1; 188 private static final int OPERATION_COPY = 2; 189 private static final int OPERATION_MOVE = 3; 190 private static final int OPERATION_DELETE= 4; 191 192 @IntDef(flag = true, value = { 193 OPERATION_UNKNOWN, 194 OPERATION_COPY, 195 OPERATION_MOVE, 196 OPERATION_DELETE 197 }) 198 @Retention(RetentionPolicy.SOURCE) 199 public @interface MetricsOpType {} 200 201 // Codes representing different provider types. Used for sorting file operations when logging. 202 private static final int PROVIDER_INTRA = 0; 203 private static final int PROVIDER_SYSTEM = 1; 204 private static final int PROVIDER_EXTERNAL = 2; 205 206 @IntDef(flag = false, value = { 207 PROVIDER_INTRA, 208 PROVIDER_SYSTEM, 209 PROVIDER_EXTERNAL 210 }) 211 @Retention(RetentionPolicy.SOURCE) 212 public @interface Provider {} 213 214 215 // Codes representing different user actions. These are used for bucketing stats in the 216 // COUNT_USER_ACTION histogram. 217 // The historgram includes action triggered from menu or invoked by keyboard shortcut. 218 // Do not change or rearrange these values, that will break historical data. Only add to the 219 // list. 220 // Do not use negative numbers or zero; clearcut only handles positive integers. 221 public static final int USER_ACTION_OTHER = 1; 222 public static final int USER_ACTION_GRID = 2; 223 public static final int USER_ACTION_LIST = 3; 224 public static final int USER_ACTION_SORT_NAME = 4; 225 public static final int USER_ACTION_SORT_DATE = 5; 226 public static final int USER_ACTION_SORT_SIZE = 6; 227 public static final int USER_ACTION_SEARCH = 7; 228 public static final int USER_ACTION_SHOW_SIZE = 8; 229 public static final int USER_ACTION_HIDE_SIZE = 9; 230 public static final int USER_ACTION_SETTINGS = 10; 231 public static final int USER_ACTION_COPY_TO = 11; 232 public static final int USER_ACTION_MOVE_TO = 12; 233 public static final int USER_ACTION_DELETE = 13; 234 public static final int USER_ACTION_RENAME = 14; 235 public static final int USER_ACTION_CREATE_DIR = 15; 236 public static final int USER_ACTION_SELECT_ALL = 16; 237 public static final int USER_ACTION_SHARE = 17; 238 public static final int USER_ACTION_OPEN = 18; 239 public static final int USER_ACTION_SHOW_ADVANCED = 19; 240 public static final int USER_ACTION_HIDE_ADVANCED = 20; 241 public static final int USER_ACTION_NEW_WINDOW = 21; 242 public static final int USER_ACTION_PASTE_CLIPBOARD = 22; 243 public static final int USER_ACTION_COPY_CLIPBOARD = 23; 244 public static final int USER_ACTION_DRAG_N_DROP = 24; 245 public static final int USER_ACTION_DRAG_N_DROP_MULTI_WINDOW = 25; 246 247 @IntDef(flag = false, value = { 248 USER_ACTION_OTHER, 249 USER_ACTION_GRID, 250 USER_ACTION_LIST, 251 USER_ACTION_SORT_NAME, 252 USER_ACTION_SORT_DATE, 253 USER_ACTION_SORT_SIZE, 254 USER_ACTION_SEARCH, 255 USER_ACTION_SHOW_SIZE, 256 USER_ACTION_HIDE_SIZE, 257 USER_ACTION_SETTINGS, 258 USER_ACTION_COPY_TO, 259 USER_ACTION_MOVE_TO, 260 USER_ACTION_DELETE, 261 USER_ACTION_RENAME, 262 USER_ACTION_CREATE_DIR, 263 USER_ACTION_SELECT_ALL, 264 USER_ACTION_SHARE, 265 USER_ACTION_OPEN, 266 USER_ACTION_SHOW_ADVANCED, 267 USER_ACTION_HIDE_ADVANCED, 268 USER_ACTION_NEW_WINDOW, 269 USER_ACTION_PASTE_CLIPBOARD, 270 USER_ACTION_COPY_CLIPBOARD, 271 USER_ACTION_DRAG_N_DROP, 272 USER_ACTION_DRAG_N_DROP_MULTI_WINDOW 273 }) 274 @Retention(RetentionPolicy.SOURCE) 275 public @interface UserAction {} 276 277 // Codes representing different menu actions. These are used for bucketing stats in the 278 // COUNT_MENU_ACTION histogram. 279 // Do not change or rearrange these values, that will break historical data. Only add to the 280 // list. 281 // Do not use negative numbers or zero; clearcut only handles positive integers. 282 private static final int ACTION_OTHER = 1; 283 private static final int ACTION_OPEN = 2; 284 private static final int ACTION_CREATE = 3; 285 private static final int ACTION_GET_CONTENT = 4; 286 private static final int ACTION_OPEN_TREE = 5; 287 @Deprecated private static final int ACTION_MANAGE = 6; 288 private static final int ACTION_BROWSE = 7; 289 private static final int ACTION_PICK_COPY_DESTINATION = 8; 290 291 @IntDef(flag = true, value = { 292 ACTION_OTHER, 293 ACTION_OPEN, 294 ACTION_CREATE, 295 ACTION_GET_CONTENT, 296 ACTION_OPEN_TREE, 297 ACTION_MANAGE, 298 ACTION_BROWSE, 299 ACTION_PICK_COPY_DESTINATION 300 }) 301 @Retention(RetentionPolicy.SOURCE) 302 public @interface MetricsAction {} 303 304 // Codes representing different actions to open the drawer. They are used for bucketing stats in 305 // the COUNT_DRAWER_OPENED histogram. 306 // Do not change or rearrange these values, that will break historical data. Only add to the 307 // list. 308 // Do not use negative numbers or zero; clearcut only handles positive integers. 309 private static final int DRAWER_OPENED_HAMBURGER = 1; 310 private static final int DRAWER_OPENED_SWIPE = 2; 311 312 @IntDef(flag = true, value = { 313 DRAWER_OPENED_HAMBURGER, 314 DRAWER_OPENED_SWIPE 315 }) 316 @Retention(RetentionPolicy.SOURCE) 317 public @interface DrawerTrigger {} 318 319 /** 320 * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up. 321 * 322 * @param context 323 * @param state 324 * @param intent 325 */ logActivityLaunch(Context context, State state, Intent intent)326 public static void logActivityLaunch(Context context, State state, Intent intent) { 327 // Log the launch action. 328 logHistogram(context, COUNT_LAUNCH_ACTION, toMetricsAction(state.action)); 329 // Then log auxiliary data (roots/mime types) associated with some actions. 330 Uri uri = intent.getData(); 331 switch (state.action) { 332 case State.ACTION_OPEN: 333 logHistogram(context, COUNT_OPEN_MIME, sanitizeMime(intent.getType())); 334 break; 335 case State.ACTION_CREATE: 336 logHistogram(context, COUNT_CREATE_MIME, sanitizeMime(intent.getType())); 337 break; 338 case State.ACTION_GET_CONTENT: 339 logHistogram(context, COUNT_GET_CONTENT_MIME, sanitizeMime(intent.getType())); 340 break; 341 case State.ACTION_BROWSE: 342 logHistogram(context, COUNT_BROWSE_ROOT, sanitizeRoot(uri)); 343 break; 344 default: 345 break; 346 } 347 } 348 349 /** 350 * Logs a root visited event. Call this when the user clicks on a root in the RootsFragment. 351 * 352 * @param context 353 * @param info 354 */ logRootVisited(Context context, RootInfo info)355 public static void logRootVisited(Context context, RootInfo info) { 356 logHistogram(context, COUNT_ROOT_VISITED, sanitizeRoot(info)); 357 } 358 359 /** 360 * Logs an app visited event. Call this when the user clicks on an app in the RootsFragment. 361 * 362 * @param context 363 * @param info 364 */ logAppVisited(Context context, ResolveInfo info)365 public static void logAppVisited(Context context, ResolveInfo info) { 366 logHistogram(context, COUNT_ROOT_VISITED, sanitizeRoot(info)); 367 } 368 369 /** 370 * Logs a drawer opened event. Call this when the user opens drawer by swipe or by clicking the 371 * hamburger icon. 372 * @param context 373 * @param trigger type of action that opened the drawer 374 */ logDrawerOpened(Context context, @DrawerController.Trigger int trigger)375 public static void logDrawerOpened(Context context, @DrawerController.Trigger int trigger) { 376 if (trigger == DrawerController.OPENED_HAMBURGER) { 377 logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_HAMBURGER); 378 } else if (trigger == DrawerController.OPENED_SWIPE) { 379 logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_SWIPE); 380 } 381 } 382 383 /** 384 * Logs file operation stats. Call this when a file operation has completed. The given 385 * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one 386 * provider to another vs copying within a given provider). No PII is logged. 387 * 388 * @param context 389 * @param operationType 390 * @param srcs 391 * @param dst 392 */ logFileOperation( Context context, @OpType int operationType, List<DocumentInfo> srcs, @Nullable DocumentInfo dst)393 public static void logFileOperation( 394 Context context, 395 @OpType int operationType, 396 List<DocumentInfo> srcs, 397 @Nullable DocumentInfo dst) { 398 ProviderCounts counts = countProviders(srcs, dst); 399 400 if (counts.intraProvider > 0) { 401 logIntraProviderFileOps(context, dst.authority, operationType); 402 } 403 if (counts.systemProvider > 0) { 404 // Log file operations on system providers. 405 logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType); 406 } 407 if (counts.externalProvider > 0) { 408 // Log file operations on external providers. 409 logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType); 410 } 411 } 412 413 /** 414 * Logs create directory operation. It is a part of file operation stats. We do not 415 * differentiate between internal and external locations, all create directory operations are 416 * logged under COUNT_FILEOP_SYSTEM. Call this when a create directory operation has completed. 417 * 418 * @param context 419 */ logCreateDirOperation(Context context)420 public static void logCreateDirOperation(Context context) { 421 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR); 422 } 423 424 /** 425 * Logs rename file operation. It is a part of file operation stats. We do not differentiate 426 * between internal and external locations, all rename operations are logged under 427 * COUNT_FILEOP_SYSTEM. Call this when a rename file operation has completed. 428 * 429 * @param context 430 */ logRenameFileOperation(Context context)431 public static void logRenameFileOperation(Context context) { 432 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME); 433 } 434 435 /** 436 * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete) 437 * fails. 438 * 439 * @param context 440 * @param operationType 441 * @param failedFiles 442 */ logFileOperationErrors(Context context, @OpType int operationType, List<DocumentInfo> failedFiles)443 public static void logFileOperationErrors(Context context, @OpType int operationType, 444 List<DocumentInfo> failedFiles) { 445 ProviderCounts counts = countProviders(failedFiles, null); 446 447 @FileOp int opCode = FILEOP_OTHER_ERROR; 448 switch (operationType) { 449 case FileOperationService.OPERATION_COPY: 450 opCode = FILEOP_COPY_ERROR; 451 break; 452 case FileOperationService.OPERATION_DELETE: 453 opCode = FILEOP_DELETE_ERROR; 454 break; 455 case FileOperationService.OPERATION_MOVE: 456 opCode = FILEOP_MOVE_ERROR; 457 break; 458 } 459 if (counts.systemProvider > 0) { 460 logHistogram(context, COUNT_FILEOP_SYSTEM, opCode); 461 } 462 if (counts.externalProvider > 0) { 463 logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode); 464 } 465 } 466 467 /** 468 * Logs create directory operation error. We do not differentiate between internal and external 469 * locations, all create directory errors are logged under COUNT_FILEOP_SYSTEM. Call this when a 470 * create directory operation fails. 471 * 472 * @param context 473 */ logCreateDirError(Context context)474 public static void logCreateDirError(Context context) { 475 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR_ERROR); 476 } 477 478 /** 479 * Logs rename file operation error. We do not differentiate between internal and external 480 * locations, all rename errors are logged under COUNT_FILEOP_SYSTEM. Call this 481 * when a rename file operation fails. 482 * 483 * @param context 484 */ logRenameFileError(Context context)485 public static void logRenameFileError(Context context) { 486 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME_ERROR); 487 } 488 489 /** 490 * Logs the cancellation of a file operation. Call this when a Job is canceled. 491 * @param context 492 * @param operationType 493 */ logFileOperationCancelled(Context context, @OpType int operationType)494 public static void logFileOperationCancelled(Context context, @OpType int operationType) { 495 logHistogram(context, COUNT_FILEOP_CANCELED, toMetricsOpType(operationType)); 496 } 497 498 /** 499 * Logs startup time in milliseconds. 500 * @param context 501 * @param startupMs Startup time in milliseconds. 502 */ logStartupMs(Context context, int startupMs)503 public static void logStartupMs(Context context, int startupMs) { 504 logHistogram(context, COUNT_STARTUP_MS, startupMs); 505 } 506 logInterProviderFileOps( Context context, String histogram, DocumentInfo dst, @OpType int operationType)507 private static void logInterProviderFileOps( 508 Context context, 509 String histogram, 510 DocumentInfo dst, 511 @OpType int operationType) { 512 if (operationType == FileOperationService.OPERATION_DELETE) { 513 logHistogram(context, histogram, FILEOP_DELETE); 514 } else { 515 assert(dst != null); 516 @Provider int providerType = 517 isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL; 518 logHistogram(context, histogram, getOpCode(operationType, providerType)); 519 } 520 } 521 logIntraProviderFileOps( Context context, String authority, @OpType int operationType)522 private static void logIntraProviderFileOps( 523 Context context, String authority, @OpType int operationType) { 524 // Find the right histogram to log to, then log the operation. 525 String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL; 526 logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA)); 527 } 528 529 // Types for logInvalidScopedAccessRequest 530 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS = 531 "docsui_scoped_directory_access_invalid_args"; 532 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY = 533 "docsui_scoped_directory_access_invalid_dir"; 534 public static final String SCOPED_DIRECTORY_ACCESS_ERROR = 535 "docsui_scoped_directory_access_error"; 536 537 @StringDef(value = { 538 SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS, 539 SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY, 540 SCOPED_DIRECTORY_ACCESS_ERROR 541 }) 542 @Retention(RetentionPolicy.SOURCE) 543 public @interface InvalidScopedAccess{} 544 logInvalidScopedAccessRequest(Context context, @InvalidScopedAccess String type)545 public static void logInvalidScopedAccessRequest(Context context, 546 @InvalidScopedAccess String type) { 547 switch (type) { 548 case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS: 549 case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY: 550 case SCOPED_DIRECTORY_ACCESS_ERROR: 551 logCount(context, type); 552 break; 553 default: 554 Log.wtf(TAG, "invalid InvalidScopedAccess: " + type); 555 } 556 } 557 558 // Types for logValidScopedAccessRequest 559 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0; 560 public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1; 561 public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2; 562 public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3; 563 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4; 564 565 @IntDef(flag = true, value = { 566 SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED, 567 SCOPED_DIRECTORY_ACCESS_GRANTED, 568 SCOPED_DIRECTORY_ACCESS_DENIED, 569 SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST, 570 SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED 571 }) 572 @Retention(RetentionPolicy.SOURCE) 573 public @interface ScopedAccessGrant {} 574 logValidScopedAccessRequest(Activity activity, String directory, @ScopedAccessGrant int type)575 public static void logValidScopedAccessRequest(Activity activity, String directory, 576 @ScopedAccessGrant int type) { 577 int index = -1; 578 if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) { 579 index = -2; 580 } else { 581 for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) { 582 if (STANDARD_DIRECTORIES[i].equals(directory)) { 583 index = i; 584 break; 585 } 586 } 587 } 588 final String packageName = activity.getCallingPackage(); 589 switch (type) { 590 case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED: 591 MetricsLogger.action(activity, MetricsEvent 592 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName); 593 MetricsLogger.action(activity, MetricsEvent 594 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index); 595 break; 596 case SCOPED_DIRECTORY_ACCESS_GRANTED: 597 MetricsLogger.action(activity, MetricsEvent 598 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName); 599 MetricsLogger.action(activity, MetricsEvent 600 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index); 601 break; 602 case SCOPED_DIRECTORY_ACCESS_DENIED: 603 MetricsLogger.action(activity, MetricsEvent 604 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName); 605 MetricsLogger.action(activity, MetricsEvent 606 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index); 607 break; 608 case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST: 609 MetricsLogger.action(activity, MetricsEvent 610 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName); 611 MetricsLogger.action(activity, MetricsEvent 612 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index); 613 break; 614 case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED: 615 MetricsLogger.action(activity, MetricsEvent 616 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName); 617 MetricsLogger.action(activity, MetricsEvent 618 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index); 619 break; 620 default: 621 Log.wtf(TAG, "invalid ScopedAccessGrant: " + type); 622 } 623 } 624 625 /** 626 * Logs the action that was started by user. 627 * @param context 628 * @param userAction 629 */ logUserAction(Context context, @UserAction int userAction)630 public static void logUserAction(Context context, @UserAction int userAction) { 631 logHistogram(context, COUNT_USER_ACTION, userAction); 632 } 633 634 /** 635 * Internal method for making a MetricsLogger.count call. Increments the given counter by 1. 636 * 637 * @param context 638 * @param name The counter to increment. 639 */ logCount(Context context, String name)640 private static void logCount(Context context, String name) { 641 if (DEBUG) Log.d(TAG, name + ": " + 1); 642 MetricsLogger.count(context, name, 1); 643 } 644 645 /** 646 * Internal method for making a MetricsLogger.histogram call. 647 * 648 * @param context 649 * @param name The name of the histogram. 650 * @param bucket The bucket to increment. 651 */ logHistogram(Context context, String name, @ActionType int bucket)652 private static void logHistogram(Context context, String name, @ActionType int bucket) { 653 if (DEBUG) Log.d(TAG, name + ": " + bucket); 654 MetricsLogger.histogram(context, name, bucket); 655 } 656 657 /** 658 * Generates an integer identifying the given root. For privacy, this function only recognizes a 659 * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into 660 * a single ROOT_OTHER bucket. 661 */ sanitizeRoot(Uri uri)662 private static @Root int sanitizeRoot(Uri uri) { 663 if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) { 664 return ROOT_NONE; 665 } 666 667 switch (uri.getAuthority()) { 668 case AUTHORITY_MEDIA: 669 switch (DocumentsContract.getRootId(uri)) { 670 case "audio_root": 671 return ROOT_AUDIO; 672 case "images_root": 673 return ROOT_IMAGES; 674 case "videos_root": 675 return ROOT_VIDEOS; 676 default: 677 return ROOT_OTHER; 678 } 679 case AUTHORITY_STORAGE: 680 if ("home".equals(DocumentsContract.getRootId(uri))) { 681 return ROOT_HOME; 682 } else { 683 return ROOT_DEVICE_STORAGE; 684 } 685 case AUTHORITY_DOWNLOADS: 686 return ROOT_DOWNLOADS; 687 case AUTHORITY_MTP: 688 return ROOT_MTP; 689 default: 690 return ROOT_OTHER; 691 } 692 } 693 694 /** @see #sanitizeRoot(Uri) */ sanitizeRoot(RootInfo root)695 private static @Root int sanitizeRoot(RootInfo root) { 696 if (root.isRecents()) { 697 // Recents root is special and only identifiable via this method call. Other roots are 698 // identified by URI. 699 return ROOT_RECENTS; 700 } else { 701 return sanitizeRoot(root.getUri()); 702 } 703 } 704 705 /** @see #sanitizeRoot(Uri) */ sanitizeRoot(ResolveInfo info)706 private static @Root int sanitizeRoot(ResolveInfo info) { 707 // Log all apps under a single bucket in the roots histogram. 708 return ROOT_THIRD_PARTY_APP; 709 } 710 711 /** 712 * Generates an int identifying a mime type. For privacy, this function only recognizes a small 713 * set of hard-coded types. For any other type, this function returns "other". 714 * 715 * @param mimeType 716 * @return 717 */ sanitizeMime(String mimeType)718 private static @Mime int sanitizeMime(String mimeType) { 719 if (mimeType == null) { 720 return MIME_NONE; 721 } else if ("*/*".equals(mimeType)) { 722 return MIME_ANY; 723 } else { 724 String type = mimeType.substring(0, mimeType.indexOf('/')); 725 switch (type) { 726 case "application": 727 return MIME_APPLICATION; 728 case "audio": 729 return MIME_AUDIO; 730 case "image": 731 return MIME_IMAGE; 732 case "message": 733 return MIME_MESSAGE; 734 case "multipart": 735 return MIME_MULTIPART; 736 case "text": 737 return MIME_TEXT; 738 case "video": 739 return MIME_VIDEO; 740 } 741 } 742 // Bucket all other types into one bucket. 743 return MIME_OTHER; 744 } 745 isSystemProvider(String authority)746 private static boolean isSystemProvider(String authority) { 747 switch (authority) { 748 case AUTHORITY_MEDIA: 749 case AUTHORITY_STORAGE: 750 case AUTHORITY_DOWNLOADS: 751 return true; 752 default: 753 return false; 754 } 755 } 756 757 /** 758 * @param operation 759 * @param providerType 760 * @return An opcode, suitable for use as histogram bucket, for the given operation/provider 761 * combination. 762 */ getOpCode(@pType int operation, @Provider int providerType)763 private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) { 764 switch (operation) { 765 case FileOperationService.OPERATION_COPY: 766 switch (providerType) { 767 case PROVIDER_INTRA: 768 return FILEOP_COPY_INTRA_PROVIDER; 769 case PROVIDER_SYSTEM: 770 return FILEOP_COPY_SYSTEM_PROVIDER; 771 case PROVIDER_EXTERNAL: 772 return FILEOP_COPY_EXTERNAL_PROVIDER; 773 } 774 case FileOperationService.OPERATION_MOVE: 775 switch (providerType) { 776 case PROVIDER_INTRA: 777 return FILEOP_MOVE_INTRA_PROVIDER; 778 case PROVIDER_SYSTEM: 779 return FILEOP_MOVE_SYSTEM_PROVIDER; 780 case PROVIDER_EXTERNAL: 781 return FILEOP_MOVE_EXTERNAL_PROVIDER; 782 } 783 case FileOperationService.OPERATION_DELETE: 784 return FILEOP_DELETE; 785 default: 786 Log.w(TAG, "Unrecognized operation type when logging a file operation"); 787 return FILEOP_OTHER; 788 } 789 } 790 791 /** 792 * Maps FileOperationService OpType values, to MetricsOpType values. 793 */ toMetricsOpType(@pType int operation)794 private static @MetricsOpType int toMetricsOpType(@OpType int operation) { 795 switch (operation) { 796 case FileOperationService.OPERATION_COPY: 797 return OPERATION_COPY; 798 case FileOperationService.OPERATION_MOVE: 799 return OPERATION_MOVE; 800 case FileOperationService.OPERATION_DELETE: 801 return OPERATION_DELETE; 802 case FileOperationService.OPERATION_UNKNOWN: 803 default: 804 return OPERATION_UNKNOWN; 805 } 806 } 807 toMetricsAction(int action)808 private static @MetricsAction int toMetricsAction(int action) { 809 switch(action) { 810 case State.ACTION_OPEN: 811 return ACTION_OPEN; 812 case State.ACTION_CREATE: 813 return ACTION_CREATE; 814 case State.ACTION_GET_CONTENT: 815 return ACTION_GET_CONTENT; 816 case State.ACTION_OPEN_TREE: 817 return ACTION_OPEN_TREE; 818 case State.ACTION_BROWSE: 819 return ACTION_BROWSE; 820 case State.ACTION_PICK_COPY_DESTINATION: 821 return ACTION_PICK_COPY_DESTINATION; 822 default: 823 return ACTION_OTHER; 824 } 825 } 826 827 /** 828 * Count the given src documents and provide a tally of how many come from the same provider as 829 * the dst document (if a dst is provided), how many come from system providers, and how many 830 * come from external 3rd-party providers. 831 */ countProviders( List<DocumentInfo> srcs, @Nullable DocumentInfo dst)832 private static ProviderCounts countProviders( 833 List<DocumentInfo> srcs, @Nullable DocumentInfo dst) { 834 ProviderCounts counts = new ProviderCounts(); 835 for (DocumentInfo doc: srcs) { 836 if (dst != null && doc.authority.equals(dst.authority)) { 837 counts.intraProvider++; 838 } else if (isSystemProvider(doc.authority)){ 839 counts.systemProvider++; 840 } else { 841 counts.externalProvider++; 842 } 843 } 844 return counts; 845 } 846 847 private static class ProviderCounts { 848 int intraProvider; 849 int systemProvider; 850 int externalProvider; 851 } 852 } 853