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.content.ContentResolver.wrap; 20 21 import static com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow; 22 23 import android.content.ContentProviderClient; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.ResolveInfo; 27 import android.net.Uri; 28 import android.os.RemoteException; 29 import android.provider.DocumentsContract; 30 import android.provider.DocumentsContract.Path; 31 import android.provider.DocumentsProvider; 32 import android.util.Log; 33 34 import androidx.annotation.Nullable; 35 36 import com.android.documentsui.base.DocumentInfo; 37 import com.android.documentsui.base.Providers; 38 import com.android.documentsui.base.RootInfo; 39 import com.android.documentsui.base.State; 40 import com.android.documentsui.files.LauncherActivity; 41 import com.android.documentsui.picker.PickResult; 42 import com.android.documentsui.roots.ProvidersAccess; 43 import com.android.documentsui.services.FileOperationService; 44 import com.android.documentsui.services.FileOperationService.OpType; 45 46 import java.io.FileNotFoundException; 47 import java.util.List; 48 49 /** 50 * Methods for logging metrics. 51 */ 52 public final class Metrics { 53 private static final String TAG = "Metrics"; 54 55 /** 56 * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up. 57 * 58 * @param state 59 * @param intent 60 */ logActivityLaunch(State state, Intent intent)61 public static void logActivityLaunch(State state, Intent intent) { 62 Uri uri = intent.getData(); 63 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_LAUNCH_REPORTED, 64 toMetricsAction(state.action), false, 65 sanitizeMime(intent.getType()), sanitizeRoot(uri)); 66 } 67 68 /** 69 * Logs when DocumentsUI are launched with {@link DocumentsContract#EXTRA_INITIAL_URI}. 70 * 71 * @param state used to resolve action 72 * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't 73 * support {@link DocumentsProvider#findDocumentPath(String, String)} 74 */ logLaunchAtLocation(State state, @Nullable Uri rootUri)75 public static void logLaunchAtLocation(State state, @Nullable Uri rootUri) { 76 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_LAUNCH_REPORTED, 77 toMetricsAction(state.action), true, 78 MetricConsts.MIME_UNKNOWN, sanitizeRoot(rootUri)); 79 } 80 81 /** 82 * Logs a root visited event in file managers. Call this when the user 83 * taps on a root in {@link com.android.documentsui.sidebar.RootsFragment}. 84 * @param scope 85 * @param info 86 */ logRootVisited(@etricConsts.ContextScope int scope, RootInfo info)87 public static void logRootVisited(@MetricConsts.ContextScope int scope, RootInfo info) { 88 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_ROOT_VISITED, scope, sanitizeRoot(info)); 89 } 90 91 /** 92 * Logs an app visited event in file pickers. Call this when the user visits 93 * on an app in the RootsFragment. 94 * 95 * @param info 96 */ logAppVisited(ResolveInfo info)97 public static void logAppVisited(ResolveInfo info) { 98 DocumentsStatsLog.write( 99 DocumentsStatsLog.DOCS_UI_ROOT_VISITED, 100 MetricConsts.PICKER_SCOPE, sanitizeRoot(info)); 101 } 102 103 /** 104 * Logs file operation stats. Call this when a file operation has completed. The given 105 * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one 106 * provider to another vs copying within a given provider). No PII is logged. 107 * 108 * @param operationType 109 * @param srcs 110 * @param dst 111 */ logFileOperation( @pType int operationType, List<DocumentInfo> srcs, @Nullable DocumentInfo dst)112 public static void logFileOperation( 113 @OpType int operationType, 114 List<DocumentInfo> srcs, 115 @Nullable DocumentInfo dst) { 116 ProviderCounts counts = new ProviderCounts(); 117 countProviders(counts, srcs, dst); 118 if (counts.intraProvider > 0) { 119 logIntraProviderFileOps(dst.authority, operationType); 120 } 121 if (counts.systemProvider > 0) { 122 // Log file operations on system providers. 123 logInterProviderFileOps(MetricConsts.PROVIDER_SYSTEM, dst, operationType); 124 } 125 if (counts.externalProvider > 0) { 126 // Log file operations on external providers. 127 logInterProviderFileOps(MetricConsts.PROVIDER_EXTERNAL, dst, operationType); 128 } 129 } 130 logFileOperated( @pType int operationType, @MetricConsts.FileOpMode int approach)131 public static void logFileOperated( 132 @OpType int operationType, @MetricConsts.FileOpMode int approach) { 133 switch (operationType) { 134 case FileOperationService.OPERATION_COPY: 135 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, 136 MetricConsts.FILEOP_COPY, approach); 137 break; 138 case FileOperationService.OPERATION_MOVE: 139 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, 140 MetricConsts.FILEOP_MOVE, approach); 141 break; 142 } 143 } 144 145 /** 146 * Logs create directory operation. It is a part of file operation stats. We do not 147 * differentiate between internal and external locations, all create directory operations are 148 * logged under COUNT_FILEOP_SYSTEM. Call this when a create directory operation has completed. 149 */ logCreateDirOperation()150 public static void logCreateDirOperation() { 151 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 152 MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_CREATE_DIR); 153 } 154 155 /** 156 * Logs rename file operation. It is a part of file operation stats. We do not differentiate 157 * between internal and external locations, all rename operations are logged under 158 * COUNT_FILEOP_SYSTEM. Call this when a rename file operation has completed. 159 */ logRenameFileOperation()160 public static void logRenameFileOperation() { 161 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 162 MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_RENAME); 163 } 164 165 /** 166 * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete) 167 * fails. 168 * 169 * @param operationType 170 * @param failedFiles 171 */ logFileOperationErrors(@pType int operationType, List<DocumentInfo> failedFiles, List<Uri> failedUris)172 public static void logFileOperationErrors(@OpType int operationType, 173 List<DocumentInfo> failedFiles, List<Uri> failedUris) { 174 ProviderCounts counts = new ProviderCounts(); 175 countProviders(counts, failedFiles, null); 176 // TODO: Report URI errors separate from file operation errors. 177 countProviders(counts, failedUris); 178 @MetricConsts.FileOp int opCode = MetricConsts.FILEOP_OTHER_ERROR; 179 switch (operationType) { 180 case FileOperationService.OPERATION_COPY: 181 opCode = MetricConsts.FILEOP_COPY_ERROR; 182 break; 183 case FileOperationService.OPERATION_COMPRESS: 184 opCode = MetricConsts.FILEOP_COMPRESS_ERROR; 185 break; 186 case FileOperationService.OPERATION_EXTRACT: 187 opCode = MetricConsts.FILEOP_EXTRACT_ERROR; 188 break; 189 case FileOperationService.OPERATION_DELETE: 190 opCode = MetricConsts.FILEOP_DELETE_ERROR; 191 break; 192 case FileOperationService.OPERATION_MOVE: 193 opCode = MetricConsts.FILEOP_MOVE_ERROR; 194 break; 195 } 196 if (counts.systemProvider > 0) { 197 DocumentsStatsLog.write( 198 DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 199 MetricConsts.PROVIDER_SYSTEM, opCode); 200 } 201 if (counts.externalProvider > 0) { 202 DocumentsStatsLog.write( 203 DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 204 MetricConsts.PROVIDER_EXTERNAL, opCode); 205 } 206 } 207 logFileOperationFailure( Context context, @MetricConsts.SubFileOp int subFileOp, Uri docUri)208 public static void logFileOperationFailure( 209 Context context, @MetricConsts.SubFileOp int subFileOp, Uri docUri) { 210 final String authority = docUri.getAuthority(); 211 switch (authority) { 212 case Providers.AUTHORITY_MEDIA: 213 DocumentsStatsLog.write( 214 DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, 215 MetricConsts.AUTH_MEDIA, subFileOp); 216 break; 217 case Providers.AUTHORITY_STORAGE: 218 logStorageFileOperationFailure(context, subFileOp, docUri); 219 break; 220 case Providers.AUTHORITY_DOWNLOADS: 221 DocumentsStatsLog.write( 222 DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, 223 MetricConsts.AUTH_DOWNLOADS, subFileOp); 224 break; 225 case Providers.AUTHORITY_MTP: 226 DocumentsStatsLog.write( 227 DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, 228 MetricConsts.AUTH_MTP, subFileOp); 229 break; 230 default: 231 DocumentsStatsLog.write( 232 DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, 233 MetricConsts.AUTH_OTHER, subFileOp); 234 break; 235 } 236 } 237 238 /** 239 * Logs create directory operation error. We do not differentiate between internal and external 240 * locations, all create directory errors are logged under COUNT_FILEOP_SYSTEM. Call this when a 241 * create directory operation fails. 242 */ logCreateDirError()243 public static void logCreateDirError() { 244 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 245 MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_CREATE_DIR_ERROR); 246 } 247 248 /** 249 * Logs rename file operation error. We do not differentiate between internal and external 250 * locations, all rename errors are logged under COUNT_FILEOP_SYSTEM. Call this 251 * when a rename file operation fails. 252 */ logRenameFileError()253 public static void logRenameFileError() { 254 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 255 MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_RENAME_ERROR); 256 } 257 258 /** 259 * Logs the cancellation of a file operation. Call this when a Job is canceled. 260 * 261 * @param operationType 262 */ logFileOperationCancelled(@pType int operationType)263 public static void logFileOperationCancelled(@OpType int operationType) { 264 DocumentsStatsLog.write( 265 DocumentsStatsLog.DOCS_UI_FILE_OP_CANCELED, toMetricsOpType(operationType)); 266 } 267 268 /** 269 * Logs startup time in milliseconds. 270 * 271 * @param startupMs Startup time in milliseconds. 272 */ logStartupMs(int startupMs)273 public static void logStartupMs(int startupMs) { 274 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_STARTUP_MS, startupMs); 275 } 276 logInterProviderFileOps( @etricConsts.Provider int providerType, DocumentInfo dst, @OpType int operationType)277 private static void logInterProviderFileOps( 278 @MetricConsts.Provider int providerType, 279 DocumentInfo dst, 280 @OpType int operationType) { 281 if (operationType == FileOperationService.OPERATION_DELETE) { 282 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 283 providerType, MetricConsts.FILEOP_DELETE); 284 } else { 285 assert(dst != null); 286 @MetricConsts.Provider int opProviderType = isSystemProvider(dst.authority) 287 ? MetricConsts.PROVIDER_SYSTEM : MetricConsts.PROVIDER_EXTERNAL; 288 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 289 providerType, getOpCode(operationType, opProviderType)); 290 } 291 } 292 logIntraProviderFileOps(String authority, @OpType int operationType)293 private static void logIntraProviderFileOps(String authority, @OpType int operationType) { 294 @MetricConsts.Provider int providerType = isSystemProvider(authority) 295 ? MetricConsts.PROVIDER_SYSTEM : MetricConsts.PROVIDER_EXTERNAL; 296 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, 297 providerType, getOpCode(operationType, MetricConsts.PROVIDER_INTRA)); 298 } 299 300 /** 301 * Logs the action that was started by user. 302 * 303 * @param userAction 304 */ logUserAction(@etricConsts.UserAction int userAction)305 public static void logUserAction(@MetricConsts.UserAction int userAction) { 306 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_USER_ACTION_REPORTED, userAction); 307 } 308 logPickerLaunchedFrom(String packgeName)309 public static void logPickerLaunchedFrom(String packgeName) { 310 DocumentsStatsLog.write( 311 DocumentsStatsLog.DOCS_UI_PICKER_LAUNCHED_FROM_REPORTED, packgeName); 312 } 313 logSearchType(int searchType)314 public static void logSearchType(int searchType) { 315 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_SEARCH_TYPE_REPORTED, searchType); 316 } 317 logSearchMode(boolean isKeywordSearch, boolean isChipsSearch)318 public static void logSearchMode(boolean isKeywordSearch, boolean isChipsSearch) { 319 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_SEARCH_MODE_REPORTED, 320 getSearchMode(isKeywordSearch, isChipsSearch)); 321 } 322 logPickResult(PickResult result)323 public static void logPickResult(PickResult result) { 324 DocumentsStatsLog.write( 325 DocumentsStatsLog.DOCS_UI_PICK_RESULT_REPORTED, 326 result.getActionCount(), 327 result.getDuration(), 328 result.getFileCount(), 329 result.isSearching(), 330 result.getRoot(), 331 result.getMimeType(), 332 result.getRepeatedPickTimes()); 333 } 334 logStorageFileOperationFailure( Context context, @MetricConsts.SubFileOp int subFileOp, Uri docUri)335 private static void logStorageFileOperationFailure( 336 Context context, @MetricConsts.SubFileOp int subFileOp, Uri docUri) { 337 assert(Providers.AUTHORITY_STORAGE.equals(docUri.getAuthority())); 338 boolean isInternal; 339 try (ContentProviderClient client = acquireUnstableProviderOrThrow( 340 context.getContentResolver(), Providers.AUTHORITY_STORAGE)) { 341 final Path path = DocumentsContract.findDocumentPath(wrap(client), docUri); 342 final ProvidersAccess providers = DocumentsApplication.getProvidersCache(context); 343 final RootInfo root = providers.getRootOneshot( 344 Providers.AUTHORITY_STORAGE, path.getRootId()); 345 isInternal = !root.supportsEject(); 346 } catch (FileNotFoundException | RemoteException | RuntimeException e) { 347 Log.e(TAG, "Failed to obtain its root info. Log the metrics as internal.", e); 348 // It's not very likely to have an external storage so log it as internal. 349 isInternal = true; 350 } 351 @MetricConsts.MetricsAuth final int authority = isInternal 352 ? MetricConsts.AUTH_STORAGE_INTERNAL : MetricConsts.AUTH_STORAGE_EXTERNAL; 353 DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, authority, subFileOp); 354 } 355 356 /** 357 * Generates an integer identifying the given root. For privacy, this function only recognizes a 358 * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into 359 * a single ROOT_OTHER bucket. 360 */ sanitizeRoot(Uri uri)361 private static @MetricConsts.Root int sanitizeRoot(Uri uri) { 362 if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) { 363 return MetricConsts.ROOT_NONE; 364 } 365 switch (uri.getAuthority()) { 366 case Providers.AUTHORITY_MEDIA: 367 String rootId = getRootIdSafely(uri); 368 if (rootId == null) { 369 return MetricConsts.ROOT_NONE; 370 } 371 switch (rootId) { 372 case Providers.ROOT_ID_AUDIO: 373 return MetricConsts.ROOT_AUDIO; 374 case Providers.ROOT_ID_IMAGES: 375 return MetricConsts.ROOT_IMAGES; 376 case Providers.ROOT_ID_VIDEOS: 377 return MetricConsts.ROOT_VIDEOS; 378 default: 379 return MetricConsts.ROOT_OTHER_DOCS_PROVIDER; 380 } 381 case Providers.AUTHORITY_STORAGE: 382 rootId = getRootIdSafely(uri); 383 if (rootId == null) { 384 return MetricConsts.ROOT_NONE; 385 } 386 if (Providers.ROOT_ID_HOME.equals(rootId)) { 387 return MetricConsts.ROOT_HOME; 388 } else { 389 return MetricConsts.ROOT_DEVICE_STORAGE; 390 } 391 case Providers.AUTHORITY_DOWNLOADS: 392 return MetricConsts.ROOT_DOWNLOADS; 393 case Providers.AUTHORITY_MTP: 394 return MetricConsts.ROOT_MTP; 395 default: 396 return MetricConsts.ROOT_OTHER_DOCS_PROVIDER; 397 } 398 } 399 400 /** @see #sanitizeRoot(Uri) */ sanitizeRoot(RootInfo root)401 public static @MetricConsts.Root int sanitizeRoot(RootInfo root) { 402 if (root.isRecents()) { 403 // Recents root is special and only identifiable via this method call. Other roots are 404 // identified by URI. 405 return MetricConsts.ROOT_RECENTS; 406 } else { 407 return sanitizeRoot(root.getUri()); 408 } 409 } 410 411 /** @see #sanitizeRoot(Uri) */ sanitizeRoot(ResolveInfo info)412 public static @MetricConsts.Root int sanitizeRoot(ResolveInfo info) { 413 // Log all apps under a single bucket in the roots histogram. 414 return MetricConsts.ROOT_THIRD_PARTY_APP; 415 } 416 417 /** 418 * Generates an int identifying a mime type. For privacy, this function only recognizes a small 419 * set of hard-coded types. For any other type, this function returns "other". 420 * 421 * @param mimeType 422 * @return 423 */ sanitizeMime(String mimeType)424 public static @MetricConsts.Mime int sanitizeMime(String mimeType) { 425 if (mimeType == null) { 426 return MetricConsts.MIME_NONE; 427 } else if ("*/*".equals(mimeType)) { 428 return MetricConsts.MIME_ANY; 429 } else { 430 String type = mimeType.substring(0, mimeType.indexOf('/')); 431 switch (type) { 432 case "application": 433 return MetricConsts.MIME_APPLICATION; 434 case "audio": 435 return MetricConsts.MIME_AUDIO; 436 case "image": 437 return MetricConsts.MIME_IMAGE; 438 case "message": 439 return MetricConsts.MIME_MESSAGE; 440 case "multipart": 441 return MetricConsts.MIME_MULTIPART; 442 case "text": 443 return MetricConsts.MIME_TEXT; 444 case "video": 445 return MetricConsts.MIME_VIDEO; 446 } 447 } 448 // Bucket all other types into one bucket. 449 return MetricConsts.MIME_OTHER; 450 } 451 isSystemProvider(String authority)452 private static boolean isSystemProvider(String authority) { 453 switch (authority) { 454 case Providers.AUTHORITY_MEDIA: 455 case Providers.AUTHORITY_STORAGE: 456 case Providers.AUTHORITY_DOWNLOADS: 457 return true; 458 default: 459 return false; 460 } 461 } 462 463 /** 464 * @param operation 465 * @param providerType 466 * @return An opcode, suitable for use as histogram bucket, for the given operation/provider 467 * combination. 468 */ getOpCode( @pType int operation, @MetricConsts.Provider int providerType)469 private static @MetricConsts.FileOp int getOpCode( 470 @OpType int operation, @MetricConsts.Provider int providerType) { 471 switch (operation) { 472 case FileOperationService.OPERATION_COPY: 473 switch (providerType) { 474 case MetricConsts.PROVIDER_INTRA: 475 return MetricConsts.FILEOP_COPY_INTRA_PROVIDER; 476 case MetricConsts.PROVIDER_SYSTEM: 477 return MetricConsts.FILEOP_COPY_SYSTEM_PROVIDER; 478 case MetricConsts.PROVIDER_EXTERNAL: 479 return MetricConsts.FILEOP_COPY_EXTERNAL_PROVIDER; 480 } 481 case FileOperationService.OPERATION_COMPRESS: 482 switch (providerType) { 483 case MetricConsts.PROVIDER_INTRA: 484 return MetricConsts.FILEOP_COMPRESS_INTRA_PROVIDER; 485 case MetricConsts.PROVIDER_SYSTEM: 486 return MetricConsts.FILEOP_COMPRESS_SYSTEM_PROVIDER; 487 case MetricConsts.PROVIDER_EXTERNAL: 488 return MetricConsts.FILEOP_COMPRESS_EXTERNAL_PROVIDER; 489 } 490 case FileOperationService.OPERATION_EXTRACT: 491 switch (providerType) { 492 case MetricConsts.PROVIDER_INTRA: 493 return MetricConsts.FILEOP_EXTRACT_INTRA_PROVIDER; 494 case MetricConsts.PROVIDER_SYSTEM: 495 return MetricConsts.FILEOP_EXTRACT_SYSTEM_PROVIDER; 496 case MetricConsts.PROVIDER_EXTERNAL: 497 return MetricConsts.FILEOP_EXTRACT_EXTERNAL_PROVIDER; 498 } 499 case FileOperationService.OPERATION_MOVE: 500 switch (providerType) { 501 case MetricConsts.PROVIDER_INTRA: 502 return MetricConsts.FILEOP_MOVE_INTRA_PROVIDER; 503 case MetricConsts.PROVIDER_SYSTEM: 504 return MetricConsts.FILEOP_MOVE_SYSTEM_PROVIDER; 505 case MetricConsts.PROVIDER_EXTERNAL: 506 return MetricConsts.FILEOP_MOVE_EXTERNAL_PROVIDER; 507 } 508 case FileOperationService.OPERATION_DELETE: 509 return MetricConsts.FILEOP_DELETE; 510 default: 511 Log.w(TAG, "Unrecognized operation type when logging a file operation"); 512 return MetricConsts.FILEOP_OTHER; 513 } 514 } 515 516 /** 517 * Maps FileOperationService OpType values, to MetricsOpType values. 518 */ toMetricsOpType(@pType int operation)519 private static @MetricConsts.FileOp int toMetricsOpType(@OpType int operation) { 520 switch (operation) { 521 case FileOperationService.OPERATION_COPY: 522 return MetricConsts.FILEOP_COPY; 523 case FileOperationService.OPERATION_MOVE: 524 return MetricConsts.FILEOP_MOVE; 525 case FileOperationService.OPERATION_DELETE: 526 return MetricConsts.FILEOP_DELETE; 527 case FileOperationService.OPERATION_UNKNOWN: 528 default: 529 return MetricConsts.FILEOP_UNKNOWN; 530 } 531 } 532 toMetricsAction(int action)533 private static @MetricConsts.MetricsAction int toMetricsAction(int action) { 534 switch(action) { 535 case State.ACTION_OPEN: 536 return MetricConsts.ACTION_OPEN; 537 case State.ACTION_CREATE: 538 return MetricConsts.ACTION_CREATE; 539 case State.ACTION_GET_CONTENT: 540 return MetricConsts.ACTION_GET_CONTENT; 541 case State.ACTION_OPEN_TREE: 542 return MetricConsts.ACTION_OPEN_TREE; 543 case State.ACTION_BROWSE: 544 return MetricConsts.ACTION_BROWSE; 545 case State.ACTION_PICK_COPY_DESTINATION: 546 return MetricConsts.ACTION_PICK_COPY_DESTINATION; 547 default: 548 return MetricConsts.ACTION_OTHER; 549 } 550 } 551 getSearchMode(boolean isKeyword, boolean isChip)552 private static int getSearchMode(boolean isKeyword, boolean isChip) { 553 if (isKeyword && isChip) { 554 return MetricConsts.SEARCH_KEYWORD_N_CHIPS; 555 } else if (isKeyword) { 556 return MetricConsts.SEARCH_KEYWORD; 557 } else if (isChip) { 558 return MetricConsts.SEARCH_CHIPS; 559 } else { 560 return MetricConsts.SEARCH_UNKNOWN; 561 } 562 } 563 564 /** 565 * Count the given src documents and provide a tally of how many come from the same provider as 566 * the dst document (if a dst is provided), how many come from system providers, and how many 567 * come from external 3rd-party providers. 568 */ countProviders( ProviderCounts counts, List<DocumentInfo> srcs, @Nullable DocumentInfo dst)569 private static void countProviders( 570 ProviderCounts counts, List<DocumentInfo> srcs, @Nullable DocumentInfo dst) { 571 for (DocumentInfo doc: srcs) { 572 countForAuthority(counts, doc.authority, dst); 573 } 574 } 575 576 /** 577 * Count the given uris and provide a tally of how many come from the same provider as 578 * the dst document (if a dst is provided), how many come from system providers, and how many 579 * come from external 3rd-party providers. 580 */ countProviders(ProviderCounts counts, List<Uri> uris)581 private static void countProviders(ProviderCounts counts, List<Uri> uris) { 582 for (Uri uri: uris) { 583 countForAuthority(counts, uri.getAuthority(), null); 584 } 585 } 586 countForAuthority( ProviderCounts counts, String authority, @Nullable DocumentInfo dst)587 private static void countForAuthority( 588 ProviderCounts counts, String authority, @Nullable DocumentInfo dst) { 589 if (dst != null && authority.equals(dst.authority)) { 590 counts.intraProvider++; 591 } else if (isSystemProvider(authority)){ 592 counts.systemProvider++; 593 } else { 594 counts.externalProvider++; 595 } 596 } 597 598 private static class ProviderCounts { 599 int intraProvider; 600 int systemProvider; 601 int externalProvider; 602 } 603 getRootIdSafely(Uri uri)604 private static String getRootIdSafely(Uri uri) { 605 try { 606 return DocumentsContract.getRootId(uri); 607 } catch (IllegalArgumentException iae) { 608 Log.w(TAG, "Invalid root Uri " + uri.toSafeString()); 609 } 610 return null; 611 } 612 } 613