blob: 172282a9b3f916b91b9cc944f98b1cdc763b10f8 [file] [log] [blame]
Ben Kwa1c0a3892016-01-26 11:50:03 -08001/*
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
17package com.android.documentsui;
18
19import static com.android.documentsui.Shared.DEBUG;
Ben Kwafaa27202016-01-28 16:39:57 -080020import static com.android.internal.util.Preconditions.checkArgument;
Ben Kwa1c0a3892016-01-26 11:50:03 -080021
Ben Kwafaa27202016-01-28 16:39:57 -080022import android.annotation.IntDef;
23import android.annotation.Nullable;
Ben Kwa1c0a3892016-01-26 11:50:03 -080024import android.content.Context;
25import android.content.Intent;
26import android.content.pm.ResolveInfo;
27import android.net.Uri;
28import android.provider.DocumentsContract;
29import android.util.Log;
30
Ben Kwafaa27202016-01-28 16:39:57 -080031import com.android.documentsui.model.DocumentInfo;
Ben Kwa1c0a3892016-01-26 11:50:03 -080032import com.android.documentsui.model.RootInfo;
Ben Kwafaa27202016-01-28 16:39:57 -080033import com.android.documentsui.services.FileOperationService;
34import com.android.documentsui.services.FileOperationService.OpType;
Ben Kwa1c0a3892016-01-26 11:50:03 -080035import com.android.internal.logging.MetricsLogger;
36
Ben Kwafaa27202016-01-28 16:39:57 -080037import java.lang.annotation.Retention;
38import java.lang.annotation.RetentionPolicy;
39import java.util.List;
40
Ben Kwa1c0a3892016-01-26 11:50:03 -080041/** @hide */
42public final class Metrics {
43 private static final String TAG = "Metrics";
44
45 // These are the native provider authorities that the metrics code is capable of recognizing and
46 // explicitly counting.
47 private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
48 private static final String AUTHORITY_STORAGE = "com.android.externalstorage.documents";
49 private static final String AUTHORITY_DOWNLOADS = "com.android.providers.downloads.documents";
Ben Kwaebaaea42016-01-28 18:15:07 -080050 private static final String AUTHORITY_MTP = "com.android.mtp.documents";
Ben Kwa1c0a3892016-01-26 11:50:03 -080051
52 // These strings have to be whitelisted in tron. Do not change them.
53 private static final String COUNT_LAUNCH_ACTION = "docsui_launch_action";
54 private static final String COUNT_ROOT_VISITED = "docsui_root_visited";
55 private static final String COUNT_OPEN_MIME = "docsui_open_mime";
56 private static final String COUNT_CREATE_MIME = "docsui_create_mime";
57 private static final String COUNT_GET_CONTENT_MIME = "docsui_get_content_mime";
58 private static final String COUNT_BROWSE_ROOT = "docsui_browse_root";
59 private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
60 private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
Ben Kwafaa27202016-01-28 16:39:57 -080061 private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system";
62 private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
63 private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
Ben Kwa1c0a3892016-01-26 11:50:03 -080064
65 // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
66 // root that is not explicitly recognized by the Metrics code (see {@link
67 // #getSanitizedRootIndex}). Apps are also bucketed in this histogram using negative indices
68 // (see below).
Ben Kwaebaaea42016-01-28 18:15:07 -080069 // Do not change or rearrange these values, that will break historical data. Only add to the end
70 // of the list.
Ben Kwa1c0a3892016-01-26 11:50:03 -080071 private static final int ROOT_NONE = 0;
72 private static final int ROOT_OTHER = 1;
73 private static final int ROOT_AUDIO = 2;
74 private static final int ROOT_DEVICE_STORAGE = 3;
75 private static final int ROOT_DOWNLOADS = 4;
76 private static final int ROOT_HOME = 5;
77 private static final int ROOT_IMAGES = 6;
78 private static final int ROOT_RECENTS = 7;
79 private static final int ROOT_VIDEOS = 8;
Ben Kwaebaaea42016-01-28 18:15:07 -080080 private static final int ROOT_MTP = 9;
Ben Kwa1c0a3892016-01-26 11:50:03 -080081 // Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they
82 // are logged analogously to roots. Use negative numbers to identify apps.
83 private static final int ROOT_THIRD_PARTY_APP = -1;
84
Ben Kwafaa27202016-01-28 16:39:57 -080085 @IntDef(flag = true, value = {
86 ROOT_NONE,
87 ROOT_OTHER,
88 ROOT_AUDIO,
89 ROOT_DEVICE_STORAGE,
90 ROOT_DOWNLOADS,
91 ROOT_HOME,
92 ROOT_IMAGES,
93 ROOT_RECENTS,
94 ROOT_VIDEOS,
Ben Kwaebaaea42016-01-28 18:15:07 -080095 ROOT_MTP,
Ben Kwafaa27202016-01-28 16:39:57 -080096 ROOT_THIRD_PARTY_APP
97 })
98 @Retention(RetentionPolicy.SOURCE)
99 public @interface Root {}
100
Ben Kwa1c0a3892016-01-26 11:50:03 -0800101 // Indices for bucketing mime types.
102 private static final int MIME_OTHER = -2; // anything not enumerated below
103 private static final int MIME_NONE = -1; // null mime
104 private static final int MIME_ANY = 0; // */*
105 private static final int MIME_APPLICATION = 1; // application/*
106 private static final int MIME_AUDIO = 2; // audio/*
107 private static final int MIME_IMAGE = 3; // image/*
108 private static final int MIME_MESSAGE = 4; // message/*
109 private static final int MIME_MULTIPART = 5; // multipart/*
110 private static final int MIME_TEXT = 6; // text/*
111 private static final int MIME_VIDEO = 7; // video/*
112
Ben Kwafaa27202016-01-28 16:39:57 -0800113 @IntDef(flag = true, value = {
114 MIME_OTHER,
115 MIME_NONE,
116 MIME_ANY,
117 MIME_APPLICATION,
118 MIME_AUDIO,
119 MIME_IMAGE,
120 MIME_MESSAGE,
121 MIME_MULTIPART,
122 MIME_TEXT,
123 MIME_VIDEO
124 })
125 @Retention(RetentionPolicy.SOURCE)
126 public @interface Mime {}
127
128 // Codes representing different kinds of file operations. These are used for bucketing
129 // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms.
130 private static final int FILEOP_OTHER = 0; // any file operation not listed below
131 private static final int FILEOP_COPY_INTRA_PROVIDER = 1; // Copy within a provider
132 private static final int FILEOP_COPY_SYSTEM_PROVIDER = 2; // Copy to a system provider.
133 private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 3; // Copy to a 3rd-party provider.
134 private static final int FILEOP_MOVE_INTRA_PROVIDER = 4; // Move within a provider.
135 private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 5; // Move to a system provider.
136 private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 6; // Move to a 3rd-party provider.
137 private static final int FILEOP_DELETE = 7;
138 private static final int FILEOP_OTHER_ERROR = -1;
139 private static final int FILEOP_COPY_ERROR = -2;
140 private static final int FILEOP_MOVE_ERROR = -3;
141 private static final int FILEOP_DELETE_ERROR = -4;
142
143 @IntDef(flag = true, value = {
144 FILEOP_OTHER,
145 FILEOP_COPY_INTRA_PROVIDER,
146 FILEOP_COPY_SYSTEM_PROVIDER,
147 FILEOP_COPY_EXTERNAL_PROVIDER,
148 FILEOP_MOVE_INTRA_PROVIDER,
149 FILEOP_MOVE_SYSTEM_PROVIDER,
150 FILEOP_MOVE_EXTERNAL_PROVIDER,
151 FILEOP_DELETE,
152 FILEOP_OTHER_ERROR,
153 FILEOP_COPY_ERROR,
154 FILEOP_MOVE_ERROR,
155 FILEOP_DELETE_ERROR
156 })
157 @Retention(RetentionPolicy.SOURCE)
158 public @interface FileOp {}
159
160 // Codes representing different provider types. Used for sorting file operations when logging.
161 private static final int PROVIDER_INTRA = 0;
162 private static final int PROVIDER_SYSTEM = 1;
163 private static final int PROVIDER_EXTERNAL = 2;
164
165 @IntDef(flag = true, value = {
166 PROVIDER_INTRA,
167 PROVIDER_SYSTEM,
168 PROVIDER_EXTERNAL
169 })
170 @Retention(RetentionPolicy.SOURCE)
171 public @interface Provider {}
172
Ben Kwa1c0a3892016-01-26 11:50:03 -0800173 /**
174 * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
175 *
176 * @param context
177 * @param state
178 * @param intent
179 */
180 public static void logActivityLaunch(Context context, State state, Intent intent) {
181 // Log the launch action.
182 logHistogram(context, COUNT_LAUNCH_ACTION, state.action);
183 // Then log auxiliary data (roots/mime types) associated with some actions.
184 Uri uri = intent.getData();
185 switch (state.action) {
186 case State.ACTION_OPEN:
187 logHistogram(context, COUNT_OPEN_MIME, sanitizeMime(intent.getType()));
188 break;
189 case State.ACTION_CREATE:
190 logHistogram(context, COUNT_CREATE_MIME, sanitizeMime(intent.getType()));
191 break;
192 case State.ACTION_GET_CONTENT:
193 logHistogram(context, COUNT_GET_CONTENT_MIME, sanitizeMime(intent.getType()));
194 break;
195 case State.ACTION_MANAGE:
196 logHistogram(context, COUNT_MANAGE_ROOT, sanitizeRoot(uri));
197 break;
198 case State.ACTION_BROWSE:
199 logHistogram(context, COUNT_BROWSE_ROOT, sanitizeRoot(uri));
200 break;
201 default:
202 break;
203 }
204 }
205
206 /**
207 * Logs a root visited event. Call this when the user clicks on a root in the RootsFragment.
208 *
209 * @param context
210 * @param info
211 */
212 public static void logRootVisited(Context context, RootInfo info) {
213 logHistogram(context, COUNT_ROOT_VISITED, sanitizeRoot(info));
214 }
215
216 /**
217 * Logs an app visited event. Call this when the user clicks on an app in the RootsFragment.
218 *
219 * @param context
220 * @param info
221 */
222 public static void logAppVisited(Context context, ResolveInfo info) {
223 logHistogram(context, COUNT_ROOT_VISITED, sanitizeRoot(info));
224 }
225
226 /**
227 * Logs a multi-window start. Call this when the user spawns a new DocumentsUI window.
228 *
229 * @param context
230 */
231 public static void logMultiWindow(Context context) {
232 logCount(context, COUNT_MULTI_WINDOW);
233 }
234
235 /**
Ben Kwafaa27202016-01-28 16:39:57 -0800236 * Logs file operation stats. Call this when a file operation has completed. The given
237 * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
238 * provider to another vs copying within a given provider). No PII is logged.
239 *
240 * @param context
241 * @param operationType
242 * @param srcs
243 * @param dst
244 */
245 public static void logFileOperation(
246 Context context,
247 @OpType int operationType,
248 List<DocumentInfo> srcs,
249 @Nullable DocumentInfo dst) {
250 ProviderCounts counts = countProviders(srcs, dst);
251
252 if (counts.intraProvider > 0) {
253 logIntraProviderFileOps(context, dst.authority, operationType);
254 }
255 if (counts.systemProvider > 0) {
256 // Log file operations on system providers.
257 logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType);
258 }
259 if (counts.externalProvider > 0) {
260 // Log file operations on external providers.
261 logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType);
262 }
263 }
264
265 /**
266 * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete)
267 * fails.
268 *
269 * @param context
270 * @param operationType
271 * @param failedFiles
272 */
273 public static void logFileOperationErrors(Context context, @OpType int operationType,
274 List<DocumentInfo> failedFiles) {
275 ProviderCounts counts = countProviders(failedFiles, null);
276
277 @FileOp int opCode = FILEOP_OTHER_ERROR;
278 switch (operationType) {
279 case FileOperationService.OPERATION_COPY:
280 opCode = FILEOP_COPY_ERROR;
281 break;
282 case FileOperationService.OPERATION_DELETE:
283 opCode = FILEOP_DELETE_ERROR;
284 break;
285 case FileOperationService.OPERATION_MOVE:
286 opCode = FILEOP_MOVE_ERROR;
287 break;
288 }
289 if (counts.systemProvider > 0) {
290 logHistogram(context, COUNT_FILEOP_SYSTEM, opCode);
291 }
292 if (counts.externalProvider > 0) {
293 logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode);
294 }
295 }
296
297 /**
298 * Log the cancellation of a file operation. Call this when a Job is canceled.
299 * @param context
300 * @param operationType
301 */
302 public static void logFileOperationCancelled(Context context, @OpType int operationType) {
303 logHistogram(context, COUNT_FILEOP_CANCELED, operationType);
304 }
305
306 private static void logInterProviderFileOps(
307 Context context,
308 String histogram,
309 DocumentInfo dst,
310 @OpType int operationType) {
311 if (operationType == FileOperationService.OPERATION_DELETE) {
312 logHistogram(context, histogram, FILEOP_DELETE);
313 } else {
314 checkArgument(dst != null);
315 @Provider int providerType =
316 isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL;
317 logHistogram(context, histogram, getOpCode(operationType, providerType));
318 }
319 }
320
321 private static void logIntraProviderFileOps(
322 Context context, String authority, @OpType int operationType) {
323 // Find the right histogram to log to, then log the operation.
324 String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL;
325 logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA));
326 }
327
328 /**
Ben Kwa1c0a3892016-01-26 11:50:03 -0800329 * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
330 *
331 * @param context
332 * @param name The counter to increment.
333 */
334 private static void logCount(Context context, String name) {
335 if (DEBUG) Log.d(TAG, name + ": " + 1);
336 MetricsLogger.count(context, name, 1);
337 }
338
339 /**
340 * Internal method for making a MetricsLogger.histogram call.
341 *
342 * @param context
343 * @param name The name of the histogram.
344 * @param bucket The bucket to increment.
345 */
346 private static void logHistogram(Context context, String name, int bucket) {
347 if (DEBUG) Log.d(TAG, name + ": " + bucket);
348 MetricsLogger.histogram(context, name, bucket);
349 }
350
351 /**
352 * Generates an integer identifying the given root. For privacy, this function only recognizes a
353 * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
354 * a single ROOT_OTHER bucket.
355 */
Ben Kwafaa27202016-01-28 16:39:57 -0800356 private static @Root int sanitizeRoot(Uri uri) {
Tomasz Mikolajewski63e2aae2016-02-01 12:01:14 +0900357 if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800358 return ROOT_NONE;
359 }
360
361 switch (uri.getAuthority()) {
362 case AUTHORITY_MEDIA:
363 switch (DocumentsContract.getRootId(uri)) {
364 case "audio_root":
365 return ROOT_AUDIO;
366 case "images_root":
367 return ROOT_IMAGES;
368 case "videos_root":
369 return ROOT_VIDEOS;
370 default:
371 return ROOT_OTHER;
372 }
373 case AUTHORITY_STORAGE:
374 if ("home".equals(DocumentsContract.getRootId(uri))) {
375 return ROOT_HOME;
376 } else {
377 return ROOT_DEVICE_STORAGE;
378 }
379 case AUTHORITY_DOWNLOADS:
380 return ROOT_DOWNLOADS;
Ben Kwaebaaea42016-01-28 18:15:07 -0800381 case AUTHORITY_MTP:
382 return ROOT_MTP;
Ben Kwa1c0a3892016-01-26 11:50:03 -0800383 default:
384 return ROOT_OTHER;
385 }
386 }
387
388 /** @see #sanitizeRoot(Uri) */
Ben Kwafaa27202016-01-28 16:39:57 -0800389 private static @Root int sanitizeRoot(RootInfo root) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800390 if (root.isRecents()) {
391 // Recents root is special and only identifiable via this method call. Other roots are
392 // identified by URI.
393 return ROOT_RECENTS;
394 } else {
395 return sanitizeRoot(root.getUri());
396 }
397 }
398
399 /** @see #sanitizeRoot(Uri) */
Ben Kwafaa27202016-01-28 16:39:57 -0800400 private static @Root int sanitizeRoot(ResolveInfo info) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800401 // Log all apps under a single bucket in the roots histogram.
402 return ROOT_THIRD_PARTY_APP;
403 }
404
405 /**
406 * Generates an int identifying a mime type. For privacy, this function only recognizes a small
407 * set of hard-coded types. For any other type, this function returns "other".
408 *
409 * @param mimeType
410 * @return
411 */
Ben Kwafaa27202016-01-28 16:39:57 -0800412 private static @Mime int sanitizeMime(String mimeType) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800413 if (mimeType == null) {
414 return MIME_NONE;
415 } else if ("*/*".equals(mimeType)) {
416 return MIME_ANY;
417 } else {
418 String type = mimeType.substring(0, mimeType.indexOf('/'));
419 switch (type) {
420 case "application":
421 return MIME_APPLICATION;
422 case "audio":
423 return MIME_AUDIO;
424 case "image":
425 return MIME_IMAGE;
426 case "message":
427 return MIME_MESSAGE;
428 case "multipart":
429 return MIME_MULTIPART;
430 case "text":
431 return MIME_TEXT;
432 case "video":
433 return MIME_VIDEO;
434 }
435 }
436 // Bucket all other types into one bucket.
437 return MIME_OTHER;
438 }
Ben Kwafaa27202016-01-28 16:39:57 -0800439
440 private static boolean isSystemProvider(String authority) {
441 switch (authority) {
442 case AUTHORITY_MEDIA:
443 case AUTHORITY_STORAGE:
444 case AUTHORITY_DOWNLOADS:
445 return true;
446 default:
447 return false;
448 }
449 }
450
451 /**
452 * @param operation
453 * @param providerType
454 * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
455 * combination.
456 */
457 private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
458 switch (operation) {
459 case FileOperationService.OPERATION_COPY:
460 switch (providerType) {
461 case PROVIDER_INTRA:
462 return FILEOP_COPY_INTRA_PROVIDER;
463 case PROVIDER_SYSTEM:
464 return FILEOP_COPY_SYSTEM_PROVIDER;
465 case PROVIDER_EXTERNAL:
466 return FILEOP_COPY_EXTERNAL_PROVIDER;
467 }
468 case FileOperationService.OPERATION_MOVE:
469 switch (providerType) {
470 case PROVIDER_INTRA:
471 return FILEOP_MOVE_INTRA_PROVIDER;
472 case PROVIDER_SYSTEM:
473 return FILEOP_MOVE_SYSTEM_PROVIDER;
474 case PROVIDER_EXTERNAL:
475 return FILEOP_MOVE_EXTERNAL_PROVIDER;
476 }
477 case FileOperationService.OPERATION_DELETE:
478 return FILEOP_DELETE;
479 default:
480 Log.w(TAG, "Unrecognized operation type when logging a file operation");
481 return FILEOP_OTHER;
482 }
483 }
484
485 /**
486 * Count the given src documents and provide a tally of how many come from the same provider as
487 * the dst document (if a dst is provided), how many come from system providers, and how many
488 * come from external 3rd-party providers.
489 */
490 private static ProviderCounts countProviders(
491 List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
492 ProviderCounts counts = new ProviderCounts();
493 for (DocumentInfo doc: srcs) {
494 if (dst != null && doc.authority.equals(dst.authority)) {
495 counts.intraProvider++;
496 } else if (isSystemProvider(doc.authority)){
497 counts.systemProvider++;
498 } else {
499 counts.externalProvider++;
500 }
501 }
502 return counts;
503 }
504
505 private static class ProviderCounts {
506 int intraProvider;
507 int systemProvider;
508 int externalProvider;
509 }
Ben Kwa1c0a3892016-01-26 11:50:03 -0800510}