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