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