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.DocumentsApplication.acquireUnstableProviderOrThrow; 21 import static com.android.documentsui.base.Shared.DEBUG; 22 23 import android.annotation.IntDef; 24 import android.annotation.Nullable; 25 import android.annotation.StringDef; 26 import android.app.Activity; 27 import android.content.ContentProviderClient; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.ResolveInfo; 31 import android.net.Uri; 32 import android.os.RemoteException; 33 import android.provider.DocumentsContract; 34 import android.provider.DocumentsContract.Path; 35 import android.provider.DocumentsProvider; 36 import android.util.Log; 37 38 import com.android.documentsui.base.DocumentInfo; 39 import com.android.documentsui.base.Providers; 40 import com.android.documentsui.base.RootInfo; 41 import com.android.documentsui.base.State; 42 import com.android.documentsui.base.State.ActionType; 43 import com.android.documentsui.files.LauncherActivity; 44 import com.android.documentsui.roots.ProvidersAccess; 45 import com.android.documentsui.services.FileOperationService; 46 import com.android.documentsui.services.FileOperationService.OpType; 47 import com.android.internal.logging.MetricsLogger; 48 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 49 50 import java.lang.annotation.Retention; 51 import java.lang.annotation.RetentionPolicy; 52 import java.util.List; 53 54 /** @hide */ 55 public final class Metrics { 56 private static final String TAG = "Metrics"; 57 58 // These strings have to be whitelisted in tron. Do not change them. 59 private static final String COUNT_LAUNCH_ACTION = "docsui_launch_action"; 60 private static final String COUNT_ROOT_VISITED_IN_MANAGER 61 = "docsui_root_visited_in_manager"; 62 private static final String COUNT_ROOT_VISITED_IN_PICKER 63 = "docsui_root_visited_in_picker"; 64 private static final String COUNT_OPEN_MIME = "docsui_open_mime"; 65 private static final String COUNT_CREATE_MIME = "docsui_create_mime"; 66 private static final String COUNT_GET_CONTENT_MIME = "docsui_get_content_mime"; 67 private static final String COUNT_BROWSE_ROOT = "docsui_browse_root"; 68 @Deprecated private static final String COUNT_MANAGE_ROOT = "docsui_manage_root"; 69 @Deprecated private static final String COUNT_MULTI_WINDOW = "docsui_multi_window"; 70 private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system"; 71 private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external"; 72 private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled"; 73 private static final String COUNT_STARTUP_MS = "docsui_startup_ms"; 74 @Deprecated private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened"; 75 private static final String COUNT_USER_ACTION = "docsui_menu_action"; 76 private static final String COUNT_BROWSE_AT_LOCATION = "docsui_browse_at_location"; 77 private static final String COUNT_CREATE_AT_LOCATION = "docsui_create_at_location"; 78 private static final String COUNT_OPEN_AT_LOCATION = "docsui_open_at_location"; 79 private static final String COUNT_GET_CONTENT_AT_LOCATION = "docsui_get_content_at_location"; 80 private static final String COUNT_MEDIA_FILEOP_FAILURE = "docsui_media_fileop_failure"; 81 private static final String COUNT_DOWNLOADS_FILEOP_FAILURE = "docsui_downloads_fileop_failure"; 82 private static final String COUNT_INTERNAL_STORAGE_FILEOP_FAILURE 83 = "docsui_internal_storage_fileop_failure"; 84 private static final String COUNT_EXTERNAL_STORAGE_FILEOP_FAILURE 85 = "docsui_external_storage_fileop_failure"; 86 private static final String COUNT_MTP_FILEOP_FAILURE = "docsui_mtp_fileop_failure"; 87 private static final String COUNT_OTHER_FILEOP_FAILURE = "docsui_other_fileop_failure"; 88 private static final String COUNT_FILE_COPIED = "docsui_file_copied"; 89 private static final String COUNT_FILE_MOVED = "docsui_file_moved"; 90 91 // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any 92 // root that is not explicitly recognized by the Metrics code (see {@link 93 // #getSanitizedRootIndex}). Apps are also bucketed in this histogram. 94 // Do not change or rearrange these values, that will break historical data. Only add to the end 95 // of the list. 96 // Do not use negative numbers or zero; clearcut only handles positive integers. 97 private static final int ROOT_NONE = 1; 98 private static final int ROOT_OTHER = 2; 99 private static final int ROOT_AUDIO = 3; 100 private static final int ROOT_DEVICE_STORAGE = 4; 101 private static final int ROOT_DOWNLOADS = 5; 102 private static final int ROOT_HOME = 6; 103 private static final int ROOT_IMAGES = 7; 104 private static final int ROOT_RECENTS = 8; 105 private static final int ROOT_VIDEOS = 9; 106 private static final int ROOT_MTP = 10; 107 // Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they 108 // are logged analogously to roots. 109 private static final int ROOT_THIRD_PARTY_APP = 100; 110 111 @IntDef(flag = true, value = { 112 ROOT_NONE, 113 ROOT_OTHER, 114 ROOT_AUDIO, 115 ROOT_DEVICE_STORAGE, 116 ROOT_DOWNLOADS, 117 ROOT_HOME, 118 ROOT_IMAGES, 119 ROOT_RECENTS, 120 ROOT_VIDEOS, 121 ROOT_MTP, 122 ROOT_THIRD_PARTY_APP 123 }) 124 @Retention(RetentionPolicy.SOURCE) 125 public @interface Root {} 126 127 // Indices for bucketing mime types. 128 // Do not change or rearrange these values, that will break historical data. Only add to the end 129 // of the list. 130 // Do not use negative numbers or zero; clearcut only handles positive integers. 131 private static final int MIME_NONE = 1; // null mime 132 private static final int MIME_ANY = 2; // */* 133 private static final int MIME_APPLICATION = 3; // application/* 134 private static final int MIME_AUDIO = 4; // audio/* 135 private static final int MIME_IMAGE = 5; // image/* 136 private static final int MIME_MESSAGE = 6; // message/* 137 private static final int MIME_MULTIPART = 7; // multipart/* 138 private static final int MIME_TEXT = 8; // text/* 139 private static final int MIME_VIDEO = 9; // video/* 140 private static final int MIME_OTHER = 10; // anything not enumerated below 141 142 @IntDef(flag = true, value = { 143 MIME_NONE, 144 MIME_ANY, 145 MIME_APPLICATION, 146 MIME_AUDIO, 147 MIME_IMAGE, 148 MIME_MESSAGE, 149 MIME_MULTIPART, 150 MIME_TEXT, 151 MIME_VIDEO, 152 MIME_OTHER 153 }) 154 @Retention(RetentionPolicy.SOURCE) 155 public @interface Mime {} 156 157 public static final int FILES_SCOPE = 1; 158 public static final int PICKER_SCOPE = 2; 159 160 @IntDef({ FILES_SCOPE, PICKER_SCOPE }) 161 @Retention(RetentionPolicy.SOURCE) 162 public @interface ContextScope {} 163 164 // Codes representing different kinds of file operations. These are used for bucketing 165 // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms. 166 // Do not change or rearrange these values, that will break historical data. Only add to the 167 // list. 168 // Do not use negative numbers or zero; clearcut only handles positive integers. 169 private static final int FILEOP_OTHER = 1; // any file operation not listed below 170 private static final int FILEOP_COPY_INTRA_PROVIDER = 2; // Copy within a provider 171 private static final int FILEOP_COPY_SYSTEM_PROVIDER = 3; // Copy to a system provider. 172 private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 4; // Copy to a 3rd-party provider. 173 private static final int FILEOP_MOVE_INTRA_PROVIDER = 5; // Move within a provider. 174 private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 6; // Move to a system provider. 175 private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 7; // Move to a 3rd-party provider. 176 private static final int FILEOP_DELETE = 8; 177 private static final int FILEOP_RENAME = 9; 178 private static final int FILEOP_CREATE_DIR = 10; 179 private static final int FILEOP_OTHER_ERROR = 100; 180 private static final int FILEOP_DELETE_ERROR = 101; 181 private static final int FILEOP_MOVE_ERROR = 102; 182 private static final int FILEOP_COPY_ERROR = 103; 183 private static final int FILEOP_RENAME_ERROR = 104; 184 private static final int FILEOP_CREATE_DIR_ERROR = 105; 185 private static final int FILEOP_COMPRESS_INTRA_PROVIDER = 106; // Compres within a provider 186 private static final int FILEOP_COMPRESS_SYSTEM_PROVIDER = 107; // Compress to a system provider. 187 private static final int FILEOP_COMPRESS_EXTERNAL_PROVIDER = 108; // Compress to a 3rd-party provider. 188 private static final int FILEOP_EXTRACT_INTRA_PROVIDER = 109; // Extract within a provider 189 private static final int FILEOP_EXTRACT_SYSTEM_PROVIDER = 110; // Extract to a system provider. 190 private static final int FILEOP_EXTRACT_EXTERNAL_PROVIDER = 111; // Extract to a 3rd-party provider. 191 private static final int FILEOP_COMPRESS_ERROR = 112; 192 private static final int FILEOP_EXTRACT_ERROR = 113; 193 194 @IntDef(flag = true, value = { 195 FILEOP_OTHER, 196 FILEOP_COPY_INTRA_PROVIDER, 197 FILEOP_COPY_SYSTEM_PROVIDER, 198 FILEOP_COPY_EXTERNAL_PROVIDER, 199 FILEOP_MOVE_INTRA_PROVIDER, 200 FILEOP_MOVE_SYSTEM_PROVIDER, 201 FILEOP_MOVE_EXTERNAL_PROVIDER, 202 FILEOP_DELETE, 203 FILEOP_RENAME, 204 FILEOP_CREATE_DIR, 205 FILEOP_OTHER_ERROR, 206 FILEOP_DELETE_ERROR, 207 FILEOP_MOVE_ERROR, 208 FILEOP_COPY_ERROR, 209 FILEOP_RENAME_ERROR, 210 FILEOP_CREATE_DIR_ERROR, 211 FILEOP_COMPRESS_INTRA_PROVIDER, 212 FILEOP_COMPRESS_SYSTEM_PROVIDER, 213 FILEOP_COMPRESS_EXTERNAL_PROVIDER, 214 FILEOP_EXTRACT_INTRA_PROVIDER, 215 FILEOP_EXTRACT_SYSTEM_PROVIDER, 216 FILEOP_EXTRACT_EXTERNAL_PROVIDER, 217 FILEOP_COMPRESS_ERROR, 218 FILEOP_EXTRACT_ERROR 219 }) 220 @Retention(RetentionPolicy.SOURCE) 221 public @interface FileOp {} 222 223 // Codes representing different kinds of file operations. These are used for bucketing 224 // operations in the COUNT_FILEOP_CANCELED histogram. 225 // Do not change or rearrange these values, that will break historical data. Only add to the 226 // list. 227 // Do not use negative numbers or zero; clearcut only handles positive integers. 228 private static final int OPERATION_UNKNOWN = 1; 229 private static final int OPERATION_COPY = 2; 230 private static final int OPERATION_MOVE = 3; 231 private static final int OPERATION_DELETE = 4; 232 private static final int OPERATION_COMPRESS = 5; 233 private static final int OPERATION_EXTRACT = 6; 234 235 @IntDef(flag = true, value = { 236 OPERATION_UNKNOWN, 237 OPERATION_COPY, 238 OPERATION_MOVE, 239 OPERATION_DELETE, 240 OPERATION_COMPRESS, 241 OPERATION_EXTRACT 242 }) 243 @Retention(RetentionPolicy.SOURCE) 244 public @interface MetricsOpType {} 245 246 // Codes representing different provider types. Used for sorting file operations when logging. 247 private static final int PROVIDER_INTRA = 0; 248 private static final int PROVIDER_SYSTEM = 1; 249 private static final int PROVIDER_EXTERNAL = 2; 250 251 @IntDef(flag = false, value = { 252 PROVIDER_INTRA, 253 PROVIDER_SYSTEM, 254 PROVIDER_EXTERNAL 255 }) 256 @Retention(RetentionPolicy.SOURCE) 257 public @interface Provider {} 258 259 // Codes representing different types of sub-fileops. These are used for bucketing fileop 260 // failures in COUNT_*_FILEOP_FAILURE. 261 public static final int SUBFILEOP_QUERY_DOCUMENT = 1; 262 public static final int SUBFILEOP_QUERY_CHILDREN = 2; 263 public static final int SUBFILEOP_OPEN_FILE = 3; 264 public static final int SUBFILEOP_READ_FILE = 4; 265 public static final int SUBFILEOP_CREATE_DOCUMENT = 5; 266 public static final int SUBFILEOP_WRITE_FILE = 6; 267 public static final int SUBFILEOP_DELETE_DOCUMENT = 7; 268 public static final int SUBFILEOP_OBTAIN_STREAM_TYPE = 8; 269 public static final int SUBFILEOP_QUICK_MOVE = 9; 270 public static final int SUBFILEOP_QUICK_COPY = 10; 271 272 @IntDef(flag = false, value = { 273 SUBFILEOP_QUERY_DOCUMENT, 274 SUBFILEOP_QUERY_CHILDREN, 275 SUBFILEOP_OPEN_FILE, 276 SUBFILEOP_READ_FILE, 277 SUBFILEOP_CREATE_DOCUMENT, 278 SUBFILEOP_WRITE_FILE, 279 SUBFILEOP_DELETE_DOCUMENT, 280 SUBFILEOP_OBTAIN_STREAM_TYPE, 281 SUBFILEOP_QUICK_MOVE, 282 SUBFILEOP_QUICK_COPY 283 }) 284 @Retention(RetentionPolicy.SOURCE) 285 public @interface SubFileOp {} 286 287 // Codes representing different user actions. These are used for bucketing stats in the 288 // COUNT_USER_ACTION histogram. 289 // The historgram includes action triggered from menu or invoked by keyboard shortcut. 290 // Do not change or rearrange these values, that will break historical data. Only add to the 291 // list. 292 // Do not use negative numbers or zero; clearcut only handles positive integers. 293 public static final int USER_ACTION_OTHER = 1; 294 public static final int USER_ACTION_GRID = 2; 295 public static final int USER_ACTION_LIST = 3; 296 public static final int USER_ACTION_SORT_NAME = 4; 297 public static final int USER_ACTION_SORT_DATE = 5; 298 public static final int USER_ACTION_SORT_SIZE = 6; 299 public static final int USER_ACTION_SEARCH = 7; 300 public static final int USER_ACTION_SHOW_SIZE = 8; 301 public static final int USER_ACTION_HIDE_SIZE = 9; 302 public static final int USER_ACTION_SETTINGS = 10; 303 public static final int USER_ACTION_COPY_TO = 11; 304 public static final int USER_ACTION_MOVE_TO = 12; 305 public static final int USER_ACTION_DELETE = 13; 306 public static final int USER_ACTION_RENAME = 14; 307 public static final int USER_ACTION_CREATE_DIR = 15; 308 public static final int USER_ACTION_SELECT_ALL = 16; 309 public static final int USER_ACTION_SHARE = 17; 310 public static final int USER_ACTION_OPEN = 18; 311 public static final int USER_ACTION_SHOW_ADVANCED = 19; 312 public static final int USER_ACTION_HIDE_ADVANCED = 20; 313 public static final int USER_ACTION_NEW_WINDOW = 21; 314 public static final int USER_ACTION_PASTE_CLIPBOARD = 22; 315 public static final int USER_ACTION_COPY_CLIPBOARD = 23; 316 public static final int USER_ACTION_DRAG_N_DROP = 24; 317 public static final int USER_ACTION_DRAG_N_DROP_MULTI_WINDOW = 25; 318 public static final int USER_ACTION_CUT_CLIPBOARD = 26; 319 public static final int USER_ACTION_COMPRESS = 27; 320 public static final int USER_ACTION_EXTRACT_TO = 28; 321 public static final int USER_ACTION_VIEW_IN_APPLICATION = 29; 322 public static final int USER_ACTION_INSPECTOR = 30; 323 324 @IntDef(flag = false, value = { 325 USER_ACTION_OTHER, 326 USER_ACTION_GRID, 327 USER_ACTION_LIST, 328 USER_ACTION_SORT_NAME, 329 USER_ACTION_SORT_DATE, 330 USER_ACTION_SORT_SIZE, 331 USER_ACTION_SEARCH, 332 USER_ACTION_SHOW_SIZE, 333 USER_ACTION_HIDE_SIZE, 334 USER_ACTION_SETTINGS, 335 USER_ACTION_COPY_TO, 336 USER_ACTION_MOVE_TO, 337 USER_ACTION_DELETE, 338 USER_ACTION_RENAME, 339 USER_ACTION_CREATE_DIR, 340 USER_ACTION_SELECT_ALL, 341 USER_ACTION_SHARE, 342 USER_ACTION_OPEN, 343 USER_ACTION_SHOW_ADVANCED, 344 USER_ACTION_HIDE_ADVANCED, 345 USER_ACTION_NEW_WINDOW, 346 USER_ACTION_PASTE_CLIPBOARD, 347 USER_ACTION_COPY_CLIPBOARD, 348 USER_ACTION_DRAG_N_DROP, 349 USER_ACTION_DRAG_N_DROP_MULTI_WINDOW, 350 USER_ACTION_CUT_CLIPBOARD, 351 USER_ACTION_COMPRESS, 352 USER_ACTION_EXTRACT_TO, 353 USER_ACTION_VIEW_IN_APPLICATION, 354 USER_ACTION_INSPECTOR 355 }) 356 @Retention(RetentionPolicy.SOURCE) 357 public @interface UserAction {} 358 359 // Codes representing different approaches to copy/move a document. OPMODE_PROVIDER indicates 360 // it's an optimized operation provided by providers; OPMODE_CONVERTED means it's converted from 361 // a virtual file; and OPMODE_CONVENTIONAL means it's byte copied. 362 public static final int OPMODE_PROVIDER = 1; 363 public static final int OPMODE_CONVERTED = 2; 364 public static final int OPMODE_CONVENTIONAL = 3; 365 @IntDef({OPMODE_PROVIDER, OPMODE_CONVERTED, OPMODE_CONVENTIONAL}) 366 @Retention(RetentionPolicy.SOURCE) 367 public @interface FileOpMode {} 368 369 // Codes representing different menu actions. These are used for bucketing stats in the 370 // COUNT_MENU_ACTION histogram. 371 // Do not change or rearrange these values, that will break historical data. Only add to the 372 // list. 373 // Do not use negative numbers or zero; clearcut only handles positive integers. 374 private static final int ACTION_OTHER = 1; 375 private static final int ACTION_OPEN = 2; 376 private static final int ACTION_CREATE = 3; 377 private static final int ACTION_GET_CONTENT = 4; 378 private static final int ACTION_OPEN_TREE = 5; 379 @Deprecated private static final int ACTION_MANAGE = 6; 380 private static final int ACTION_BROWSE = 7; 381 private static final int ACTION_PICK_COPY_DESTINATION = 8; 382 383 @IntDef(flag = true, value = { 384 ACTION_OTHER, 385 ACTION_OPEN, 386 ACTION_CREATE, 387 ACTION_GET_CONTENT, 388 ACTION_OPEN_TREE, 389 ACTION_MANAGE, 390 ACTION_BROWSE, 391 ACTION_PICK_COPY_DESTINATION 392 }) 393 @Retention(RetentionPolicy.SOURCE) 394 public @interface MetricsAction {} 395 396 // Codes representing different actions to open the drawer. They are used for bucketing stats in 397 // the COUNT_DRAWER_OPENED histogram. 398 // Do not change or rearrange these values, that will break historical data. Only add to the 399 // list. 400 // Do not use negative numbers or zero; clearcut only handles positive integers. 401 private static final int DRAWER_OPENED_HAMBURGER = 1; 402 private static final int DRAWER_OPENED_SWIPE = 2; 403 404 @IntDef(flag = true, value = { 405 DRAWER_OPENED_HAMBURGER, 406 DRAWER_OPENED_SWIPE 407 }) 408 @Retention(RetentionPolicy.SOURCE) 409 public @interface DrawerTrigger {} 410 411 /** 412 * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up. 413 * 414 * @param context 415 * @param state 416 * @param intent 417 */ 418 public static void logActivityLaunch(Context context, State state, Intent intent) { 419 // Log the launch action. 420 logHistogram(context, COUNT_LAUNCH_ACTION, toMetricsAction(state.action)); 421 // Then log auxiliary data (roots/mime types) associated with some actions. 422 Uri uri = intent.getData(); 423 switch (state.action) { 424 case State.ACTION_OPEN: 425 logHistogram(context, COUNT_OPEN_MIME, sanitizeMime(intent.getType())); 426 break; 427 case State.ACTION_CREATE: 428 logHistogram(context, COUNT_CREATE_MIME, sanitizeMime(intent.getType())); 429 break; 430 case State.ACTION_GET_CONTENT: 431 logHistogram(context, COUNT_GET_CONTENT_MIME, sanitizeMime(intent.getType())); 432 break; 433 case State.ACTION_BROWSE: 434 logHistogram(context, COUNT_BROWSE_ROOT, sanitizeRoot(uri)); 435 break; 436 default: 437 break; 438 } 439 } 440 441 /** 442 * Logs when DocumentsUI are launched with {@link DocumentsContract#EXTRA_INITIAL_URI}. 443 * 444 * @param context 445 * @param state used to resolve action 446 * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't 447 * support {@link DocumentsProvider#findDocumentPath(String, String)} 448 */ 449 public static void logLaunchAtLocation(Context context, State state, @Nullable Uri rootUri) { 450 switch (state.action) { 451 case State.ACTION_BROWSE: 452 logHistogram(context, COUNT_BROWSE_AT_LOCATION, sanitizeRoot(rootUri)); 453 break; 454 case State.ACTION_CREATE: 455 logHistogram(context, COUNT_CREATE_AT_LOCATION, sanitizeRoot(rootUri)); 456 break; 457 case State.ACTION_GET_CONTENT: 458 logHistogram(context, COUNT_GET_CONTENT_AT_LOCATION, sanitizeRoot(rootUri)); 459 break; 460 case State.ACTION_OPEN: 461 logHistogram(context, COUNT_OPEN_AT_LOCATION, sanitizeRoot(rootUri)); 462 break; 463 } 464 } 465 466 /** 467 * Logs a root visited event in file managers. Call this when the user 468 * taps on a root in {@link com.android.documentsui.sidebar.RootsFragment}. 469 * 470 * @param context 471 * @param scope 472 * @param info 473 */ 474 public static void logRootVisited( 475 Context context, @ContextScope int scope, RootInfo info) { 476 switch (scope) { 477 case FILES_SCOPE: 478 logHistogram(context, COUNT_ROOT_VISITED_IN_MANAGER, 479 sanitizeRoot(info)); 480 break; 481 case PICKER_SCOPE: 482 logHistogram(context, COUNT_ROOT_VISITED_IN_PICKER, 483 sanitizeRoot(info)); 484 break; 485 } 486 } 487 488 /** 489 * Logs an app visited event in file pickers. Call this when the user visits 490 * on an app in the RootsFragment. 491 * 492 * @param context 493 * @param info 494 */ 495 public static void logAppVisited(Context context, ResolveInfo info) { 496 logHistogram(context, COUNT_ROOT_VISITED_IN_PICKER, sanitizeRoot(info)); 497 } 498 499 /** 500 * Logs file operation stats. Call this when a file operation has completed. The given 501 * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one 502 * provider to another vs copying within a given provider). No PII is logged. 503 * 504 * @param context 505 * @param operationType 506 * @param srcs 507 * @param dst 508 */ 509 public static void logFileOperation( 510 Context context, 511 @OpType int operationType, 512 List<DocumentInfo> srcs, 513 @Nullable DocumentInfo dst) { 514 515 ProviderCounts counts = new ProviderCounts(); 516 countProviders(counts, srcs, dst); 517 518 if (counts.intraProvider > 0) { 519 logIntraProviderFileOps(context, dst.authority, operationType); 520 } 521 if (counts.systemProvider > 0) { 522 // Log file operations on system providers. 523 logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType); 524 } 525 if (counts.externalProvider > 0) { 526 // Log file operations on external providers. 527 logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType); 528 } 529 } 530 531 public static void logFileOperated( 532 Context context, @OpType int operationType, @FileOpMode int approach) { 533 switch (operationType) { 534 case FileOperationService.OPERATION_COPY: 535 logHistogram(context, COUNT_FILE_COPIED, approach); 536 break; 537 case FileOperationService.OPERATION_MOVE: 538 logHistogram(context, COUNT_FILE_MOVED, approach); 539 break; 540 } 541 } 542 543 /** 544 * Logs create directory operation. It is a part of file operation stats. We do not 545 * differentiate between internal and external locations, all create directory operations are 546 * logged under COUNT_FILEOP_SYSTEM. Call this when a create directory operation has completed. 547 * 548 * @param context 549 */ 550 public static void logCreateDirOperation(Context context) { 551 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR); 552 } 553 554 /** 555 * Logs rename file operation. It is a part of file operation stats. We do not differentiate 556 * between internal and external locations, all rename operations are logged under 557 * COUNT_FILEOP_SYSTEM. Call this when a rename file operation has completed. 558 * 559 * @param context 560 */ 561 public static void logRenameFileOperation(Context context) { 562 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME); 563 } 564 565 /** 566 * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete) 567 * fails. 568 * 569 * @param context 570 * @param operationType 571 * @param failedFiles 572 */ 573 public static void logFileOperationErrors(Context context, @OpType int operationType, 574 List<DocumentInfo> failedFiles, List<Uri> failedUris) { 575 576 ProviderCounts counts = new ProviderCounts(); 577 countProviders(counts, failedFiles, null); 578 579 // TODO: Report URI errors separate from file operation errors. 580 countProviders(counts, failedUris); 581 582 @FileOp int opCode = FILEOP_OTHER_ERROR; 583 switch (operationType) { 584 case FileOperationService.OPERATION_COPY: 585 opCode = FILEOP_COPY_ERROR; 586 break; 587 case FileOperationService.OPERATION_COMPRESS: 588 opCode = FILEOP_COMPRESS_ERROR; 589 break; 590 case FileOperationService.OPERATION_EXTRACT: 591 opCode = FILEOP_EXTRACT_ERROR; 592 break; 593 case FileOperationService.OPERATION_DELETE: 594 opCode = FILEOP_DELETE_ERROR; 595 break; 596 case FileOperationService.OPERATION_MOVE: 597 opCode = FILEOP_MOVE_ERROR; 598 break; 599 } 600 if (counts.systemProvider > 0) { 601 logHistogram(context, COUNT_FILEOP_SYSTEM, opCode); 602 } 603 if (counts.externalProvider > 0) { 604 logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode); 605 } 606 } 607 608 public static void logFileOperationFailure( 609 Context context, @SubFileOp int subFileOp, Uri docUri) { 610 final String authority = docUri.getAuthority(); 611 switch (authority) { 612 case Providers.AUTHORITY_MEDIA: 613 logHistogram(context, COUNT_MEDIA_FILEOP_FAILURE, subFileOp); 614 break; 615 case Providers.AUTHORITY_STORAGE: 616 logStorageFileOperationFailure(context, subFileOp, docUri); 617 break; 618 case Providers.AUTHORITY_DOWNLOADS: 619 logHistogram(context, COUNT_DOWNLOADS_FILEOP_FAILURE, subFileOp); 620 break; 621 case Providers.AUTHORITY_MTP: 622 logHistogram(context, COUNT_MTP_FILEOP_FAILURE, subFileOp); 623 break; 624 default: 625 logHistogram(context, COUNT_OTHER_FILEOP_FAILURE, subFileOp); 626 break; 627 } 628 } 629 630 /** 631 * Logs create directory operation error. We do not differentiate between internal and external 632 * locations, all create directory errors are logged under COUNT_FILEOP_SYSTEM. Call this when a 633 * create directory operation fails. 634 * 635 * @param context 636 */ 637 public static void logCreateDirError(Context context) { 638 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR_ERROR); 639 } 640 641 /** 642 * Logs rename file operation error. We do not differentiate between internal and external 643 * locations, all rename errors are logged under COUNT_FILEOP_SYSTEM. Call this 644 * when a rename file operation fails. 645 * 646 * @param context 647 */ 648 public static void logRenameFileError(Context context) { 649 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME_ERROR); 650 } 651 652 /** 653 * Logs the cancellation of a file operation. Call this when a Job is canceled. 654 * @param context 655 * @param operationType 656 */ 657 public static void logFileOperationCancelled(Context context, @OpType int operationType) { 658 logHistogram(context, COUNT_FILEOP_CANCELED, toMetricsOpType(operationType)); 659 } 660 661 /** 662 * Logs startup time in milliseconds. 663 * @param context 664 * @param startupMs Startup time in milliseconds. 665 */ 666 public static void logStartupMs(Context context, int startupMs) { 667 logHistogram(context, COUNT_STARTUP_MS, startupMs); 668 } 669 670 private static void logInterProviderFileOps( 671 Context context, 672 String histogram, 673 DocumentInfo dst, 674 @OpType int operationType) { 675 if (operationType == FileOperationService.OPERATION_DELETE) { 676 logHistogram(context, histogram, FILEOP_DELETE); 677 } else { 678 assert(dst != null); 679 @Provider int providerType = 680 isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL; 681 logHistogram(context, histogram, getOpCode(operationType, providerType)); 682 } 683 } 684 685 private static void logIntraProviderFileOps( 686 Context context, String authority, @OpType int operationType) { 687 // Find the right histogram to log to, then log the operation. 688 String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL; 689 logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA)); 690 } 691 692 // Types for logInvalidScopedAccessRequest 693 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS = 694 "docsui_scoped_directory_access_invalid_args"; 695 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY = 696 "docsui_scoped_directory_access_invalid_dir"; 697 public static final String SCOPED_DIRECTORY_ACCESS_ERROR = 698 "docsui_scoped_directory_access_error"; 699 700 @StringDef(value = { 701 SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS, 702 SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY, 703 SCOPED_DIRECTORY_ACCESS_ERROR 704 }) 705 @Retention(RetentionPolicy.SOURCE) 706 public @interface InvalidScopedAccess{} 707 708 public static void logInvalidScopedAccessRequest(Context context, 709 @InvalidScopedAccess String type) { 710 switch (type) { 711 case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS: 712 case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY: 713 case SCOPED_DIRECTORY_ACCESS_ERROR: 714 logCount(context, type); 715 break; 716 default: 717 Log.wtf(TAG, "invalid InvalidScopedAccess: " + type); 718 } 719 } 720 721 // Types for logValidScopedAccessRequest 722 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0; 723 public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1; 724 public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2; 725 public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3; 726 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4; 727 728 @IntDef(flag = true, value = { 729 SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED, 730 SCOPED_DIRECTORY_ACCESS_GRANTED, 731 SCOPED_DIRECTORY_ACCESS_DENIED, 732 SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST, 733 SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED 734 }) 735 @Retention(RetentionPolicy.SOURCE) 736 public @interface ScopedAccessGrant {} 737 738 public static void logValidScopedAccessRequest(Activity activity, String directory, 739 @ScopedAccessGrant int type) { 740 int index = -1; 741 if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) { 742 index = -2; 743 } else { 744 for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) { 745 if (STANDARD_DIRECTORIES[i].equals(directory)) { 746 index = i; 747 break; 748 } 749 } 750 } 751 final String packageName = activity.getCallingPackage(); 752 switch (type) { 753 case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED: 754 MetricsLogger.action(activity, MetricsEvent 755 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName); 756 MetricsLogger.action(activity, MetricsEvent 757 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index); 758 break; 759 case SCOPED_DIRECTORY_ACCESS_GRANTED: 760 MetricsLogger.action(activity, MetricsEvent 761 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName); 762 MetricsLogger.action(activity, MetricsEvent 763 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index); 764 break; 765 case SCOPED_DIRECTORY_ACCESS_DENIED: 766 MetricsLogger.action(activity, MetricsEvent 767 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName); 768 MetricsLogger.action(activity, MetricsEvent 769 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index); 770 break; 771 case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST: 772 MetricsLogger.action(activity, MetricsEvent 773 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName); 774 MetricsLogger.action(activity, MetricsEvent 775 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index); 776 break; 777 case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED: 778 MetricsLogger.action(activity, MetricsEvent 779 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName); 780 MetricsLogger.action(activity, MetricsEvent 781 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index); 782 break; 783 default: 784 Log.wtf(TAG, "invalid ScopedAccessGrant: " + type); 785 } 786 } 787 788 /** 789 * Logs the action that was started by user. 790 * @param context 791 * @param userAction 792 */ 793 public static void logUserAction(Context context, @UserAction int userAction) { 794 logHistogram(context, COUNT_USER_ACTION, userAction); 795 } 796 797 private static void logStorageFileOperationFailure( 798 Context context, @SubFileOp int subFileOp, Uri docUri) { 799 assert(Providers.AUTHORITY_STORAGE.equals(docUri.getAuthority())); 800 801 boolean isInternal; 802 try (ContentProviderClient client = acquireUnstableProviderOrThrow( 803 context.getContentResolver(), Providers.AUTHORITY_STORAGE)) { 804 final Path path = DocumentsContract.findDocumentPath(client, docUri); 805 806 final ProvidersAccess providers = DocumentsApplication.getProvidersCache(context); 807 final RootInfo root = providers.getRootOneshot( 808 Providers.AUTHORITY_STORAGE, path.getRootId()); 809 isInternal = !root.supportsEject(); 810 } catch (RemoteException | RuntimeException e) { 811 Log.e(TAG, "Failed to obtain its root info. Log the metrics as internal.", e); 812 // It's not very likely to have an external storage so log it as internal. 813 isInternal = true; 814 } 815 816 final String histogram = isInternal 817 ? COUNT_INTERNAL_STORAGE_FILEOP_FAILURE 818 : COUNT_EXTERNAL_STORAGE_FILEOP_FAILURE; 819 logHistogram(context, histogram, subFileOp); 820 } 821 822 /** 823 * Internal method for making a MetricsLogger.count call. Increments the given counter by 1. 824 * 825 * @param context 826 * @param name The counter to increment. 827 */ 828 private static void logCount(Context context, String name) { 829 if (DEBUG) Log.d(TAG, name + ": " + 1); 830 MetricsLogger.count(context, name, 1); 831 } 832 833 /** 834 * Internal method for making a MetricsLogger.histogram call. 835 * 836 * @param context 837 * @param name The name of the histogram. 838 * @param bucket The bucket to increment. 839 */ 840 private static void logHistogram(Context context, String name, @ActionType int bucket) { 841 if (DEBUG) Log.d(TAG, name + ": " + bucket); 842 MetricsLogger.histogram(context, name, bucket); 843 } 844 845 /** 846 * Generates an integer identifying the given root. For privacy, this function only recognizes a 847 * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into 848 * a single ROOT_OTHER bucket. 849 */ 850 private static @Root int sanitizeRoot(Uri uri) { 851 if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) { 852 return ROOT_NONE; 853 } 854 855 switch (uri.getAuthority()) { 856 case Providers.AUTHORITY_MEDIA: 857 switch (DocumentsContract.getRootId(uri)) { 858 case Providers.ROOT_ID_AUDIO: 859 return ROOT_AUDIO; 860 case Providers.ROOT_ID_IMAGES: 861 return ROOT_IMAGES; 862 case Providers.ROOT_ID_VIDEOS: 863 return ROOT_VIDEOS; 864 default: 865 return ROOT_OTHER; 866 } 867 case Providers.AUTHORITY_STORAGE: 868 if (Providers.ROOT_ID_HOME.equals(DocumentsContract.getRootId(uri))) { 869 return ROOT_HOME; 870 } else { 871 return ROOT_DEVICE_STORAGE; 872 } 873 case Providers.AUTHORITY_DOWNLOADS: 874 return ROOT_DOWNLOADS; 875 case Providers.AUTHORITY_MTP: 876 return ROOT_MTP; 877 default: 878 return ROOT_OTHER; 879 } 880 } 881 882 /** @see #sanitizeRoot(Uri) */ 883 private static @Root int sanitizeRoot(RootInfo root) { 884 if (root.isRecents()) { 885 // Recents root is special and only identifiable via this method call. Other roots are 886 // identified by URI. 887 return ROOT_RECENTS; 888 } else { 889 return sanitizeRoot(root.getUri()); 890 } 891 } 892 893 /** @see #sanitizeRoot(Uri) */ 894 private static @Root int sanitizeRoot(ResolveInfo info) { 895 // Log all apps under a single bucket in the roots histogram. 896 return ROOT_THIRD_PARTY_APP; 897 } 898 899 /** 900 * Generates an int identifying a mime type. For privacy, this function only recognizes a small 901 * set of hard-coded types. For any other type, this function returns "other". 902 * 903 * @param mimeType 904 * @return 905 */ 906 private static @Mime int sanitizeMime(String mimeType) { 907 if (mimeType == null) { 908 return MIME_NONE; 909 } else if ("*/*".equals(mimeType)) { 910 return MIME_ANY; 911 } else { 912 String type = mimeType.substring(0, mimeType.indexOf('/')); 913 switch (type) { 914 case "application": 915 return MIME_APPLICATION; 916 case "audio": 917 return MIME_AUDIO; 918 case "image": 919 return MIME_IMAGE; 920 case "message": 921 return MIME_MESSAGE; 922 case "multipart": 923 return MIME_MULTIPART; 924 case "text": 925 return MIME_TEXT; 926 case "video": 927 return MIME_VIDEO; 928 } 929 } 930 // Bucket all other types into one bucket. 931 return MIME_OTHER; 932 } 933 934 private static boolean isSystemProvider(String authority) { 935 switch (authority) { 936 case Providers.AUTHORITY_MEDIA: 937 case Providers.AUTHORITY_STORAGE: 938 case Providers.AUTHORITY_DOWNLOADS: 939 return true; 940 default: 941 return false; 942 } 943 } 944 945 /** 946 * @param operation 947 * @param providerType 948 * @return An opcode, suitable for use as histogram bucket, for the given operation/provider 949 * combination. 950 */ 951 private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) { 952 switch (operation) { 953 case FileOperationService.OPERATION_COPY: 954 switch (providerType) { 955 case PROVIDER_INTRA: 956 return FILEOP_COPY_INTRA_PROVIDER; 957 case PROVIDER_SYSTEM: 958 return FILEOP_COPY_SYSTEM_PROVIDER; 959 case PROVIDER_EXTERNAL: 960 return FILEOP_COPY_EXTERNAL_PROVIDER; 961 } 962 case FileOperationService.OPERATION_COMPRESS: 963 switch (providerType) { 964 case PROVIDER_INTRA: 965 return FILEOP_COMPRESS_INTRA_PROVIDER; 966 case PROVIDER_SYSTEM: 967 return FILEOP_COMPRESS_SYSTEM_PROVIDER; 968 case PROVIDER_EXTERNAL: 969 return FILEOP_COMPRESS_EXTERNAL_PROVIDER; 970 } 971 case FileOperationService.OPERATION_EXTRACT: 972 switch (providerType) { 973 case PROVIDER_INTRA: 974 return FILEOP_EXTRACT_INTRA_PROVIDER; 975 case PROVIDER_SYSTEM: 976 return FILEOP_EXTRACT_SYSTEM_PROVIDER; 977 case PROVIDER_EXTERNAL: 978 return FILEOP_EXTRACT_EXTERNAL_PROVIDER; 979 } 980 case FileOperationService.OPERATION_MOVE: 981 switch (providerType) { 982 case PROVIDER_INTRA: 983 return FILEOP_MOVE_INTRA_PROVIDER; 984 case PROVIDER_SYSTEM: 985 return FILEOP_MOVE_SYSTEM_PROVIDER; 986 case PROVIDER_EXTERNAL: 987 return FILEOP_MOVE_EXTERNAL_PROVIDER; 988 } 989 case FileOperationService.OPERATION_DELETE: 990 return FILEOP_DELETE; 991 default: 992 Log.w(TAG, "Unrecognized operation type when logging a file operation"); 993 return FILEOP_OTHER; 994 } 995 } 996 997 /** 998 * Maps FileOperationService OpType values, to MetricsOpType values. 999 */ 1000 private static @MetricsOpType int toMetricsOpType(@OpType int operation) { 1001 switch (operation) { 1002 case FileOperationService.OPERATION_COPY: 1003 return OPERATION_COPY; 1004 case FileOperationService.OPERATION_MOVE: 1005 return OPERATION_MOVE; 1006 case FileOperationService.OPERATION_DELETE: 1007 return OPERATION_DELETE; 1008 case FileOperationService.OPERATION_UNKNOWN: 1009 default: 1010 return OPERATION_UNKNOWN; 1011 } 1012 } 1013 1014 private static @MetricsAction int toMetricsAction(int action) { 1015 switch(action) { 1016 case State.ACTION_OPEN: 1017 return ACTION_OPEN; 1018 case State.ACTION_CREATE: 1019 return ACTION_CREATE; 1020 case State.ACTION_GET_CONTENT: 1021 return ACTION_GET_CONTENT; 1022 case State.ACTION_OPEN_TREE: 1023 return ACTION_OPEN_TREE; 1024 case State.ACTION_BROWSE: 1025 return ACTION_BROWSE; 1026 case State.ACTION_PICK_COPY_DESTINATION: 1027 return ACTION_PICK_COPY_DESTINATION; 1028 default: 1029 return ACTION_OTHER; 1030 } 1031 } 1032 1033 /** 1034 * Count the given src documents and provide a tally of how many come from the same provider as 1035 * the dst document (if a dst is provided), how many come from system providers, and how many 1036 * come from external 3rd-party providers. 1037 */ 1038 private static void countProviders( 1039 ProviderCounts counts, List<DocumentInfo> srcs, @Nullable DocumentInfo dst) { 1040 for (DocumentInfo doc: srcs) { 1041 countForAuthority(counts, doc.authority, dst); 1042 } 1043 } 1044 1045 /** 1046 * Count the given uris and provide a tally of how many come from the same provider as 1047 * the dst document (if a dst is provided), how many come from system providers, and how many 1048 * come from external 3rd-party providers. 1049 */ 1050 private static void countProviders(ProviderCounts counts, List<Uri> uris) { 1051 for (Uri uri: uris) { 1052 countForAuthority(counts, uri.getAuthority(), null); 1053 } 1054 } 1055 1056 private static void countForAuthority( 1057 ProviderCounts counts, String authority, @Nullable DocumentInfo dst) { 1058 if (dst != null && authority.equals(dst.authority)) { 1059 counts.intraProvider++; 1060 } else if (isSystemProvider(authority)){ 1061 counts.systemProvider++; 1062 } else { 1063 counts.externalProvider++; 1064 } 1065 } 1066 1067 private static class ProviderCounts { 1068 int intraProvider; 1069 int systemProvider; 1070 int externalProvider; 1071 } 1072 } 1073