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 
323     @IntDef(flag = false, value = {
324             USER_ACTION_OTHER,
325             USER_ACTION_GRID,
326             USER_ACTION_LIST,
327             USER_ACTION_SORT_NAME,
328             USER_ACTION_SORT_DATE,
329             USER_ACTION_SORT_SIZE,
330             USER_ACTION_SEARCH,
331             USER_ACTION_SHOW_SIZE,
332             USER_ACTION_HIDE_SIZE,
333             USER_ACTION_SETTINGS,
334             USER_ACTION_COPY_TO,
335             USER_ACTION_MOVE_TO,
336             USER_ACTION_DELETE,
337             USER_ACTION_RENAME,
338             USER_ACTION_CREATE_DIR,
339             USER_ACTION_SELECT_ALL,
340             USER_ACTION_SHARE,
341             USER_ACTION_OPEN,
342             USER_ACTION_SHOW_ADVANCED,
343             USER_ACTION_HIDE_ADVANCED,
344             USER_ACTION_NEW_WINDOW,
345             USER_ACTION_PASTE_CLIPBOARD,
346             USER_ACTION_COPY_CLIPBOARD,
347             USER_ACTION_DRAG_N_DROP,
348             USER_ACTION_DRAG_N_DROP_MULTI_WINDOW,
349             USER_ACTION_CUT_CLIPBOARD,
350             USER_ACTION_COMPRESS,
351             USER_ACTION_EXTRACT_TO,
352             USER_ACTION_VIEW_IN_APPLICATION
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      */
logActivityLaunch(Context context, State state, Intent intent)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      */
logLaunchAtLocation(Context context, State state, @Nullable Uri rootUri)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      */
logRootVisited( Context context, @ContextScope int scope, RootInfo info)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      */
logAppVisited(Context context, ResolveInfo info)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      */
logFileOperation( Context context, @OpType int operationType, List<DocumentInfo> srcs, @Nullable DocumentInfo dst)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 
logFileOperated( Context context, @OpType int operationType, @FileOpMode int approach)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      */
logCreateDirOperation(Context context)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      */
logRenameFileOperation(Context context)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      */
logFileOperationErrors(Context context, @OpType int operationType, List<DocumentInfo> failedFiles, List<Uri> failedUris)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 
logFileOperationFailure( Context context, @SubFileOp int subFileOp, Uri docUri)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      */
logCreateDirError(Context context)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      */
logRenameFileError(Context context)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      */
logFileOperationCancelled(Context context, @OpType int operationType)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      */
logStartupMs(Context context, int startupMs)664     public static void logStartupMs(Context context, int startupMs) {
665         logHistogram(context, COUNT_STARTUP_MS, startupMs);
666     }
667 
logInterProviderFileOps( Context context, String histogram, DocumentInfo dst, @OpType int operationType)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 
logIntraProviderFileOps( Context context, String authority, @OpType int operationType)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     // Types for logInvalidScopedAccessRequest
691     public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS =
692             "docsui_scoped_directory_access_invalid_args";
693     public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY =
694             "docsui_scoped_directory_access_invalid_dir";
695     public static final String SCOPED_DIRECTORY_ACCESS_ERROR =
696             "docsui_scoped_directory_access_error";
697 
698     @StringDef(value = {
699             SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS,
700             SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY,
701             SCOPED_DIRECTORY_ACCESS_ERROR
702     })
703     @Retention(RetentionPolicy.SOURCE)
704     public @interface InvalidScopedAccess{}
705 
logInvalidScopedAccessRequest(Context context, @InvalidScopedAccess String type)706     public static void logInvalidScopedAccessRequest(Context context,
707             @InvalidScopedAccess String type) {
708         switch (type) {
709             case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS:
710             case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY:
711             case SCOPED_DIRECTORY_ACCESS_ERROR:
712                 logCount(context, type);
713                 break;
714             default:
715                 Log.wtf(TAG, "invalid InvalidScopedAccess: " + type);
716         }
717     }
718 
719     // Types for logValidScopedAccessRequest
720     public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0;
721     public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1;
722     public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2;
723     public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3;
724     public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4;
725 
726     @IntDef(flag = true, value = {
727             SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED,
728             SCOPED_DIRECTORY_ACCESS_GRANTED,
729             SCOPED_DIRECTORY_ACCESS_DENIED,
730             SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST,
731             SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED
732     })
733     @Retention(RetentionPolicy.SOURCE)
734     public @interface ScopedAccessGrant {}
735 
logValidScopedAccessRequest(Activity activity, String directory, @ScopedAccessGrant int type)736     public static void logValidScopedAccessRequest(Activity activity, String directory,
737             @ScopedAccessGrant int type) {
738         int index = -1;
739         if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) {
740             index = -2;
741         } else {
742             for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) {
743                 if (STANDARD_DIRECTORIES[i].equals(directory)) {
744                     index = i;
745                     break;
746                 }
747             }
748         }
749         final String packageName = activity.getCallingPackage();
750         switch (type) {
751             case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED:
752                 MetricsLogger.action(activity, MetricsEvent
753                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName);
754                 MetricsLogger.action(activity, MetricsEvent
755                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index);
756                 break;
757             case SCOPED_DIRECTORY_ACCESS_GRANTED:
758                 MetricsLogger.action(activity, MetricsEvent
759                         .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName);
760                 MetricsLogger.action(activity, MetricsEvent
761                         .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index);
762                 break;
763             case SCOPED_DIRECTORY_ACCESS_DENIED:
764                 MetricsLogger.action(activity, MetricsEvent
765                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName);
766                 MetricsLogger.action(activity, MetricsEvent
767                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index);
768                 break;
769             case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST:
770                 MetricsLogger.action(activity, MetricsEvent
771                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName);
772                 MetricsLogger.action(activity, MetricsEvent
773                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index);
774                 break;
775             case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED:
776                 MetricsLogger.action(activity, MetricsEvent
777                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName);
778                 MetricsLogger.action(activity, MetricsEvent
779                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index);
780                 break;
781             default:
782                 Log.wtf(TAG, "invalid ScopedAccessGrant: " + type);
783         }
784     }
785 
786     /**
787      * Logs the action that was started by user.
788      * @param context
789      * @param userAction
790      */
logUserAction(Context context, @UserAction int userAction)791     public static void logUserAction(Context context, @UserAction int userAction) {
792         logHistogram(context, COUNT_USER_ACTION, userAction);
793     }
794 
logStorageFileOperationFailure( Context context, @SubFileOp int subFileOp, Uri docUri)795     private static void logStorageFileOperationFailure(
796             Context context, @SubFileOp int subFileOp, Uri docUri) {
797         assert(Providers.AUTHORITY_STORAGE.equals(docUri.getAuthority()));
798 
799         boolean isInternal;
800         try (ContentProviderClient client = acquireUnstableProviderOrThrow(
801                 context.getContentResolver(), Providers.AUTHORITY_STORAGE)) {
802             final Path path = DocumentsContract.findDocumentPath(client, docUri);
803 
804             final ProvidersAccess providers = DocumentsApplication.getProvidersCache(context);
805             final RootInfo root = providers.getRootOneshot(
806                     Providers.AUTHORITY_STORAGE, path.getRootId());
807             isInternal = !root.supportsEject();
808         } catch (RemoteException | RuntimeException e) {
809             Log.e(TAG, "Failed to obtain its root info. Log the metrics as internal.", e);
810             // It's not very likely to have an external storage so log it as internal.
811             isInternal = true;
812         }
813 
814         final String histogram = isInternal
815                 ? COUNT_INTERNAL_STORAGE_FILEOP_FAILURE
816                 : COUNT_EXTERNAL_STORAGE_FILEOP_FAILURE;
817         logHistogram(context, histogram, subFileOp);
818     }
819 
820     /**
821      * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
822      *
823      * @param context
824      * @param name The counter to increment.
825      */
logCount(Context context, String name)826     private static void logCount(Context context, String name) {
827         if (DEBUG) Log.d(TAG, name + ": " + 1);
828         MetricsLogger.count(context, name, 1);
829     }
830 
831     /**
832      * Internal method for making a MetricsLogger.histogram call.
833      *
834      * @param context
835      * @param name The name of the histogram.
836      * @param bucket The bucket to increment.
837      */
logHistogram(Context context, String name, @ActionType int bucket)838     private static void logHistogram(Context context, String name, @ActionType int bucket) {
839         if (DEBUG) Log.d(TAG, name + ": " + bucket);
840         MetricsLogger.histogram(context, name, bucket);
841     }
842 
843     /**
844      * Generates an integer identifying the given root. For privacy, this function only recognizes a
845      * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
846      * a single ROOT_OTHER bucket.
847      */
sanitizeRoot(Uri uri)848     private static @Root int sanitizeRoot(Uri uri) {
849         if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) {
850             return ROOT_NONE;
851         }
852 
853         switch (uri.getAuthority()) {
854             case Providers.AUTHORITY_MEDIA:
855                 switch (DocumentsContract.getRootId(uri)) {
856                     case Providers.ROOT_ID_AUDIO:
857                         return ROOT_AUDIO;
858                     case Providers.ROOT_ID_IMAGES:
859                         return ROOT_IMAGES;
860                     case Providers.ROOT_ID_VIDEOS:
861                         return ROOT_VIDEOS;
862                     default:
863                         return ROOT_OTHER;
864                 }
865             case Providers.AUTHORITY_STORAGE:
866                 if (Providers.ROOT_ID_HOME.equals(DocumentsContract.getRootId(uri))) {
867                     return ROOT_HOME;
868                 } else {
869                     return ROOT_DEVICE_STORAGE;
870                 }
871             case Providers.AUTHORITY_DOWNLOADS:
872                 return ROOT_DOWNLOADS;
873             case Providers.AUTHORITY_MTP:
874                 return ROOT_MTP;
875             default:
876                 return ROOT_OTHER;
877         }
878     }
879 
880     /** @see #sanitizeRoot(Uri) */
sanitizeRoot(RootInfo root)881     private static @Root int sanitizeRoot(RootInfo root) {
882         if (root.isRecents()) {
883             // Recents root is special and only identifiable via this method call. Other roots are
884             // identified by URI.
885             return ROOT_RECENTS;
886         } else {
887             return sanitizeRoot(root.getUri());
888         }
889     }
890 
891     /** @see #sanitizeRoot(Uri) */
sanitizeRoot(ResolveInfo info)892     private static @Root int sanitizeRoot(ResolveInfo info) {
893         // Log all apps under a single bucket in the roots histogram.
894         return ROOT_THIRD_PARTY_APP;
895     }
896 
897     /**
898      * Generates an int identifying a mime type. For privacy, this function only recognizes a small
899      * set of hard-coded types. For any other type, this function returns "other".
900      *
901      * @param mimeType
902      * @return
903      */
sanitizeMime(String mimeType)904     private static @Mime int sanitizeMime(String mimeType) {
905         if (mimeType == null) {
906             return MIME_NONE;
907         } else if ("*/*".equals(mimeType)) {
908             return MIME_ANY;
909         } else {
910             String type = mimeType.substring(0, mimeType.indexOf('/'));
911             switch (type) {
912                 case "application":
913                     return MIME_APPLICATION;
914                 case "audio":
915                     return MIME_AUDIO;
916                 case "image":
917                     return MIME_IMAGE;
918                 case "message":
919                     return MIME_MESSAGE;
920                 case "multipart":
921                     return MIME_MULTIPART;
922                 case "text":
923                     return MIME_TEXT;
924                 case "video":
925                     return MIME_VIDEO;
926             }
927         }
928         // Bucket all other types into one bucket.
929         return MIME_OTHER;
930     }
931 
isSystemProvider(String authority)932     private static boolean isSystemProvider(String authority) {
933         switch (authority) {
934             case Providers.AUTHORITY_MEDIA:
935             case Providers.AUTHORITY_STORAGE:
936             case Providers.AUTHORITY_DOWNLOADS:
937                 return true;
938             default:
939                 return false;
940         }
941     }
942 
943     /**
944      * @param operation
945      * @param providerType
946      * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
947      *         combination.
948      */
getOpCode(@pType int operation, @Provider int providerType)949     private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
950         switch (operation) {
951             case FileOperationService.OPERATION_COPY:
952                 switch (providerType) {
953                     case PROVIDER_INTRA:
954                         return FILEOP_COPY_INTRA_PROVIDER;
955                     case PROVIDER_SYSTEM:
956                         return FILEOP_COPY_SYSTEM_PROVIDER;
957                     case PROVIDER_EXTERNAL:
958                         return FILEOP_COPY_EXTERNAL_PROVIDER;
959                 }
960             case FileOperationService.OPERATION_COMPRESS:
961                 switch (providerType) {
962                     case PROVIDER_INTRA:
963                         return FILEOP_COMPRESS_INTRA_PROVIDER;
964                     case PROVIDER_SYSTEM:
965                         return FILEOP_COMPRESS_SYSTEM_PROVIDER;
966                     case PROVIDER_EXTERNAL:
967                         return FILEOP_COMPRESS_EXTERNAL_PROVIDER;
968                 }
969              case FileOperationService.OPERATION_EXTRACT:
970                 switch (providerType) {
971                     case PROVIDER_INTRA:
972                         return FILEOP_EXTRACT_INTRA_PROVIDER;
973                     case PROVIDER_SYSTEM:
974                         return FILEOP_EXTRACT_SYSTEM_PROVIDER;
975                     case PROVIDER_EXTERNAL:
976                         return FILEOP_EXTRACT_EXTERNAL_PROVIDER;
977                 }
978             case FileOperationService.OPERATION_MOVE:
979                 switch (providerType) {
980                     case PROVIDER_INTRA:
981                         return FILEOP_MOVE_INTRA_PROVIDER;
982                     case PROVIDER_SYSTEM:
983                         return FILEOP_MOVE_SYSTEM_PROVIDER;
984                     case PROVIDER_EXTERNAL:
985                         return FILEOP_MOVE_EXTERNAL_PROVIDER;
986                 }
987             case FileOperationService.OPERATION_DELETE:
988                 return FILEOP_DELETE;
989             default:
990                 Log.w(TAG, "Unrecognized operation type when logging a file operation");
991                 return FILEOP_OTHER;
992         }
993     }
994 
995     /**
996      * Maps FileOperationService OpType values, to MetricsOpType values.
997      */
toMetricsOpType(@pType int operation)998     private static @MetricsOpType int toMetricsOpType(@OpType int operation) {
999         switch (operation) {
1000             case FileOperationService.OPERATION_COPY:
1001                 return OPERATION_COPY;
1002             case FileOperationService.OPERATION_MOVE:
1003                 return OPERATION_MOVE;
1004             case FileOperationService.OPERATION_DELETE:
1005                 return OPERATION_DELETE;
1006             case FileOperationService.OPERATION_UNKNOWN:
1007             default:
1008                 return OPERATION_UNKNOWN;
1009         }
1010     }
1011 
toMetricsAction(int action)1012     private static @MetricsAction int toMetricsAction(int action) {
1013         switch(action) {
1014             case State.ACTION_OPEN:
1015                 return ACTION_OPEN;
1016             case State.ACTION_CREATE:
1017                 return ACTION_CREATE;
1018             case State.ACTION_GET_CONTENT:
1019                 return ACTION_GET_CONTENT;
1020             case State.ACTION_OPEN_TREE:
1021                 return ACTION_OPEN_TREE;
1022             case State.ACTION_BROWSE:
1023                 return ACTION_BROWSE;
1024             case State.ACTION_PICK_COPY_DESTINATION:
1025                 return ACTION_PICK_COPY_DESTINATION;
1026             default:
1027                 return ACTION_OTHER;
1028         }
1029     }
1030 
1031     /**
1032      * Count the given src documents and provide a tally of how many come from the same provider as
1033      * the dst document (if a dst is provided), how many come from system providers, and how many
1034      * come from external 3rd-party providers.
1035      */
countProviders( ProviderCounts counts, List<DocumentInfo> srcs, @Nullable DocumentInfo dst)1036     private static void countProviders(
1037             ProviderCounts counts, List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
1038         for (DocumentInfo doc: srcs) {
1039             countForAuthority(counts, doc.authority, dst);
1040         }
1041     }
1042 
1043     /**
1044      * Count the given uris and provide a tally of how many come from the same provider as
1045      * the dst document (if a dst is provided), how many come from system providers, and how many
1046      * come from external 3rd-party providers.
1047      */
countProviders(ProviderCounts counts, List<Uri> uris)1048     private static void countProviders(ProviderCounts counts, List<Uri> uris) {
1049         for (Uri uri: uris) {
1050             countForAuthority(counts, uri.getAuthority(), null);
1051         }
1052     }
1053 
countForAuthority( ProviderCounts counts, String authority, @Nullable DocumentInfo dst)1054     private static void countForAuthority(
1055             ProviderCounts counts, String authority, @Nullable DocumentInfo dst) {
1056         if (dst != null && authority.equals(dst.authority)) {
1057             counts.intraProvider++;
1058         } else if (isSystemProvider(authority)){
1059             counts.systemProvider++;
1060         } else {
1061             counts.externalProvider++;
1062         }
1063     }
1064 
1065     private static class ProviderCounts {
1066         int intraProvider;
1067         int systemProvider;
1068         int externalProvider;
1069     }
1070 }
1071