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