Home | History | Annotate | Download | only in documentsui
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.documentsui;
     18 
     19 import static android.os.Environment.STANDARD_DIRECTORIES;
     20 import static com.android.documentsui.Shared.DEBUG;
     21 
     22 import android.annotation.IntDef;
     23 import android.annotation.Nullable;
     24 import android.annotation.StringDef;
     25 import android.app.Activity;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.pm.ResolveInfo;
     29 import android.net.Uri;
     30 import android.provider.DocumentsContract;
     31 import android.util.Log;
     32 import android.view.KeyEvent;
     33 
     34 import com.android.documentsui.State.ActionType;
     35 import com.android.documentsui.model.DocumentInfo;
     36 import com.android.documentsui.model.RootInfo;
     37 import com.android.documentsui.services.FileOperationService;
     38 import com.android.documentsui.services.FileOperationService.OpType;
     39 import com.android.internal.logging.MetricsLogger;
     40 import com.android.internal.logging.MetricsProto.MetricsEvent;
     41 
     42 import java.lang.annotation.Retention;
     43 import java.lang.annotation.RetentionPolicy;
     44 import java.util.List;
     45 
     46 /** @hide */
     47 public 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";
     55     private static final String AUTHORITY_MTP = "com.android.mtp.documents";
     56 
     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";
     64     @Deprecated private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
     65     @Deprecated private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
     66     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";
     69     private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
     70     private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened";
     71     private static final String COUNT_USER_ACTION = "docsui_menu_action";
     72 
     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
     75     // #getSanitizedRootIndex}). Apps are also bucketed in this histogram.
     76     // Do not change or rearrange these values, that will break historical data. Only add to the end
     77     // of the list.
     78     // 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;
     89     // Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they
     90     // are logged analogously to roots.
     91     private static final int ROOT_THIRD_PARTY_APP = 100;
     92 
     93     @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,
    103             ROOT_MTP,
    104             ROOT_THIRD_PARTY_APP
    105     })
    106     @Retention(RetentionPolicy.SOURCE)
    107     public @interface Root {}
    108 
    109     // Indices for bucketing mime types.
    110     // 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
    123 
    124     @IntDef(flag = true, value = {
    125             MIME_NONE,
    126             MIME_ANY,
    127             MIME_APPLICATION,
    128             MIME_AUDIO,
    129             MIME_IMAGE,
    130             MIME_MESSAGE,
    131             MIME_MULTIPART,
    132             MIME_TEXT,
    133             MIME_VIDEO,
    134             MIME_OTHER
    135     })
    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.
    141     // 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;
    152     private static final int FILEOP_RENAME = 9;
    153     private static final int FILEOP_CREATE_DIR = 10;
    154     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;
    158     private static final int FILEOP_RENAME_ERROR = 104;
    159     private static final int FILEOP_CREATE_DIR_ERROR = 105;
    160 
    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,
    170             FILEOP_RENAME,
    171             FILEOP_CREATE_DIR,
    172             FILEOP_OTHER_ERROR,
    173             FILEOP_COPY_ERROR,
    174             FILEOP_MOVE_ERROR,
    175             FILEOP_DELETE_ERROR,
    176             FILEOP_RENAME_ERROR,
    177             FILEOP_CREATE_DIR_ERROR
    178     })
    179     @Retention(RetentionPolicy.SOURCE)
    180     public @interface FileOp {}
    181 
    182     // 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 
    201     // 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 
    215     // 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.
    218     // 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.
    221     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;
    246 
    247     @IntDef(flag = false, value = {
    248             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
    273     })
    274     @Retention(RetentionPolicy.SOURCE)
    275     public @interface UserAction {}
    276 
    277     // Codes representing different menu actions. These are used for bucketing stats in the
    278     // COUNT_MENU_ACTION histogram.
    279     // 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;
    287     @Deprecated private static final int ACTION_MANAGE = 6;
    288     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 
    304     // 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 
    319     /**
    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.
    328         logHistogram(context, COUNT_LAUNCH_ACTION, toMetricsAction(state.action));
    329         // 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;
    341             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     /**
    370      * 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     /**
    384      * 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     /**
    414      * 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     /**
    436      * 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     /**
    468      * 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) {
    475         logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR_ERROR);
    476     }
    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     /**
    490      * Logs the cancellation of a file operation.  Call this when a Job is canceled.
    491      * @param context
    492      * @param operationType
    493      */
    494     public static void logFileOperationCancelled(Context context, @OpType int operationType) {
    495         logHistogram(context, COUNT_FILEOP_CANCELED, toMetricsOpType(operationType));
    496     }
    497 
    498     /**
    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 
    507     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 {
    515             assert(dst != null);
    516             @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 
    529     // Types for logInvalidScopedAccessRequest
    530     public static final String SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS =
    531             "docsui_scoped_directory_access_invalid_args";
    532     public static final String SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY =
    533             "docsui_scoped_directory_access_invalid_dir";
    534     public static final String SCOPED_DIRECTORY_ACCESS_ERROR =
    535             "docsui_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         switch (type) {
    548             case SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS:
    549             case SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY:
    550             case SCOPED_DIRECTORY_ACCESS_ERROR:
    551                 logCount(context, type);
    552                 break;
    553             default:
    554                 Log.wtf(TAG, "invalid InvalidScopedAccess: " + type);
    555         }
    556     }
    557 
    558     // Types for logValidScopedAccessRequest
    559     public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED = 0;
    560     public static final int SCOPED_DIRECTORY_ACCESS_GRANTED = 1;
    561     public static final int SCOPED_DIRECTORY_ACCESS_DENIED = 2;
    562     public static final int SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST = 3;
    563     public static final int SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED = 4;
    564 
    565     @IntDef(flag = true, value = {
    566             SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED,
    567             SCOPED_DIRECTORY_ACCESS_GRANTED,
    568             SCOPED_DIRECTORY_ACCESS_DENIED,
    569             SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST,
    570             SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED
    571     })
    572     @Retention(RetentionPolicy.SOURCE)
    573     public @interface ScopedAccessGrant {}
    574 
    575     public static void logValidScopedAccessRequest(Activity activity, String directory,
    576             @ScopedAccessGrant int type) {
    577         int index = -1;
    578         if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) {
    579             index = -2;
    580         } else {
    581             for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) {
    582                 if (STANDARD_DIRECTORIES[i].equals(directory)) {
    583                     index = i;
    584                     break;
    585                 }
    586             }
    587         }
    588         final String packageName = activity.getCallingPackage();
    589         switch (type) {
    590             case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED:
    591                 MetricsLogger.action(activity, MetricsEvent
    592                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_PACKAGE, packageName);
    593                 MetricsLogger.action(activity, MetricsEvent
    594                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER, index);
    595                 break;
    596             case SCOPED_DIRECTORY_ACCESS_GRANTED:
    597                 MetricsLogger.action(activity, MetricsEvent
    598                         .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_PACKAGE, packageName);
    599                 MetricsLogger.action(activity, MetricsEvent
    600                         .ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER, index);
    601                 break;
    602             case SCOPED_DIRECTORY_ACCESS_DENIED:
    603                 MetricsLogger.action(activity, MetricsEvent
    604                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_PACKAGE, packageName);
    605                 MetricsLogger.action(activity, MetricsEvent
    606                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER, index);
    607                 break;
    608             case SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST:
    609                 MetricsLogger.action(activity, MetricsEvent
    610                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE, packageName);
    611                 MetricsLogger.action(activity, MetricsEvent
    612                         .ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER, index);
    613                 break;
    614             case SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED:
    615                 MetricsLogger.action(activity, MetricsEvent
    616                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_PACKAGE, packageName);
    617                 MetricsLogger.action(activity, MetricsEvent
    618                         .ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER, index);
    619                 break;
    620             default:
    621                 Log.wtf(TAG, "invalid ScopedAccessGrant: " + type);
    622         }
    623     }
    624 
    625     /**
    626      * Logs the action that was started by user.
    627      * @param context
    628      * @param userAction
    629      */
    630     public static void logUserAction(Context context, @UserAction int userAction) {
    631         logHistogram(context, COUNT_USER_ACTION, userAction);
    632     }
    633 
    634     /**
    635      * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
    636      *
    637      * @param context
    638      * @param name The counter to increment.
    639      */
    640     private static void logCount(Context context, String name) {
    641         if (DEBUG) Log.d(TAG, name + ": " + 1);
    642         MetricsLogger.count(context, name, 1);
    643     }
    644 
    645     /**
    646      * Internal method for making a MetricsLogger.histogram call.
    647      *
    648      * @param context
    649      * @param name The name of the histogram.
    650      * @param bucket The bucket to increment.
    651      */
    652     private static void logHistogram(Context context, String name, @ActionType int bucket) {
    653         if (DEBUG) Log.d(TAG, name + ": " + bucket);
    654         MetricsLogger.histogram(context, name, bucket);
    655     }
    656 
    657     /**
    658      * Generates an integer identifying the given root. For privacy, this function only recognizes a
    659      * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
    660      * a single ROOT_OTHER bucket.
    661      */
    662     private static @Root int sanitizeRoot(Uri uri) {
    663         if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) {
    664             return ROOT_NONE;
    665         }
    666 
    667         switch (uri.getAuthority()) {
    668             case AUTHORITY_MEDIA:
    669                 switch (DocumentsContract.getRootId(uri)) {
    670                     case "audio_root":
    671                         return ROOT_AUDIO;
    672                     case "images_root":
    673                         return ROOT_IMAGES;
    674                     case "videos_root":
    675                         return ROOT_VIDEOS;
    676                     default:
    677                         return ROOT_OTHER;
    678                 }
    679             case AUTHORITY_STORAGE:
    680                 if ("home".equals(DocumentsContract.getRootId(uri))) {
    681                     return ROOT_HOME;
    682                 } else {
    683                     return ROOT_DEVICE_STORAGE;
    684                 }
    685             case AUTHORITY_DOWNLOADS:
    686                 return ROOT_DOWNLOADS;
    687             case AUTHORITY_MTP:
    688                 return ROOT_MTP;
    689             default:
    690                 return ROOT_OTHER;
    691         }
    692     }
    693 
    694     /** @see #sanitizeRoot(Uri) */
    695     private static @Root int sanitizeRoot(RootInfo root) {
    696         if (root.isRecents()) {
    697             // Recents root is special and only identifiable via this method call. Other roots are
    698             // identified by URI.
    699             return ROOT_RECENTS;
    700         } else {
    701             return sanitizeRoot(root.getUri());
    702         }
    703     }
    704 
    705     /** @see #sanitizeRoot(Uri) */
    706     private static @Root int sanitizeRoot(ResolveInfo info) {
    707         // Log all apps under a single bucket in the roots histogram.
    708         return ROOT_THIRD_PARTY_APP;
    709     }
    710 
    711     /**
    712      * Generates an int identifying a mime type. For privacy, this function only recognizes a small
    713      * set of hard-coded types. For any other type, this function returns "other".
    714      *
    715      * @param mimeType
    716      * @return
    717      */
    718     private static @Mime int sanitizeMime(String mimeType) {
    719         if (mimeType == null) {
    720             return MIME_NONE;
    721         } else if ("*/*".equals(mimeType)) {
    722             return MIME_ANY;
    723         } else {
    724             String type = mimeType.substring(0, mimeType.indexOf('/'));
    725             switch (type) {
    726                 case "application":
    727                     return MIME_APPLICATION;
    728                 case "audio":
    729                     return MIME_AUDIO;
    730                 case "image":
    731                     return MIME_IMAGE;
    732                 case "message":
    733                     return MIME_MESSAGE;
    734                 case "multipart":
    735                     return MIME_MULTIPART;
    736                 case "text":
    737                     return MIME_TEXT;
    738                 case "video":
    739                     return MIME_VIDEO;
    740             }
    741         }
    742         // Bucket all other types into one bucket.
    743         return MIME_OTHER;
    744     }
    745 
    746     private static boolean isSystemProvider(String authority) {
    747         switch (authority) {
    748             case AUTHORITY_MEDIA:
    749             case AUTHORITY_STORAGE:
    750             case AUTHORITY_DOWNLOADS:
    751                 return true;
    752             default:
    753                 return false;
    754         }
    755     }
    756 
    757     /**
    758      * @param operation
    759      * @param providerType
    760      * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
    761      *         combination.
    762      */
    763     private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
    764         switch (operation) {
    765             case FileOperationService.OPERATION_COPY:
    766                 switch (providerType) {
    767                     case PROVIDER_INTRA:
    768                         return FILEOP_COPY_INTRA_PROVIDER;
    769                     case PROVIDER_SYSTEM:
    770                         return FILEOP_COPY_SYSTEM_PROVIDER;
    771                     case PROVIDER_EXTERNAL:
    772                         return FILEOP_COPY_EXTERNAL_PROVIDER;
    773                 }
    774             case FileOperationService.OPERATION_MOVE:
    775                 switch (providerType) {
    776                     case PROVIDER_INTRA:
    777                         return FILEOP_MOVE_INTRA_PROVIDER;
    778                     case PROVIDER_SYSTEM:
    779                         return FILEOP_MOVE_SYSTEM_PROVIDER;
    780                     case PROVIDER_EXTERNAL:
    781                         return FILEOP_MOVE_EXTERNAL_PROVIDER;
    782                 }
    783             case FileOperationService.OPERATION_DELETE:
    784                 return FILEOP_DELETE;
    785             default:
    786                 Log.w(TAG, "Unrecognized operation type when logging a file operation");
    787                 return FILEOP_OTHER;
    788         }
    789     }
    790 
    791     /**
    792      * Maps FileOperationService OpType values, to MetricsOpType values.
    793      */
    794     private static @MetricsOpType int toMetricsOpType(@OpType int operation) {
    795         switch (operation) {
    796             case FileOperationService.OPERATION_COPY:
    797                 return OPERATION_COPY;
    798             case FileOperationService.OPERATION_MOVE:
    799                 return OPERATION_MOVE;
    800             case FileOperationService.OPERATION_DELETE:
    801                 return OPERATION_DELETE;
    802             case FileOperationService.OPERATION_UNKNOWN:
    803             default:
    804                 return OPERATION_UNKNOWN;
    805         }
    806     }
    807 
    808     private static @MetricsAction int toMetricsAction(int action) {
    809         switch(action) {
    810             case State.ACTION_OPEN:
    811                 return ACTION_OPEN;
    812             case State.ACTION_CREATE:
    813                 return ACTION_CREATE;
    814             case State.ACTION_GET_CONTENT:
    815                 return ACTION_GET_CONTENT;
    816             case State.ACTION_OPEN_TREE:
    817                 return ACTION_OPEN_TREE;
    818             case State.ACTION_BROWSE:
    819                 return ACTION_BROWSE;
    820             case State.ACTION_PICK_COPY_DESTINATION:
    821                 return ACTION_PICK_COPY_DESTINATION;
    822             default:
    823                 return ACTION_OTHER;
    824         }
    825     }
    826 
    827     /**
    828      * Count the given src documents and provide a tally of how many come from the same provider as
    829      * the dst document (if a dst is provided), how many come from system providers, and how many
    830      * come from external 3rd-party providers.
    831      */
    832     private static ProviderCounts countProviders(
    833             List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
    834         ProviderCounts counts = new ProviderCounts();
    835         for (DocumentInfo doc: srcs) {
    836             if (dst != null && doc.authority.equals(dst.authority)) {
    837                 counts.intraProvider++;
    838             } else if (isSystemProvider(doc.authority)){
    839                 counts.systemProvider++;
    840             } else {
    841                 counts.externalProvider++;
    842             }
    843         }
    844         return counts;
    845     }
    846 
    847     private static class ProviderCounts {
    848         int intraProvider;
    849         int systemProvider;
    850         int externalProvider;
    851     }
    852 }
    853