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