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