blob: 69a6e1f31de41dd341ad0683b2bf0f9dc6c92fe4 [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
Felipe Lemee4b60122016-02-24 10:17:41 -080019import static android.os.Environment.STANDARD_DIRECTORIES;
Ben Kwa1c0a3892016-01-26 11:50:03 -080020import static com.android.documentsui.Shared.DEBUG;
Steve McKay15b92782016-03-09 15:20:00 -080021
Ben Kwafaa27202016-01-28 16:39:57 -080022import android.annotation.IntDef;
23import android.annotation.Nullable;
Felipe Lemee4b60122016-02-24 10:17:41 -080024import android.annotation.StringDef;
25import android.app.Activity;
Ben Kwa1c0a3892016-01-26 11:50:03 -080026import android.content.Context;
27import android.content.Intent;
28import android.content.pm.ResolveInfo;
29import android.net.Uri;
30import android.provider.DocumentsContract;
31import android.util.Log;
Aga Wronskafa1832e2016-03-31 15:08:43 -070032import android.view.KeyEvent;
Ben Kwa1c0a3892016-01-26 11:50:03 -080033
Aga Wronska6d50bcc2016-03-28 17:27:02 -070034import com.android.documentsui.State.ActionType;
Ben Kwafaa27202016-01-28 16:39:57 -080035import com.android.documentsui.model.DocumentInfo;
Ben Kwa1c0a3892016-01-26 11:50:03 -080036import com.android.documentsui.model.RootInfo;
Ben Kwafaa27202016-01-28 16:39:57 -080037import com.android.documentsui.services.FileOperationService;
38import com.android.documentsui.services.FileOperationService.OpType;
Ben Kwa1c0a3892016-01-26 11:50:03 -080039import com.android.internal.logging.MetricsLogger;
Felipe Lemee4b60122016-02-24 10:17:41 -080040import com.android.internal.logging.MetricsProto.MetricsEvent;
Ben Kwa1c0a3892016-01-26 11:50:03 -080041
Ben Kwafaa27202016-01-28 16:39:57 -080042import java.lang.annotation.Retention;
43import java.lang.annotation.RetentionPolicy;
44import java.util.List;
45
Ben Kwa1c0a3892016-01-26 11:50:03 -080046/** @hide */
47public 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";
Ben Kwaebaaea42016-01-28 18:15:07 -080055 private static final String AUTHORITY_MTP = "com.android.mtp.documents";
Ben Kwa1c0a3892016-01-26 11:50:03 -080056
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";
Steve McKay15b92782016-03-09 15:20:00 -080064 @Deprecated private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
Aga Wronska94e53e42016-04-07 13:09:58 -070065 @Deprecated private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
Ben Kwafaa27202016-01-28 16:39:57 -080066 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";
Daichi Hirono320a08f2016-03-25 19:04:39 +090069 private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
Aga Wronska441b9be2016-03-29 16:57:10 -070070 private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened";
Aga Wronska94e53e42016-04-07 13:09:58 -070071 private static final String COUNT_USER_ACTION = "docsui_menu_action";
Ben Kwa1c0a3892016-01-26 11:50:03 -080072
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
Ben Kwab41a5ed2016-02-17 16:06:22 -080075 // #getSanitizedRootIndex}). Apps are also bucketed in this histogram.
Ben Kwaebaaea42016-01-28 18:15:07 -080076 // Do not change or rearrange these values, that will break historical data. Only add to the end
77 // of the list.
Ben Kwab41a5ed2016-02-17 16:06:22 -080078 // 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;
Ben Kwa1c0a3892016-01-26 11:50:03 -080089 // Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they
Ben Kwab41a5ed2016-02-17 16:06:22 -080090 // are logged analogously to roots.
91 private static final int ROOT_THIRD_PARTY_APP = 100;
Ben Kwa1c0a3892016-01-26 11:50:03 -080092
Ben Kwafaa27202016-01-28 16:39:57 -080093 @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,
Ben Kwaebaaea42016-01-28 18:15:07 -0800103 ROOT_MTP,
Ben Kwafaa27202016-01-28 16:39:57 -0800104 ROOT_THIRD_PARTY_APP
105 })
106 @Retention(RetentionPolicy.SOURCE)
107 public @interface Root {}
108
Ben Kwa1c0a3892016-01-26 11:50:03 -0800109 // Indices for bucketing mime types.
Ben Kwab41a5ed2016-02-17 16:06:22 -0800110 // 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
Ben Kwa1c0a3892016-01-26 11:50:03 -0800123
Ben Kwafaa27202016-01-28 16:39:57 -0800124 @IntDef(flag = true, value = {
Ben Kwafaa27202016-01-28 16:39:57 -0800125 MIME_NONE,
126 MIME_ANY,
127 MIME_APPLICATION,
128 MIME_AUDIO,
129 MIME_IMAGE,
130 MIME_MESSAGE,
131 MIME_MULTIPART,
132 MIME_TEXT,
Ben Kwab41a5ed2016-02-17 16:06:22 -0800133 MIME_VIDEO,
134 MIME_OTHER
Ben Kwafaa27202016-01-28 16:39:57 -0800135 })
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.
Ben Kwab41a5ed2016-02-17 16:06:22 -0800141 // 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;
Aga Wronska46a868a2016-03-30 10:57:04 -0700152 private static final int FILEOP_RENAME = 9;
153 private static final int FILEOP_CREATE_DIR = 10;
Ben Kwab41a5ed2016-02-17 16:06:22 -0800154 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;
Aga Wronska46a868a2016-03-30 10:57:04 -0700158 private static final int FILEOP_RENAME_ERROR = 104;
159 private static final int FILEOP_CREATE_DIR_ERROR = 105;
Ben Kwafaa27202016-01-28 16:39:57 -0800160
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,
Aga Wronska46a868a2016-03-30 10:57:04 -0700170 FILEOP_RENAME,
171 FILEOP_CREATE_DIR,
Ben Kwafaa27202016-01-28 16:39:57 -0800172 FILEOP_OTHER_ERROR,
173 FILEOP_COPY_ERROR,
174 FILEOP_MOVE_ERROR,
Aga Wronska46a868a2016-03-30 10:57:04 -0700175 FILEOP_DELETE_ERROR,
176 FILEOP_RENAME_ERROR,
177 FILEOP_CREATE_DIR_ERROR
Ben Kwafaa27202016-01-28 16:39:57 -0800178 })
179 @Retention(RetentionPolicy.SOURCE)
180 public @interface FileOp {}
181
Ben Kwab41a5ed2016-02-17 16:06:22 -0800182 // 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
Aga Wronska4972d712016-03-30 13:55:19 -0700201 // 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
Aga Wronska94e53e42016-04-07 13:09:58 -0700215 // 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.
Aga Wronska4972d712016-03-30 13:55:19 -0700218 // 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.
Aga Wronska94e53e42016-04-07 13:09:58 -0700221 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;
Aga Wronska4972d712016-03-30 13:55:19 -0700246
247 @IntDef(flag = false, value = {
Aga Wronska94e53e42016-04-07 13:09:58 -0700248 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
Aga Wronska4972d712016-03-30 13:55:19 -0700273 })
274 @Retention(RetentionPolicy.SOURCE)
Aga Wronska94e53e42016-04-07 13:09:58 -0700275 public @interface UserAction {}
Aga Wronska4972d712016-03-30 13:55:19 -0700276
277 // Codes representing different menu actions. These are used for bucketing stats in the
278 // COUNT_MENU_ACTION histogram.
Ben Kwab41a5ed2016-02-17 16:06:22 -0800279 // 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;
Steve McKay15b92782016-03-09 15:20:00 -0800287 @Deprecated private static final int ACTION_MANAGE = 6;
Ben Kwab41a5ed2016-02-17 16:06:22 -0800288 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
Aga Wronska441b9be2016-03-29 16:57:10 -0700304 // 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
Ben Kwa1c0a3892016-01-26 11:50:03 -0800319 /**
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 */
326 public static void logActivityLaunch(Context context, State state, Intent intent) {
327 // Log the launch action.
Ben Kwab41a5ed2016-02-17 16:06:22 -0800328 logHistogram(context, COUNT_LAUNCH_ACTION, toMetricsAction(state.action));
Ben Kwa1c0a3892016-01-26 11:50:03 -0800329 // 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;
Ben Kwa1c0a3892016-01-26 11:50:03 -0800341 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 */
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 */
365 public static void logAppVisited(Context context, ResolveInfo info) {
366 logHistogram(context, COUNT_ROOT_VISITED, sanitizeRoot(info));
367 }
368
369 /**
Aga Wronska441b9be2016-03-29 16:57:10 -0700370 * 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 */
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 /**
Ben Kwafaa27202016-01-28 16:39:57 -0800384 * 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 */
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 /**
Aga Wronska46a868a2016-03-30 10:57:04 -0700414 * 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 */
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 */
431 public static void logRenameFileOperation(Context context) {
432 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME);
433 }
434
435 /**
Ben Kwafaa27202016-01-28 16:39:57 -0800436 * 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 */
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 /**
Aga Wronska46a868a2016-03-30 10:57:04 -0700468 * 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 */
474 public static void logCreateDirError(Context context) {
Aga Wronska94e53e42016-04-07 13:09:58 -0700475 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR_ERROR);
Aga Wronska46a868a2016-03-30 10:57:04 -0700476 }
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 */
485 public static void logRenameFileError(Context context) {
486 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME_ERROR);
487 }
488
489 /**
Daichi Hirono320a08f2016-03-25 19:04:39 +0900490 * Logs the cancellation of a file operation. Call this when a Job is canceled.
Ben Kwafaa27202016-01-28 16:39:57 -0800491 * @param context
492 * @param operationType
493 */
494 public static void logFileOperationCancelled(Context context, @OpType int operationType) {
Ben Kwab41a5ed2016-02-17 16:06:22 -0800495 logHistogram(context, COUNT_FILEOP_CANCELED, toMetricsOpType(operationType));
Ben Kwafaa27202016-01-28 16:39:57 -0800496 }
497
Daichi Hirono320a08f2016-03-25 19:04:39 +0900498 /**
499 * Logs startup time in milliseconds.
500 * @param context
501 * @param startupMs Startup time in milliseconds.
502 */
503 public static void logStartupMs(Context context, int startupMs) {
504 logHistogram(context, COUNT_STARTUP_MS, startupMs);
505 }
506
Ben Kwafaa27202016-01-28 16:39:57 -0800507 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 {
Steve McKay0af8afd2016-02-25 13:34:03 -0800515 assert(dst != null);
Ben Kwafaa27202016-01-28 16:39:57 -0800516 @Provider int providerType =
517 isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL;
518 logHistogram(context, histogram, getOpCode(operationType, providerType));
519 }
520 }
521
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
Felipe Lemee4b60122016-02-24 10:17:41 -0800529 // Types for logInvalidScopedAccessRequest
530 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS =
531 "scoped_directory_access_invalid_args";
532 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY =
533 "scoped_directory_access_invalid_dir";
534 public static final String SCOPED_DIRECTORY_ACCESS_ERROR =
535 "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
545 public static void logInvalidScopedAccessRequest(Context context,
546 @InvalidScopedAccess String type) {
547 MetricsLogger.count(context, type, 1);
548 switch (type) {
549 case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS:
550 case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY:
551 case SCOPED_DIRECTORY_ACCESS_ERROR:
552 MetricsLogger.count(context, type, 1);
553 break;
554 default:
555 Log.wtf(TAG, "invalid InvalidScopedAccess: " + type);
556 }
557 }
558
559 // Types for logValidScopedAccessRequest
560 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0;
561 public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1;
562 public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2;
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800563 public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3;
564 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4;
Felipe Lemee4b60122016-02-24 10:17:41 -0800565
566 @IntDef(flag = true, value = {
567 SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED,
568 SCOPED_DIRECTORY_ACCESS_GRANTED,
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800569 SCOPED_DIRECTORY_ACCESS_DENIED,
570 SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST,
571 SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED
Felipe Lemee4b60122016-02-24 10:17:41 -0800572 })
573 @Retention(RetentionPolicy.SOURCE)
574 public @interface ScopedAccessGrant {}
575
576 public static void logValidScopedAccessRequest(Activity activity, String directory,
577 @ScopedAccessGrant int type) {
578 int index = -1;
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700579 if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) {
580 index = -2;
581 } else {
582 for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) {
583 if (STANDARD_DIRECTORIES[i].equals(directory)) {
584 index = i;
585 break;
586 }
Felipe Lemee4b60122016-02-24 10:17:41 -0800587 }
588 }
589 final String packageName = activity.getCallingPackage();
590 switch (type) {
591 case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED:
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800592 MetricsLogger.action(activity, MetricsEvent
593 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName);
594 MetricsLogger.action(activity, MetricsEvent
595 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index);
Felipe Lemee4b60122016-02-24 10:17:41 -0800596 break;
597 case SCOPED_DIRECTORY_ACCESS_GRANTED:
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800598 MetricsLogger.action(activity, MetricsEvent
599 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName);
600 MetricsLogger.action(activity, MetricsEvent
601 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index);
Felipe Lemee4b60122016-02-24 10:17:41 -0800602 break;
603 case SCOPED_DIRECTORY_ACCESS_DENIED:
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800604 MetricsLogger.action(activity, MetricsEvent
605 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName);
606 MetricsLogger.action(activity, MetricsEvent
607 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index);
608 break;
609 case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST:
610 MetricsLogger.action(activity, MetricsEvent
611 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName);
612 MetricsLogger.action(activity, MetricsEvent
613 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index);
614 break;
615 case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED:
616 MetricsLogger.action(activity, MetricsEvent
617 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName);
618 MetricsLogger.action(activity, MetricsEvent
619 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index);
Felipe Lemee4b60122016-02-24 10:17:41 -0800620 break;
621 default:
622 Log.wtf(TAG, "invalid ScopedAccessGrant: " + type);
623 }
624 }
625
Ben Kwafaa27202016-01-28 16:39:57 -0800626 /**
Aga Wronska94e53e42016-04-07 13:09:58 -0700627 * Logs the action that was started by user.
Aga Wronska4972d712016-03-30 13:55:19 -0700628 * @param context
Aga Wronska94e53e42016-04-07 13:09:58 -0700629 * @param userAction
Aga Wronska4972d712016-03-30 13:55:19 -0700630 */
Aga Wronska94e53e42016-04-07 13:09:58 -0700631 public static void logUserAction(Context context, @UserAction int userAction) {
632 logHistogram(context, COUNT_USER_ACTION, userAction);
Aga Wronska4972d712016-03-30 13:55:19 -0700633 }
634
635 /**
Ben Kwa1c0a3892016-01-26 11:50:03 -0800636 * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
637 *
638 * @param context
639 * @param name The counter to increment.
640 */
641 private static void logCount(Context context, String name) {
642 if (DEBUG) Log.d(TAG, name + ": " + 1);
643 MetricsLogger.count(context, name, 1);
644 }
645
646 /**
647 * Internal method for making a MetricsLogger.histogram call.
648 *
649 * @param context
650 * @param name The name of the histogram.
651 * @param bucket The bucket to increment.
652 */
Aga Wronska6d50bcc2016-03-28 17:27:02 -0700653 private static void logHistogram(Context context, String name, @ActionType int bucket) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800654 if (DEBUG) Log.d(TAG, name + ": " + bucket);
655 MetricsLogger.histogram(context, name, bucket);
656 }
657
658 /**
659 * Generates an integer identifying the given root. For privacy, this function only recognizes a
660 * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
661 * a single ROOT_OTHER bucket.
662 */
Ben Kwafaa27202016-01-28 16:39:57 -0800663 private static @Root int sanitizeRoot(Uri uri) {
Tomasz Mikolajewski63e2aae2016-02-01 12:01:14 +0900664 if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800665 return ROOT_NONE;
666 }
667
668 switch (uri.getAuthority()) {
669 case AUTHORITY_MEDIA:
670 switch (DocumentsContract.getRootId(uri)) {
671 case "audio_root":
672 return ROOT_AUDIO;
673 case "images_root":
674 return ROOT_IMAGES;
675 case "videos_root":
676 return ROOT_VIDEOS;
677 default:
678 return ROOT_OTHER;
679 }
680 case AUTHORITY_STORAGE:
681 if ("home".equals(DocumentsContract.getRootId(uri))) {
682 return ROOT_HOME;
683 } else {
684 return ROOT_DEVICE_STORAGE;
685 }
686 case AUTHORITY_DOWNLOADS:
687 return ROOT_DOWNLOADS;
Ben Kwaebaaea42016-01-28 18:15:07 -0800688 case AUTHORITY_MTP:
689 return ROOT_MTP;
Ben Kwa1c0a3892016-01-26 11:50:03 -0800690 default:
691 return ROOT_OTHER;
692 }
693 }
694
695 /** @see #sanitizeRoot(Uri) */
Ben Kwafaa27202016-01-28 16:39:57 -0800696 private static @Root int sanitizeRoot(RootInfo root) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800697 if (root.isRecents()) {
698 // Recents root is special and only identifiable via this method call. Other roots are
699 // identified by URI.
700 return ROOT_RECENTS;
701 } else {
702 return sanitizeRoot(root.getUri());
703 }
704 }
705
706 /** @see #sanitizeRoot(Uri) */
Ben Kwafaa27202016-01-28 16:39:57 -0800707 private static @Root int sanitizeRoot(ResolveInfo info) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800708 // Log all apps under a single bucket in the roots histogram.
709 return ROOT_THIRD_PARTY_APP;
710 }
711
712 /**
713 * Generates an int identifying a mime type. For privacy, this function only recognizes a small
714 * set of hard-coded types. For any other type, this function returns "other".
715 *
716 * @param mimeType
717 * @return
718 */
Ben Kwafaa27202016-01-28 16:39:57 -0800719 private static @Mime int sanitizeMime(String mimeType) {
Ben Kwa1c0a3892016-01-26 11:50:03 -0800720 if (mimeType == null) {
721 return MIME_NONE;
722 } else if ("*/*".equals(mimeType)) {
723 return MIME_ANY;
724 } else {
725 String type = mimeType.substring(0, mimeType.indexOf('/'));
726 switch (type) {
727 case "application":
728 return MIME_APPLICATION;
729 case "audio":
730 return MIME_AUDIO;
731 case "image":
732 return MIME_IMAGE;
733 case "message":
734 return MIME_MESSAGE;
735 case "multipart":
736 return MIME_MULTIPART;
737 case "text":
738 return MIME_TEXT;
739 case "video":
740 return MIME_VIDEO;
741 }
742 }
743 // Bucket all other types into one bucket.
744 return MIME_OTHER;
745 }
Ben Kwafaa27202016-01-28 16:39:57 -0800746
747 private static boolean isSystemProvider(String authority) {
748 switch (authority) {
749 case AUTHORITY_MEDIA:
750 case AUTHORITY_STORAGE:
751 case AUTHORITY_DOWNLOADS:
752 return true;
753 default:
754 return false;
755 }
756 }
757
758 /**
759 * @param operation
760 * @param providerType
761 * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
762 * combination.
763 */
764 private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
765 switch (operation) {
766 case FileOperationService.OPERATION_COPY:
767 switch (providerType) {
768 case PROVIDER_INTRA:
769 return FILEOP_COPY_INTRA_PROVIDER;
770 case PROVIDER_SYSTEM:
771 return FILEOP_COPY_SYSTEM_PROVIDER;
772 case PROVIDER_EXTERNAL:
773 return FILEOP_COPY_EXTERNAL_PROVIDER;
774 }
775 case FileOperationService.OPERATION_MOVE:
776 switch (providerType) {
777 case PROVIDER_INTRA:
778 return FILEOP_MOVE_INTRA_PROVIDER;
779 case PROVIDER_SYSTEM:
780 return FILEOP_MOVE_SYSTEM_PROVIDER;
781 case PROVIDER_EXTERNAL:
782 return FILEOP_MOVE_EXTERNAL_PROVIDER;
783 }
784 case FileOperationService.OPERATION_DELETE:
785 return FILEOP_DELETE;
786 default:
787 Log.w(TAG, "Unrecognized operation type when logging a file operation");
788 return FILEOP_OTHER;
789 }
790 }
791
792 /**
Ben Kwab41a5ed2016-02-17 16:06:22 -0800793 * Maps FileOperationService OpType values, to MetricsOpType values.
794 */
795 private static @MetricsOpType int toMetricsOpType(@OpType int operation) {
796 switch (operation) {
797 case FileOperationService.OPERATION_COPY:
798 return OPERATION_COPY;
799 case FileOperationService.OPERATION_MOVE:
800 return OPERATION_MOVE;
801 case FileOperationService.OPERATION_DELETE:
802 return OPERATION_DELETE;
803 case FileOperationService.OPERATION_UNKNOWN:
804 default:
805 return OPERATION_UNKNOWN;
806 }
807 }
808
809 private static @MetricsAction int toMetricsAction(int action) {
810 switch(action) {
811 case State.ACTION_OPEN:
812 return ACTION_OPEN;
813 case State.ACTION_CREATE:
814 return ACTION_CREATE;
815 case State.ACTION_GET_CONTENT:
816 return ACTION_GET_CONTENT;
817 case State.ACTION_OPEN_TREE:
818 return ACTION_OPEN_TREE;
Ben Kwab41a5ed2016-02-17 16:06:22 -0800819 case State.ACTION_BROWSE:
820 return ACTION_BROWSE;
821 case State.ACTION_PICK_COPY_DESTINATION:
822 return ACTION_PICK_COPY_DESTINATION;
823 default:
824 return ACTION_OTHER;
825 }
826 }
827
828 /**
Ben Kwafaa27202016-01-28 16:39:57 -0800829 * Count the given src documents and provide a tally of how many come from the same provider as
830 * the dst document (if a dst is provided), how many come from system providers, and how many
831 * come from external 3rd-party providers.
832 */
833 private static ProviderCounts countProviders(
834 List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
835 ProviderCounts counts = new ProviderCounts();
836 for (DocumentInfo doc: srcs) {
837 if (dst != null && doc.authority.equals(dst.authority)) {
838 counts.intraProvider++;
839 } else if (isSystemProvider(doc.authority)){
840 counts.systemProvider++;
841 } else {
842 counts.externalProvider++;
843 }
844 }
845 return counts;
846 }
847
848 private static class ProviderCounts {
849 int intraProvider;
850 int systemProvider;
851 int externalProvider;
852 }
Ben Kwa1c0a3892016-01-26 11:50:03 -0800853}