blob: a4a67f96a3ee95b22056dc95fb189d18f4b45b1e [file] [log] [blame]
Ben Kwa72379982016-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 Leme3e166b22016-02-24 10:17:41 -080019import static android.os.Environment.STANDARD_DIRECTORIES;
Ben Kwa72379982016-01-26 11:50:03 -080020import static com.android.documentsui.Shared.DEBUG;
Steve McKay6ce903d2016-03-09 15:20:00 -080021
Ben Kwad5b2af12016-01-28 16:39:57 -080022import android.annotation.IntDef;
23import android.annotation.Nullable;
Felipe Leme3e166b22016-02-24 10:17:41 -080024import android.annotation.StringDef;
25import android.app.Activity;
Ben Kwa72379982016-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 Wronska0614c162016-03-31 15:08:43 -070032import android.view.KeyEvent;
Ben Kwa72379982016-01-26 11:50:03 -080033
Aga Wronska973168c2016-03-28 17:27:02 -070034import com.android.documentsui.State.ActionType;
Ben Kwad5b2af12016-01-28 16:39:57 -080035import com.android.documentsui.model.DocumentInfo;
Ben Kwa72379982016-01-26 11:50:03 -080036import com.android.documentsui.model.RootInfo;
Ben Kwad5b2af12016-01-28 16:39:57 -080037import com.android.documentsui.services.FileOperationService;
38import com.android.documentsui.services.FileOperationService.OpType;
Ben Kwa72379982016-01-26 11:50:03 -080039import com.android.internal.logging.MetricsLogger;
Felipe Leme3e166b22016-02-24 10:17:41 -080040import com.android.internal.logging.MetricsProto.MetricsEvent;
Ben Kwa72379982016-01-26 11:50:03 -080041
Ben Kwad5b2af12016-01-28 16:39:57 -080042import java.lang.annotation.Retention;
43import java.lang.annotation.RetentionPolicy;
44import java.util.List;
45
Ben Kwa72379982016-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 Kwada518c92016-01-28 18:15:07 -080055 private static final String AUTHORITY_MTP = "com.android.mtp.documents";
Ben Kwa72379982016-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 McKay6ce903d2016-03-09 15:20:00 -080064 @Deprecated private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
Ben Kwa72379982016-01-26 11:50:03 -080065 private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
Ben Kwad5b2af12016-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 Hirono7352c552016-03-25 19:04:39 +090069 private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
Aga Wronska7dcf81e2016-03-29 16:57:10 -070070 private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened";
Aga Wronskac573bec2016-03-31 16:37:40 -070071 private static final String COUNT_DRAG_N_DROP = "docsui_drag_n_drop";
72 private static final String COUNT_SEARCH = "docsui_search";
Aga Wronskacf966ae2016-03-30 13:55:19 -070073 private static final String COUNT_MENU_ACTION = "docsui_menu_action";
Aga Wronska0614c162016-03-31 15:08:43 -070074 private static final String COUNT_KEYBOARD_ACTION = "docsui_keyboard_action";
Ben Kwa72379982016-01-26 11:50:03 -080075
76 // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
77 // root that is not explicitly recognized by the Metrics code (see {@link
Ben Kwaa87e9f92016-02-17 16:06:22 -080078 // #getSanitizedRootIndex}). Apps are also bucketed in this histogram.
Ben Kwada518c92016-01-28 18:15:07 -080079 // Do not change or rearrange these values, that will break historical data. Only add to the end
80 // of the list.
Ben Kwaa87e9f92016-02-17 16:06:22 -080081 // Do not use negative numbers or zero; clearcut only handles positive integers.
82 private static final int ROOT_NONE = 1;
83 private static final int ROOT_OTHER = 2;
84 private static final int ROOT_AUDIO = 3;
85 private static final int ROOT_DEVICE_STORAGE = 4;
86 private static final int ROOT_DOWNLOADS = 5;
87 private static final int ROOT_HOME = 6;
88 private static final int ROOT_IMAGES = 7;
89 private static final int ROOT_RECENTS = 8;
90 private static final int ROOT_VIDEOS = 9;
91 private static final int ROOT_MTP = 10;
Ben Kwa72379982016-01-26 11:50:03 -080092 // Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they
Ben Kwaa87e9f92016-02-17 16:06:22 -080093 // are logged analogously to roots.
94 private static final int ROOT_THIRD_PARTY_APP = 100;
Ben Kwa72379982016-01-26 11:50:03 -080095
Ben Kwad5b2af12016-01-28 16:39:57 -080096 @IntDef(flag = true, value = {
97 ROOT_NONE,
98 ROOT_OTHER,
99 ROOT_AUDIO,
100 ROOT_DEVICE_STORAGE,
101 ROOT_DOWNLOADS,
102 ROOT_HOME,
103 ROOT_IMAGES,
104 ROOT_RECENTS,
105 ROOT_VIDEOS,
Ben Kwada518c92016-01-28 18:15:07 -0800106 ROOT_MTP,
Ben Kwad5b2af12016-01-28 16:39:57 -0800107 ROOT_THIRD_PARTY_APP
108 })
109 @Retention(RetentionPolicy.SOURCE)
110 public @interface Root {}
111
Ben Kwa72379982016-01-26 11:50:03 -0800112 // Indices for bucketing mime types.
Ben Kwaa87e9f92016-02-17 16:06:22 -0800113 // Do not change or rearrange these values, that will break historical data. Only add to the end
114 // of the list.
115 // Do not use negative numbers or zero; clearcut only handles positive integers.
116 private static final int MIME_NONE = 1; // null mime
117 private static final int MIME_ANY = 2; // */*
118 private static final int MIME_APPLICATION = 3; // application/*
119 private static final int MIME_AUDIO = 4; // audio/*
120 private static final int MIME_IMAGE = 5; // image/*
121 private static final int MIME_MESSAGE = 6; // message/*
122 private static final int MIME_MULTIPART = 7; // multipart/*
123 private static final int MIME_TEXT = 8; // text/*
124 private static final int MIME_VIDEO = 9; // video/*
125 private static final int MIME_OTHER = 10; // anything not enumerated below
Ben Kwa72379982016-01-26 11:50:03 -0800126
Ben Kwad5b2af12016-01-28 16:39:57 -0800127 @IntDef(flag = true, value = {
Ben Kwad5b2af12016-01-28 16:39:57 -0800128 MIME_NONE,
129 MIME_ANY,
130 MIME_APPLICATION,
131 MIME_AUDIO,
132 MIME_IMAGE,
133 MIME_MESSAGE,
134 MIME_MULTIPART,
135 MIME_TEXT,
Ben Kwaa87e9f92016-02-17 16:06:22 -0800136 MIME_VIDEO,
137 MIME_OTHER
Ben Kwad5b2af12016-01-28 16:39:57 -0800138 })
139 @Retention(RetentionPolicy.SOURCE)
140 public @interface Mime {}
141
142 // Codes representing different kinds of file operations. These are used for bucketing
143 // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms.
Ben Kwaa87e9f92016-02-17 16:06:22 -0800144 // Do not change or rearrange these values, that will break historical data. Only add to the
145 // list.
146 // Do not use negative numbers or zero; clearcut only handles positive integers.
147 private static final int FILEOP_OTHER = 1; // any file operation not listed below
148 private static final int FILEOP_COPY_INTRA_PROVIDER = 2; // Copy within a provider
149 private static final int FILEOP_COPY_SYSTEM_PROVIDER = 3; // Copy to a system provider.
150 private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 4; // Copy to a 3rd-party provider.
151 private static final int FILEOP_MOVE_INTRA_PROVIDER = 5; // Move within a provider.
152 private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 6; // Move to a system provider.
153 private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 7; // Move to a 3rd-party provider.
154 private static final int FILEOP_DELETE = 8;
Aga Wronska4e8c7f62016-03-30 10:57:04 -0700155 private static final int FILEOP_RENAME = 9;
156 private static final int FILEOP_CREATE_DIR = 10;
Ben Kwaa87e9f92016-02-17 16:06:22 -0800157 private static final int FILEOP_OTHER_ERROR = 100;
158 private static final int FILEOP_DELETE_ERROR = 101;
159 private static final int FILEOP_MOVE_ERROR = 102;
160 private static final int FILEOP_COPY_ERROR = 103;
Aga Wronska4e8c7f62016-03-30 10:57:04 -0700161 private static final int FILEOP_RENAME_ERROR = 104;
162 private static final int FILEOP_CREATE_DIR_ERROR = 105;
Ben Kwad5b2af12016-01-28 16:39:57 -0800163
164 @IntDef(flag = true, value = {
165 FILEOP_OTHER,
166 FILEOP_COPY_INTRA_PROVIDER,
167 FILEOP_COPY_SYSTEM_PROVIDER,
168 FILEOP_COPY_EXTERNAL_PROVIDER,
169 FILEOP_MOVE_INTRA_PROVIDER,
170 FILEOP_MOVE_SYSTEM_PROVIDER,
171 FILEOP_MOVE_EXTERNAL_PROVIDER,
172 FILEOP_DELETE,
Aga Wronska4e8c7f62016-03-30 10:57:04 -0700173 FILEOP_RENAME,
174 FILEOP_CREATE_DIR,
Ben Kwad5b2af12016-01-28 16:39:57 -0800175 FILEOP_OTHER_ERROR,
176 FILEOP_COPY_ERROR,
177 FILEOP_MOVE_ERROR,
Aga Wronska4e8c7f62016-03-30 10:57:04 -0700178 FILEOP_DELETE_ERROR,
179 FILEOP_RENAME_ERROR,
180 FILEOP_CREATE_DIR_ERROR
Ben Kwad5b2af12016-01-28 16:39:57 -0800181 })
182 @Retention(RetentionPolicy.SOURCE)
183 public @interface FileOp {}
184
Ben Kwaa87e9f92016-02-17 16:06:22 -0800185 // Codes representing different kinds of file operations. These are used for bucketing
186 // operations in the COUNT_FILEOP_CANCELED histogram.
187 // Do not change or rearrange these values, that will break historical data. Only add to the
188 // list.
189 // Do not use negative numbers or zero; clearcut only handles positive integers.
190 private static final int OPERATION_UNKNOWN = 1;
191 private static final int OPERATION_COPY = 2;
192 private static final int OPERATION_MOVE = 3;
193 private static final int OPERATION_DELETE= 4;
194
195 @IntDef(flag = true, value = {
196 OPERATION_UNKNOWN,
197 OPERATION_COPY,
198 OPERATION_MOVE,
199 OPERATION_DELETE
200 })
201 @Retention(RetentionPolicy.SOURCE)
202 public @interface MetricsOpType {}
203
Aga Wronskacf966ae2016-03-30 13:55:19 -0700204 // Codes representing different provider types. Used for sorting file operations when logging.
205 private static final int PROVIDER_INTRA = 0;
206 private static final int PROVIDER_SYSTEM = 1;
207 private static final int PROVIDER_EXTERNAL = 2;
208
209 @IntDef(flag = false, value = {
210 PROVIDER_INTRA,
211 PROVIDER_SYSTEM,
212 PROVIDER_EXTERNAL
213 })
214 @Retention(RetentionPolicy.SOURCE)
215 public @interface Provider {}
216
217
218 // Codes representing different menu actions. These are used for bucketing stats in the
219 // COUNT_MENU_ACTION histogram.
220 // Both regular toolbar menu and action mode menu operations are included.
221 // Do not change or rearrange these values, that will break historical data. Only add to the
222 // list.
223 // Do not use negative numbers or zero; clearcut only handles positive integers.
224 private static final int ACTION_MENU_OTHER = 1;
225 private static final int ACTION_MENU_GRID = 2;
226 private static final int ACTION_MENU_LIST = 3;
227 private static final int ACTION_MENU_SORT = 4;
228 private static final int ACTION_MENU_SORT_NAME = 5;
229 private static final int ACTION_MENU_SORT_DATE = 6;
230 private static final int ACTION_MENU_SORT_SIZE = 7;
231 private static final int ACTION_MENU_SEARCH = 8;
232 private static final int ACTION_MENU_SHOW_SIZE = 9;
233 private static final int ACTION_MENU_SETTINGS = 10;
234 private static final int ACTION_MENU_COPY_TO = 11;
235 private static final int ACTION_MENU_MOVE_TO = 12;
236 private static final int ACTION_MENU_DELETE = 13;
237 private static final int ACTION_MENU_RENAME = 14;
238 private static final int ACTION_MENU_CREATE_DIR = 15;
239 private static final int ACTION_MENU_SELECT_ALL = 16;
240 private static final int ACTION_MENU_SHARE = 17;
241 private static final int ACTION_MENU_OPEN = 18;
242 private static final int ACTION_MENU_ADVANCED = 19;
243
244 @IntDef(flag = false, value = {
245 ACTION_MENU_OTHER,
246 ACTION_MENU_GRID,
247 ACTION_MENU_LIST,
248 ACTION_MENU_SORT,
249 ACTION_MENU_SORT_NAME,
250 ACTION_MENU_SORT_DATE,
251 ACTION_MENU_SORT_SIZE,
252 ACTION_MENU_SHOW_SIZE,
253 ACTION_MENU_SETTINGS,
254 ACTION_MENU_COPY_TO,
255 ACTION_MENU_MOVE_TO,
256 ACTION_MENU_DELETE,
257 ACTION_MENU_RENAME,
258 ACTION_MENU_CREATE_DIR,
259 ACTION_MENU_SELECT_ALL,
260 ACTION_MENU_SHARE,
261 ACTION_MENU_OPEN,
262 ACTION_MENU_ADVANCED
263 })
264 @Retention(RetentionPolicy.SOURCE)
265 public @interface MenuAction {}
266
267 // Codes representing different menu actions. These are used for bucketing stats in the
268 // COUNT_MENU_ACTION histogram.
Ben Kwaa87e9f92016-02-17 16:06:22 -0800269 // Do not change or rearrange these values, that will break historical data. Only add to the
270 // list.
271 // Do not use negative numbers or zero; clearcut only handles positive integers.
272 private static final int ACTION_OTHER = 1;
273 private static final int ACTION_OPEN = 2;
274 private static final int ACTION_CREATE = 3;
275 private static final int ACTION_GET_CONTENT = 4;
276 private static final int ACTION_OPEN_TREE = 5;
Steve McKay6ce903d2016-03-09 15:20:00 -0800277 @Deprecated private static final int ACTION_MANAGE = 6;
Ben Kwaa87e9f92016-02-17 16:06:22 -0800278 private static final int ACTION_BROWSE = 7;
279 private static final int ACTION_PICK_COPY_DESTINATION = 8;
280
281 @IntDef(flag = true, value = {
282 ACTION_OTHER,
283 ACTION_OPEN,
284 ACTION_CREATE,
285 ACTION_GET_CONTENT,
286 ACTION_OPEN_TREE,
287 ACTION_MANAGE,
288 ACTION_BROWSE,
289 ACTION_PICK_COPY_DESTINATION
290 })
291 @Retention(RetentionPolicy.SOURCE)
292 public @interface MetricsAction {}
293
Aga Wronska0614c162016-03-31 15:08:43 -0700294 // Codes representing different keyboard shortcut triggered actions. These are used for
295 // bucketing stats in the COUNT_KEYBOARD_ACTION histogram.
296 // Do not change or rearrange these values, that will break historical data. Only add to the
297 // list.
298 // Do not use negative numbers or zero; clearcut only handles positive integers.
299 private static final int ACTION_KEYBOARD_OTHER = 1;
300 private static final int ACTION_KEYBOARD_PASTE = 2;
301 private static final int ACTION_KEYBOARD_COPY = 3;
302 private static final int ACTION_KEYBOARD_DELETE = 4;
303 private static final int ACTION_KEYBOARD_SELECT_ALL = 5;
304 private static final int ACTION_KEYBOARD_BACK = 6;
305 private static final int ACTION_KEYBOARD_SWITCH_FOCUS = 7;
306
307 @IntDef(flag = false, value = {
308 ACTION_KEYBOARD_OTHER,
309 ACTION_KEYBOARD_PASTE,
310 ACTION_KEYBOARD_COPY,
311 ACTION_KEYBOARD_DELETE,
312 ACTION_KEYBOARD_SELECT_ALL,
313 ACTION_KEYBOARD_BACK,
314 ACTION_KEYBOARD_SWITCH_FOCUS
315 })
316 @Retention(RetentionPolicy.SOURCE)
317 public @interface KeyboardAction {}
318
Aga Wronska7dcf81e2016-03-29 16:57:10 -0700319 // Codes representing different actions to open the drawer. They are used for bucketing stats in
320 // the COUNT_DRAWER_OPENED histogram.
321 // Do not change or rearrange these values, that will break historical data. Only add to the
322 // list.
323 // Do not use negative numbers or zero; clearcut only handles positive integers.
324 private static final int DRAWER_OPENED_HAMBURGER = 1;
325 private static final int DRAWER_OPENED_SWIPE = 2;
326
327 @IntDef(flag = true, value = {
328 DRAWER_OPENED_HAMBURGER,
329 DRAWER_OPENED_SWIPE
330 })
331 @Retention(RetentionPolicy.SOURCE)
332 public @interface DrawerTrigger {}
333
Ben Kwa72379982016-01-26 11:50:03 -0800334 /**
335 * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
336 *
337 * @param context
338 * @param state
339 * @param intent
340 */
341 public static void logActivityLaunch(Context context, State state, Intent intent) {
342 // Log the launch action.
Ben Kwaa87e9f92016-02-17 16:06:22 -0800343 logHistogram(context, COUNT_LAUNCH_ACTION, toMetricsAction(state.action));
Ben Kwa72379982016-01-26 11:50:03 -0800344 // Then log auxiliary data (roots/mime types) associated with some actions.
345 Uri uri = intent.getData();
346 switch (state.action) {
347 case State.ACTION_OPEN:
348 logHistogram(context, COUNT_OPEN_MIME, sanitizeMime(intent.getType()));
349 break;
350 case State.ACTION_CREATE:
351 logHistogram(context, COUNT_CREATE_MIME, sanitizeMime(intent.getType()));
352 break;
353 case State.ACTION_GET_CONTENT:
354 logHistogram(context, COUNT_GET_CONTENT_MIME, sanitizeMime(intent.getType()));
355 break;
Ben Kwa72379982016-01-26 11:50:03 -0800356 case State.ACTION_BROWSE:
357 logHistogram(context, COUNT_BROWSE_ROOT, sanitizeRoot(uri));
358 break;
359 default:
360 break;
361 }
362 }
363
364 /**
365 * Logs a root visited event. Call this when the user clicks on a root in the RootsFragment.
366 *
367 * @param context
368 * @param info
369 */
370 public static void logRootVisited(Context context, RootInfo info) {
371 logHistogram(context, COUNT_ROOT_VISITED, sanitizeRoot(info));
372 }
373
374 /**
375 * Logs an app visited event. Call this when the user clicks on an app in the RootsFragment.
376 *
377 * @param context
378 * @param info
379 */
380 public static void logAppVisited(Context context, ResolveInfo info) {
381 logHistogram(context, COUNT_ROOT_VISITED, sanitizeRoot(info));
382 }
383
384 /**
385 * Logs a multi-window start. Call this when the user spawns a new DocumentsUI window.
386 *
387 * @param context
388 */
389 public static void logMultiWindow(Context context) {
390 logCount(context, COUNT_MULTI_WINDOW);
391 }
392
393 /**
Aga Wronska7dcf81e2016-03-29 16:57:10 -0700394 * Logs a drawer opened event. Call this when the user opens drawer by swipe or by clicking the
395 * hamburger icon.
396 * @param context
397 * @param trigger type of action that opened the drawer
398 */
399 public static void logDrawerOpened(Context context, @DrawerController.Trigger int trigger) {
400 if (trigger == DrawerController.OPENED_HAMBURGER) {
401 logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_HAMBURGER);
402 } else if (trigger == DrawerController.OPENED_SWIPE) {
403 logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_SWIPE);
404 }
405 }
406
407 /**
Ben Kwad5b2af12016-01-28 16:39:57 -0800408 * Logs file operation stats. Call this when a file operation has completed. The given
409 * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
410 * provider to another vs copying within a given provider). No PII is logged.
411 *
412 * @param context
413 * @param operationType
414 * @param srcs
415 * @param dst
416 */
417 public static void logFileOperation(
418 Context context,
419 @OpType int operationType,
420 List<DocumentInfo> srcs,
421 @Nullable DocumentInfo dst) {
422 ProviderCounts counts = countProviders(srcs, dst);
423
424 if (counts.intraProvider > 0) {
425 logIntraProviderFileOps(context, dst.authority, operationType);
426 }
427 if (counts.systemProvider > 0) {
428 // Log file operations on system providers.
429 logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType);
430 }
431 if (counts.externalProvider > 0) {
432 // Log file operations on external providers.
433 logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType);
434 }
435 }
436
437 /**
Aga Wronska4e8c7f62016-03-30 10:57:04 -0700438 * Logs create directory operation. It is a part of file operation stats. We do not
439 * differentiate between internal and external locations, all create directory operations are
440 * logged under COUNT_FILEOP_SYSTEM. Call this when a create directory operation has completed.
441 *
442 * @param context
443 */
444 public static void logCreateDirOperation(Context context) {
445 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR);
446 }
447
448 /**
449 * Logs rename file operation. It is a part of file operation stats. We do not differentiate
450 * between internal and external locations, all rename operations are logged under
451 * COUNT_FILEOP_SYSTEM. Call this when a rename file operation has completed.
452 *
453 * @param context
454 */
455 public static void logRenameFileOperation(Context context) {
456 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME);
457 }
458
459 /**
Ben Kwad5b2af12016-01-28 16:39:57 -0800460 * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete)
461 * fails.
462 *
463 * @param context
464 * @param operationType
465 * @param failedFiles
466 */
467 public static void logFileOperationErrors(Context context, @OpType int operationType,
468 List<DocumentInfo> failedFiles) {
469 ProviderCounts counts = countProviders(failedFiles, null);
470
471 @FileOp int opCode = FILEOP_OTHER_ERROR;
472 switch (operationType) {
473 case FileOperationService.OPERATION_COPY:
474 opCode = FILEOP_COPY_ERROR;
475 break;
476 case FileOperationService.OPERATION_DELETE:
477 opCode = FILEOP_DELETE_ERROR;
478 break;
479 case FileOperationService.OPERATION_MOVE:
480 opCode = FILEOP_MOVE_ERROR;
481 break;
482 }
483 if (counts.systemProvider > 0) {
484 logHistogram(context, COUNT_FILEOP_SYSTEM, opCode);
485 }
486 if (counts.externalProvider > 0) {
487 logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode);
488 }
489 }
490
491 /**
Aga Wronska4e8c7f62016-03-30 10:57:04 -0700492 * Logs create directory operation error. We do not differentiate between internal and external
493 * locations, all create directory errors are logged under COUNT_FILEOP_SYSTEM. Call this when a
494 * create directory operation fails.
495 *
496 * @param context
497 */
498 public static void logCreateDirError(Context context) {
499 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR);
500 }
501
502 /**
503 * Logs rename file operation error. We do not differentiate between internal and external
504 * locations, all rename errors are logged under COUNT_FILEOP_SYSTEM. Call this
505 * when a rename file operation fails.
506 *
507 * @param context
508 */
509 public static void logRenameFileError(Context context) {
510 logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME_ERROR);
511 }
512
513 /**
Daichi Hirono7352c552016-03-25 19:04:39 +0900514 * Logs the cancellation of a file operation. Call this when a Job is canceled.
Ben Kwad5b2af12016-01-28 16:39:57 -0800515 * @param context
516 * @param operationType
517 */
518 public static void logFileOperationCancelled(Context context, @OpType int operationType) {
Ben Kwaa87e9f92016-02-17 16:06:22 -0800519 logHistogram(context, COUNT_FILEOP_CANCELED, toMetricsOpType(operationType));
Ben Kwad5b2af12016-01-28 16:39:57 -0800520 }
521
Daichi Hirono7352c552016-03-25 19:04:39 +0900522 /**
Aga Wronska0614c162016-03-31 15:08:43 -0700523 * Logs keyboard shortcut actions. Since keyboard shortcuts have their corresponding menu items,
524 * they are identified by menu item resource id for convenience.
525 * @param context
526 * @param keyCode
527 */
528 public static void logKeyboardAction(Context context, int keyCode) {
529 @KeyboardAction int keyboardAction = ACTION_KEYBOARD_OTHER;
530 switch (keyCode) {
531 case KeyEvent.KEYCODE_V:
532 keyboardAction = ACTION_KEYBOARD_PASTE;
533 break;
534 case KeyEvent.KEYCODE_C:
535 keyboardAction = ACTION_KEYBOARD_COPY;
536 break;
537 case KeyEvent.KEYCODE_FORWARD_DEL:
538 keyboardAction = ACTION_KEYBOARD_DELETE;
539 break;
540 case KeyEvent.KEYCODE_A:
541 keyboardAction = ACTION_KEYBOARD_SELECT_ALL;
542 break;
543 case KeyEvent.KEYCODE_DEL:
544 keyboardAction = ACTION_KEYBOARD_BACK;
545 break;
546 case KeyEvent.KEYCODE_TAB:
547 keyboardAction = ACTION_KEYBOARD_SWITCH_FOCUS;
548 break;
549 default:
550 break;
551 }
552 logHistogram(context, COUNT_KEYBOARD_ACTION, keyboardAction);
553 }
554
555 /**
Daichi Hirono7352c552016-03-25 19:04:39 +0900556 * Logs startup time in milliseconds.
557 * @param context
558 * @param startupMs Startup time in milliseconds.
559 */
560 public static void logStartupMs(Context context, int startupMs) {
561 logHistogram(context, COUNT_STARTUP_MS, startupMs);
562 }
563
Aga Wronskac573bec2016-03-31 16:37:40 -0700564 /**
565 * Logs a drag and drop action. Call this when the user drops the content triggering copy.
566 * operation.
567 *
568 * @param context
569 */
570 public static void logDragNDrop(Context context) {
571 logCount(context, COUNT_DRAG_N_DROP);
572 }
573
574 /**
575 * Logs a search. Call this when the search operation is finished.
576 *
577 * @param context
578 */
579 public static void logSearch(Context context) {
580 logCount(context, COUNT_SEARCH);
581 }
582
Ben Kwad5b2af12016-01-28 16:39:57 -0800583 private static void logInterProviderFileOps(
584 Context context,
585 String histogram,
586 DocumentInfo dst,
587 @OpType int operationType) {
588 if (operationType == FileOperationService.OPERATION_DELETE) {
589 logHistogram(context, histogram, FILEOP_DELETE);
590 } else {
Steve McKaya1f76802016-02-25 13:34:03 -0800591 assert(dst != null);
Ben Kwad5b2af12016-01-28 16:39:57 -0800592 @Provider int providerType =
593 isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL;
594 logHistogram(context, histogram, getOpCode(operationType, providerType));
595 }
596 }
597
598 private static void logIntraProviderFileOps(
599 Context context, String authority, @OpType int operationType) {
600 // Find the right histogram to log to, then log the operation.
601 String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL;
602 logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA));
603 }
604
Felipe Leme3e166b22016-02-24 10:17:41 -0800605 // Types for logInvalidScopedAccessRequest
606 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS =
607 "scoped_directory_access_invalid_args";
608 public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY =
609 "scoped_directory_access_invalid_dir";
610 public static final String SCOPED_DIRECTORY_ACCESS_ERROR =
611 "scoped_directory_access_error";
612
613 @StringDef(value = {
614 SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS,
615 SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY,
616 SCOPED_DIRECTORY_ACCESS_ERROR
617 })
618 @Retention(RetentionPolicy.SOURCE)
619 public @interface InvalidScopedAccess{}
620
621 public static void logInvalidScopedAccessRequest(Context context,
622 @InvalidScopedAccess String type) {
623 MetricsLogger.count(context, type, 1);
624 switch (type) {
625 case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS:
626 case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY:
627 case SCOPED_DIRECTORY_ACCESS_ERROR:
628 MetricsLogger.count(context, type, 1);
629 break;
630 default:
631 Log.wtf(TAG, "invalid InvalidScopedAccess: " + type);
632 }
633 }
634
635 // Types for logValidScopedAccessRequest
636 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0;
637 public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1;
638 public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2;
Felipe Lemeadccb992016-03-09 17:40:49 -0800639 public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3;
640 public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4;
Felipe Leme3e166b22016-02-24 10:17:41 -0800641
642 @IntDef(flag = true, value = {
643 SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED,
644 SCOPED_DIRECTORY_ACCESS_GRANTED,
Felipe Lemeadccb992016-03-09 17:40:49 -0800645 SCOPED_DIRECTORY_ACCESS_DENIED,
646 SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST,
647 SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED
Felipe Leme3e166b22016-02-24 10:17:41 -0800648 })
649 @Retention(RetentionPolicy.SOURCE)
650 public @interface ScopedAccessGrant {}
651
652 public static void logValidScopedAccessRequest(Activity activity, String directory,
653 @ScopedAccessGrant int type) {
654 int index = -1;
Felipe Lemedb892b82016-03-17 18:56:20 -0700655 if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) {
656 index = -2;
657 } else {
658 for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) {
659 if (STANDARD_DIRECTORIES[i].equals(directory)) {
660 index = i;
661 break;
662 }
Felipe Leme3e166b22016-02-24 10:17:41 -0800663 }
664 }
665 final String packageName = activity.getCallingPackage();
666 switch (type) {
667 case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED:
Felipe Lemeadccb992016-03-09 17:40:49 -0800668 MetricsLogger.action(activity, MetricsEvent
669 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName);
670 MetricsLogger.action(activity, MetricsEvent
671 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index);
Felipe Leme3e166b22016-02-24 10:17:41 -0800672 break;
673 case SCOPED_DIRECTORY_ACCESS_GRANTED:
Felipe Lemeadccb992016-03-09 17:40:49 -0800674 MetricsLogger.action(activity, MetricsEvent
675 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName);
676 MetricsLogger.action(activity, MetricsEvent
677 .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index);
Felipe Leme3e166b22016-02-24 10:17:41 -0800678 break;
679 case SCOPED_DIRECTORY_ACCESS_DENIED:
Felipe Lemeadccb992016-03-09 17:40:49 -0800680 MetricsLogger.action(activity, MetricsEvent
681 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName);
682 MetricsLogger.action(activity, MetricsEvent
683 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index);
684 break;
685 case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST:
686 MetricsLogger.action(activity, MetricsEvent
687 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName);
688 MetricsLogger.action(activity, MetricsEvent
689 .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index);
690 break;
691 case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED:
692 MetricsLogger.action(activity, MetricsEvent
693 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName);
694 MetricsLogger.action(activity, MetricsEvent
695 .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index);
Felipe Leme3e166b22016-02-24 10:17:41 -0800696 break;
697 default:
698 Log.wtf(TAG, "invalid ScopedAccessGrant: " + type);
699 }
700 }
701
Ben Kwad5b2af12016-01-28 16:39:57 -0800702 /**
Aga Wronskacf966ae2016-03-30 13:55:19 -0700703 * Logs menu action that was selected by user.
704 * @param context
705 * @param id Resource id of the menu item.
706 */
707 public static void logMenuAction(Context context, int id) {
708 @MenuAction int menuAction = ACTION_MENU_OTHER;
709 switch (id) {
710 case R.id.menu_grid:
711 menuAction = ACTION_MENU_GRID;
712 break;
713 case R.id.menu_list:
714 menuAction = ACTION_MENU_LIST;
715 break;
716 case R.id.menu_sort:
717 menuAction = ACTION_MENU_SORT;
718 break;
719 case R.id.menu_sort_name:
720 menuAction = ACTION_MENU_SORT_NAME;
721 break;
722 case R.id.menu_sort_date:
723 menuAction = ACTION_MENU_SORT_DATE;
724 break;
725 case R.id.menu_sort_size:
726 menuAction = ACTION_MENU_SORT_SIZE;
727 break;
728 case R.id.menu_search:
729 menuAction = ACTION_MENU_SEARCH;
730 break;
731 case R.id.menu_file_size:
732 menuAction = ACTION_MENU_SHOW_SIZE;
733 break;
734 case R.id.menu_settings:
735 menuAction = ACTION_MENU_SETTINGS;
736 break;
737 case R.id.menu_copy_to:
738 menuAction = ACTION_MENU_COPY_TO;
739 break;
740 case R.id.menu_move_to:
741 menuAction = ACTION_MENU_MOVE_TO;
742 break;
743 case R.id.menu_delete:
744 menuAction = ACTION_MENU_DELETE;
745 break;
746 case R.id.menu_rename:
747 menuAction = ACTION_MENU_RENAME;
748 break;
749 case R.id.menu_create_dir:
750 menuAction = ACTION_MENU_CREATE_DIR;
751 break;
752 case R.id.menu_select_all:
753 menuAction = ACTION_MENU_SELECT_ALL;
754 break;
755 case R.id.menu_share:
756 menuAction = ACTION_MENU_SHARE;
757 break;
758 case R.id.menu_open:
759 menuAction = ACTION_MENU_OPEN;
760 break;
761 case R.id.menu_advanced:
762 menuAction = ACTION_MENU_ADVANCED;
763 break;
764 default:
765 break;
766 }
767 logHistogram(context, COUNT_MENU_ACTION, menuAction);
768 }
769
770 /**
Ben Kwa72379982016-01-26 11:50:03 -0800771 * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
772 *
773 * @param context
774 * @param name The counter to increment.
775 */
776 private static void logCount(Context context, String name) {
777 if (DEBUG) Log.d(TAG, name + ": " + 1);
778 MetricsLogger.count(context, name, 1);
779 }
780
781 /**
782 * Internal method for making a MetricsLogger.histogram call.
783 *
784 * @param context
785 * @param name The name of the histogram.
786 * @param bucket The bucket to increment.
787 */
Aga Wronska973168c2016-03-28 17:27:02 -0700788 private static void logHistogram(Context context, String name, @ActionType int bucket) {
Ben Kwa72379982016-01-26 11:50:03 -0800789 if (DEBUG) Log.d(TAG, name + ": " + bucket);
790 MetricsLogger.histogram(context, name, bucket);
791 }
792
793 /**
794 * Generates an integer identifying the given root. For privacy, this function only recognizes a
795 * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
796 * a single ROOT_OTHER bucket.
797 */
Ben Kwad5b2af12016-01-28 16:39:57 -0800798 private static @Root int sanitizeRoot(Uri uri) {
Tomasz Mikolajewskicd270152016-02-01 12:01:14 +0900799 if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) {
Ben Kwa72379982016-01-26 11:50:03 -0800800 return ROOT_NONE;
801 }
802
803 switch (uri.getAuthority()) {
804 case AUTHORITY_MEDIA:
805 switch (DocumentsContract.getRootId(uri)) {
806 case "audio_root":
807 return ROOT_AUDIO;
808 case "images_root":
809 return ROOT_IMAGES;
810 case "videos_root":
811 return ROOT_VIDEOS;
812 default:
813 return ROOT_OTHER;
814 }
815 case AUTHORITY_STORAGE:
816 if ("home".equals(DocumentsContract.getRootId(uri))) {
817 return ROOT_HOME;
818 } else {
819 return ROOT_DEVICE_STORAGE;
820 }
821 case AUTHORITY_DOWNLOADS:
822 return ROOT_DOWNLOADS;
Ben Kwada518c92016-01-28 18:15:07 -0800823 case AUTHORITY_MTP:
824 return ROOT_MTP;
Ben Kwa72379982016-01-26 11:50:03 -0800825 default:
826 return ROOT_OTHER;
827 }
828 }
829
830 /** @see #sanitizeRoot(Uri) */
Ben Kwad5b2af12016-01-28 16:39:57 -0800831 private static @Root int sanitizeRoot(RootInfo root) {
Ben Kwa72379982016-01-26 11:50:03 -0800832 if (root.isRecents()) {
833 // Recents root is special and only identifiable via this method call. Other roots are
834 // identified by URI.
835 return ROOT_RECENTS;
836 } else {
837 return sanitizeRoot(root.getUri());
838 }
839 }
840
841 /** @see #sanitizeRoot(Uri) */
Ben Kwad5b2af12016-01-28 16:39:57 -0800842 private static @Root int sanitizeRoot(ResolveInfo info) {
Ben Kwa72379982016-01-26 11:50:03 -0800843 // Log all apps under a single bucket in the roots histogram.
844 return ROOT_THIRD_PARTY_APP;
845 }
846
847 /**
848 * Generates an int identifying a mime type. For privacy, this function only recognizes a small
849 * set of hard-coded types. For any other type, this function returns "other".
850 *
851 * @param mimeType
852 * @return
853 */
Ben Kwad5b2af12016-01-28 16:39:57 -0800854 private static @Mime int sanitizeMime(String mimeType) {
Ben Kwa72379982016-01-26 11:50:03 -0800855 if (mimeType == null) {
856 return MIME_NONE;
857 } else if ("*/*".equals(mimeType)) {
858 return MIME_ANY;
859 } else {
860 String type = mimeType.substring(0, mimeType.indexOf('/'));
861 switch (type) {
862 case "application":
863 return MIME_APPLICATION;
864 case "audio":
865 return MIME_AUDIO;
866 case "image":
867 return MIME_IMAGE;
868 case "message":
869 return MIME_MESSAGE;
870 case "multipart":
871 return MIME_MULTIPART;
872 case "text":
873 return MIME_TEXT;
874 case "video":
875 return MIME_VIDEO;
876 }
877 }
878 // Bucket all other types into one bucket.
879 return MIME_OTHER;
880 }
Ben Kwad5b2af12016-01-28 16:39:57 -0800881
882 private static boolean isSystemProvider(String authority) {
883 switch (authority) {
884 case AUTHORITY_MEDIA:
885 case AUTHORITY_STORAGE:
886 case AUTHORITY_DOWNLOADS:
887 return true;
888 default:
889 return false;
890 }
891 }
892
893 /**
894 * @param operation
895 * @param providerType
896 * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
897 * combination.
898 */
899 private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
900 switch (operation) {
901 case FileOperationService.OPERATION_COPY:
902 switch (providerType) {
903 case PROVIDER_INTRA:
904 return FILEOP_COPY_INTRA_PROVIDER;
905 case PROVIDER_SYSTEM:
906 return FILEOP_COPY_SYSTEM_PROVIDER;
907 case PROVIDER_EXTERNAL:
908 return FILEOP_COPY_EXTERNAL_PROVIDER;
909 }
910 case FileOperationService.OPERATION_MOVE:
911 switch (providerType) {
912 case PROVIDER_INTRA:
913 return FILEOP_MOVE_INTRA_PROVIDER;
914 case PROVIDER_SYSTEM:
915 return FILEOP_MOVE_SYSTEM_PROVIDER;
916 case PROVIDER_EXTERNAL:
917 return FILEOP_MOVE_EXTERNAL_PROVIDER;
918 }
919 case FileOperationService.OPERATION_DELETE:
920 return FILEOP_DELETE;
921 default:
922 Log.w(TAG, "Unrecognized operation type when logging a file operation");
923 return FILEOP_OTHER;
924 }
925 }
926
927 /**
Ben Kwaa87e9f92016-02-17 16:06:22 -0800928 * Maps FileOperationService OpType values, to MetricsOpType values.
929 */
930 private static @MetricsOpType int toMetricsOpType(@OpType int operation) {
931 switch (operation) {
932 case FileOperationService.OPERATION_COPY:
933 return OPERATION_COPY;
934 case FileOperationService.OPERATION_MOVE:
935 return OPERATION_MOVE;
936 case FileOperationService.OPERATION_DELETE:
937 return OPERATION_DELETE;
938 case FileOperationService.OPERATION_UNKNOWN:
939 default:
940 return OPERATION_UNKNOWN;
941 }
942 }
943
944 private static @MetricsAction int toMetricsAction(int action) {
945 switch(action) {
946 case State.ACTION_OPEN:
947 return ACTION_OPEN;
948 case State.ACTION_CREATE:
949 return ACTION_CREATE;
950 case State.ACTION_GET_CONTENT:
951 return ACTION_GET_CONTENT;
952 case State.ACTION_OPEN_TREE:
953 return ACTION_OPEN_TREE;
Ben Kwaa87e9f92016-02-17 16:06:22 -0800954 case State.ACTION_BROWSE:
955 return ACTION_BROWSE;
956 case State.ACTION_PICK_COPY_DESTINATION:
957 return ACTION_PICK_COPY_DESTINATION;
958 default:
959 return ACTION_OTHER;
960 }
961 }
962
963 /**
Ben Kwad5b2af12016-01-28 16:39:57 -0800964 * Count the given src documents and provide a tally of how many come from the same provider as
965 * the dst document (if a dst is provided), how many come from system providers, and how many
966 * come from external 3rd-party providers.
967 */
968 private static ProviderCounts countProviders(
969 List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
970 ProviderCounts counts = new ProviderCounts();
971 for (DocumentInfo doc: srcs) {
972 if (dst != null && doc.authority.equals(dst.authority)) {
973 counts.intraProvider++;
974 } else if (isSystemProvider(doc.authority)){
975 counts.systemProvider++;
976 } else {
977 counts.externalProvider++;
978 }
979 }
980 return counts;
981 }
982
983 private static class ProviderCounts {
984 int intraProvider;
985 int systemProvider;
986 int externalProvider;
987 }
Ben Kwa72379982016-01-26 11:50:03 -0800988}