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 com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow;
     20 import static com.android.documentsui.base.SharedMinimal.DEBUG;
     21 
     22 import android.annotation.IntDef;
     23 import android.annotation.Nullable;
     24 import android.content.ContentProviderClient;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.pm.ResolveInfo;
     28 import android.net.Uri;
     29 import android.os.RemoteException;
     30 import android.provider.DocumentsContract;
     31 import android.provider.DocumentsContract.Path;
     32 import android.provider.DocumentsProvider;
     33 import android.util.Log;
     34 
     35 import com.android.documentsui.base.DocumentInfo;
     36 import com.android.documentsui.base.Providers;
     37 import com.android.documentsui.base.RootInfo;
     38 import com.android.documentsui.base.State;
     39 import com.android.documentsui.base.State.ActionType;
     40 import com.android.documentsui.files.LauncherActivity;
     41 import com.android.documentsui.roots.ProvidersAccess;
     42 import com.android.documentsui.services.FileOperationService;
     43 import com.android.documentsui.services.FileOperationService.OpType;
     44 import com.android.internal.logging.MetricsLogger;
     45 
     46 import java.lang.annotation.Retention;
     47 import java.lang.annotation.RetentionPolicy;
     48 import java.util.List;
     49 
     50 /**
     51  * Methods for logging metrics.
     52  */
     53 public final class Metrics {
     54     private static final String TAG = "Metrics";
     55 
     56     // These strings have to be whitelisted in tron. Do not change them.
     57     private static final String COUNT_LAUNCH_ACTION = "docsui_launch_action";
     58     private static final String COUNT_ROOT_VISITED_IN_MANAGER
     59             = "docsui_root_visited_in_manager";
     60     private static final String COUNT_ROOT_VISITED_IN_PICKER
     61             = "docsui_root_visited_in_picker";
     62     private static final String COUNT_OPEN_MIME = "docsui_open_mime";
     63     private static final String COUNT_CREATE_MIME = "docsui_create_mime";
     64     private static final String COUNT_GET_CONTENT_MIME = "docsui_get_content_mime";
     65     private static final String COUNT_BROWSE_ROOT = "docsui_browse_root";
     66     @Deprecated private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
     67     @Deprecated private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
     68     private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system";
     69     private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
     70     private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
     71     private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
     72     @Deprecated private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened";
     73     private static final String COUNT_USER_ACTION = "docsui_menu_action";
     74     private static final String COUNT_BROWSE_AT_LOCATION = "docsui_browse_at_location";
     75     private static final String COUNT_CREATE_AT_LOCATION = "docsui_create_at_location";
     76     private static final String COUNT_OPEN_AT_LOCATION = "docsui_open_at_location";
     77     private static final String COUNT_GET_CONTENT_AT_LOCATION = "docsui_get_content_at_location";
     78     private static final String COUNT_MEDIA_FILEOP_FAILURE = "docsui_media_fileop_failure";
     79     private static final String COUNT_DOWNLOADS_FILEOP_FAILURE = "docsui_downloads_fileop_failure";
     80     private static final String COUNT_INTERNAL_STORAGE_FILEOP_FAILURE
     81             = "docsui_internal_storage_fileop_failure";
     82     private static final String COUNT_EXTERNAL_STORAGE_FILEOP_FAILURE
     83             = "docsui_external_storage_fileop_failure";
     84     private static final String COUNT_MTP_FILEOP_FAILURE = "docsui_mtp_fileop_failure";
     85     private static final String COUNT_OTHER_FILEOP_FAILURE = "docsui_other_fileop_failure";
     86     private static final String COUNT_FILE_COPIED = "docsui_file_copied";
     87     private static final String COUNT_FILE_MOVED = "docsui_file_moved";
     88 
     89     // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
     90     // root that is not explicitly recognized by the Metrics code (see {@link
     91     // #getSanitizedRootIndex}). Apps are also bucketed in this histogram.
     92     // Do not change or rearrange these values, that will break historical data. Only add to the end
     93     // of the list.
     94     // Do not use negative numbers or zero; clearcut only handles positive integers.
     95     private static final int ROOT_NONE = 1;
     96     private static final int ROOT_OTHER = 2;
     97     private static final int ROOT_AUDIO = 3;
     98     private static final int ROOT_DEVICE_STORAGE = 4;
     99     private static final int ROOT_DOWNLOADS = 5;
    100     private static final int ROOT_HOME = 6;
    101     private static final int ROOT_IMAGES = 7;
    102     private static final int ROOT_RECENTS = 8;
    103     private static final int ROOT_VIDEOS = 9;
    104     private static final int ROOT_MTP = 10;
    105     // Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they
    106     // are logged analogously to roots.
    107     private static final int ROOT_THIRD_PARTY_APP = 100;
    108 
    109     @IntDef(flag = true, value = {
    110             ROOT_NONE,
    111             ROOT_OTHER,
    112             ROOT_AUDIO,
    113             ROOT_DEVICE_STORAGE,
    114             ROOT_DOWNLOADS,
    115             ROOT_HOME,
    116             ROOT_IMAGES,
    117             ROOT_RECENTS,
    118             ROOT_VIDEOS,
    119             ROOT_MTP,
    120             ROOT_THIRD_PARTY_APP
    121     })
    122     @Retention(RetentionPolicy.SOURCE)
    123     public @interface Root {}
    124 
    125     // Indices for bucketing mime types.
    126     // Do not change or rearrange these values, that will break historical data. Only add to the end
    127     // of the list.
    128     // Do not use negative numbers or zero; clearcut only handles positive integers.
    129     private static final int MIME_NONE = 1; // null mime
    130     private static final int MIME_ANY = 2; // */*
    131     private static final int MIME_APPLICATION = 3; // application/*
    132     private static final int MIME_AUDIO = 4; // audio/*
    133     private static final int MIME_IMAGE = 5; // image/*
    134     private static final int MIME_MESSAGE = 6; // message/*
    135     private static final int MIME_MULTIPART = 7; // multipart/*
    136     private static final int MIME_TEXT = 8; // text/*
    137     private static final int MIME_VIDEO = 9; // video/*
    138     private static final int MIME_OTHER = 10; // anything not enumerated below
    139 
    140     @IntDef(flag = true, value = {
    141             MIME_NONE,
    142             MIME_ANY,
    143             MIME_APPLICATION,
    144             MIME_AUDIO,
    145             MIME_IMAGE,
    146             MIME_MESSAGE,
    147             MIME_MULTIPART,
    148             MIME_TEXT,
    149             MIME_VIDEO,
    150             MIME_OTHER
    151     })
    152     @Retention(RetentionPolicy.SOURCE)
    153     public @interface Mime {}
    154 
    155     public static final int FILES_SCOPE = 1;
    156     public static final int PICKER_SCOPE = 2;
    157 
    158     @IntDef({ FILES_SCOPE, PICKER_SCOPE })
    159     @Retention(RetentionPolicy.SOURCE)
    160     public @interface ContextScope {}
    161 
    162     // Codes representing different kinds of file operations. These are used for bucketing
    163     // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms.
    164     // Do not change or rearrange these values, that will break historical data. Only add to the
    165     // list.
    166     // Do not use negative numbers or zero; clearcut only handles positive integers.
    167     private static final int FILEOP_OTHER = 1; // any file operation not listed below
    168     private static final int FILEOP_COPY_INTRA_PROVIDER = 2; // Copy within a provider
    169     private static final int FILEOP_COPY_SYSTEM_PROVIDER = 3; // Copy to a system provider.
    170     private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 4; // Copy to a 3rd-party provider.
    171     private static final int FILEOP_MOVE_INTRA_PROVIDER = 5; // Move within a provider.
    172     private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 6; // Move to a system provider.
    173     private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 7; // Move to a 3rd-party provider.
    174     private static final int FILEOP_DELETE = 8;
    175     private static final int FILEOP_RENAME = 9;
    176     private static final int FILEOP_CREATE_DIR = 10;
    177     private static final int FILEOP_OTHER_ERROR = 100;
    178     private static final int FILEOP_DELETE_ERROR = 101;
    179     private static final int FILEOP_MOVE_ERROR = 102;
    180     private static final int FILEOP_COPY_ERROR = 103;
    181     private static final int FILEOP_RENAME_ERROR = 104;
    182     private static final int FILEOP_CREATE_DIR_ERROR = 105;
    183     private static final int FILEOP_COMPRESS_INTRA_PROVIDER = 106; // Compres within a provider
    184     private static final int FILEOP_COMPRESS_SYSTEM_PROVIDER = 107; // Compress to a system provider.
    185     private static final int FILEOP_COMPRESS_EXTERNAL_PROVIDER = 108; // Compress to a 3rd-party provider.
    186     private static final int FILEOP_EXTRACT_INTRA_PROVIDER = 109; // Extract within a provider
    187     private static final int FILEOP_EXTRACT_SYSTEM_PROVIDER = 110; // Extract to a system provider.
    188     private static final int FILEOP_EXTRACT_EXTERNAL_PROVIDER = 111; // Extract to a 3rd-party provider.
    189     private static final int FILEOP_COMPRESS_ERROR = 112;
    190     private static final int FILEOP_EXTRACT_ERROR = 113;
    191 
    192     @IntDef(flag = true, value = {
    193             FILEOP_OTHER,
    194             FILEOP_COPY_INTRA_PROVIDER,
    195             FILEOP_COPY_SYSTEM_PROVIDER,
    196             FILEOP_COPY_EXTERNAL_PROVIDER,
    197             FILEOP_MOVE_INTRA_PROVIDER,
    198             FILEOP_MOVE_SYSTEM_PROVIDER,
    199             FILEOP_MOVE_EXTERNAL_PROVIDER,
    200             FILEOP_DELETE,
    201             FILEOP_RENAME,
    202             FILEOP_CREATE_DIR,
    203             FILEOP_OTHER_ERROR,
    204             FILEOP_DELETE_ERROR,
    205             FILEOP_MOVE_ERROR,
    206             FILEOP_COPY_ERROR,
    207             FILEOP_RENAME_ERROR,
    208             FILEOP_CREATE_DIR_ERROR,
    209             FILEOP_COMPRESS_INTRA_PROVIDER,
    210             FILEOP_COMPRESS_SYSTEM_PROVIDER,
    211             FILEOP_COMPRESS_EXTERNAL_PROVIDER,
    212             FILEOP_EXTRACT_INTRA_PROVIDER,
    213             FILEOP_EXTRACT_SYSTEM_PROVIDER,
    214             FILEOP_EXTRACT_EXTERNAL_PROVIDER,
    215             FILEOP_COMPRESS_ERROR,
    216             FILEOP_EXTRACT_ERROR
    217     })
    218     @Retention(RetentionPolicy.SOURCE)
    219     public @interface FileOp {}
    220 
    221     // Codes representing different kinds of file operations. These are used for bucketing
    222     // operations in the COUNT_FILEOP_CANCELED histogram.
    223     // Do not change or rearrange these values, that will break historical data. Only add to the
    224     // list.
    225     // Do not use negative numbers or zero; clearcut only handles positive integers.
    226     private static final int OPERATION_UNKNOWN = 1;
    227     private static final int OPERATION_COPY = 2;
    228     private static final int OPERATION_MOVE = 3;
    229     private static final int OPERATION_DELETE = 4;
    230     private static final int OPERATION_COMPRESS = 5;
    231     private static final int OPERATION_EXTRACT = 6;
    232 
    233     @IntDef(flag = true, value = {
    234             OPERATION_UNKNOWN,
    235             OPERATION_COPY,
    236             OPERATION_MOVE,
    237             OPERATION_DELETE,
    238             OPERATION_COMPRESS,
    239             OPERATION_EXTRACT
    240     })
    241     @Retention(RetentionPolicy.SOURCE)
    242     public @interface MetricsOpType {}
    243 
    244     // Codes representing different provider types.  Used for sorting file operations when logging.
    245     private static final int PROVIDER_INTRA = 0;
    246     private static final int PROVIDER_SYSTEM = 1;
    247     private static final int PROVIDER_EXTERNAL = 2;
    248 
    249     @IntDef(flag = false, value = {
    250             PROVIDER_INTRA,
    251             PROVIDER_SYSTEM,
    252             PROVIDER_EXTERNAL
    253     })
    254     @Retention(RetentionPolicy.SOURCE)
    255     public @interface Provider {}
    256 
    257     // Codes representing different types of sub-fileops. These are used for bucketing fileop
    258     // failures in COUNT_*_FILEOP_FAILURE.
    259     public static final int SUBFILEOP_QUERY_DOCUMENT = 1;
    260     public static final int SUBFILEOP_QUERY_CHILDREN = 2;
    261     public static final int SUBFILEOP_OPEN_FILE = 3;
    262     public static final int SUBFILEOP_READ_FILE = 4;
    263     public static final int SUBFILEOP_CREATE_DOCUMENT = 5;
    264     public static final int SUBFILEOP_WRITE_FILE = 6;
    265     public static final int SUBFILEOP_DELETE_DOCUMENT = 7;
    266     public static final int SUBFILEOP_OBTAIN_STREAM_TYPE = 8;
    267     public static final int SUBFILEOP_QUICK_MOVE = 9;
    268     public static final int SUBFILEOP_QUICK_COPY = 10;
    269 
    270     @IntDef(flag = false, value = {
    271             SUBFILEOP_QUERY_DOCUMENT,
    272             SUBFILEOP_QUERY_CHILDREN,
    273             SUBFILEOP_OPEN_FILE,
    274             SUBFILEOP_READ_FILE,
    275             SUBFILEOP_CREATE_DOCUMENT,
    276             SUBFILEOP_WRITE_FILE,
    277             SUBFILEOP_DELETE_DOCUMENT,
    278             SUBFILEOP_OBTAIN_STREAM_TYPE,
    279             SUBFILEOP_QUICK_MOVE,
    280             SUBFILEOP_QUICK_COPY
    281     })
    282     @Retention(RetentionPolicy.SOURCE)
    283     public @interface SubFileOp {}
    284 
    285     // Codes representing different user actions. These are used for bucketing stats in the
    286     // COUNT_USER_ACTION histogram.
    287     // The historgram includes action triggered from menu or invoked by keyboard shortcut.
    288     // Do not change or rearrange these values, that will break historical data. Only add to the
    289     // list.
    290     // Do not use negative numbers or zero; clearcut only handles positive integers.
    291     public static final int USER_ACTION_OTHER = 1;
    292     public static final int USER_ACTION_GRID = 2;
    293     public static final int USER_ACTION_LIST = 3;
    294     public static final int USER_ACTION_SORT_NAME = 4;
    295     public static final int USER_ACTION_SORT_DATE = 5;
    296     public static final int USER_ACTION_SORT_SIZE = 6;
    297     public static final int USER_ACTION_SEARCH = 7;
    298     public static final int USER_ACTION_SHOW_SIZE = 8;
    299     public static final int USER_ACTION_HIDE_SIZE = 9;
    300     public static final int USER_ACTION_SETTINGS = 10;
    301     public static final int USER_ACTION_COPY_TO = 11;
    302     public static final int USER_ACTION_MOVE_TO = 12;
    303     public static final int USER_ACTION_DELETE = 13;
    304     public static final int USER_ACTION_RENAME = 14;
    305     public static final int USER_ACTION_CREATE_DIR = 15;
    306     public static final int USER_ACTION_SELECT_ALL = 16;
    307     public static final int USER_ACTION_SHARE = 17;
    308     public static final int USER_ACTION_OPEN = 18;
    309     public static final int USER_ACTION_SHOW_ADVANCED = 19;
    310     public static final int USER_ACTION_HIDE_ADVANCED = 20;
    311     public static final int USER_ACTION_NEW_WINDOW = 21;
    312     public static final int USER_ACTION_PASTE_CLIPBOARD = 22;
    313     public static final int USER_ACTION_COPY_CLIPBOARD = 23;
    314     public static final int USER_ACTION_DRAG_N_DROP = 24;
    315     public static final int USER_ACTION_DRAG_N_DROP_MULTI_WINDOW = 25;
    316     public static final int USER_ACTION_CUT_CLIPBOARD = 26;
    317     public static final int USER_ACTION_COMPRESS = 27;
    318     public static final int USER_ACTION_EXTRACT_TO = 28;
    319     public static final int USER_ACTION_VIEW_IN_APPLICATION = 29;
    320     public static final int USER_ACTION_INSPECTOR = 30;
    321 
    322     @IntDef(flag = false, value = {
    323             USER_ACTION_OTHER,
    324             USER_ACTION_GRID,
    325             USER_ACTION_LIST,
    326             USER_ACTION_SORT_NAME,
    327             USER_ACTION_SORT_DATE,
    328             USER_ACTION_SORT_SIZE,
    329             USER_ACTION_SEARCH,
    330             USER_ACTION_SHOW_SIZE,
    331             USER_ACTION_HIDE_SIZE,
    332             USER_ACTION_SETTINGS,
    333             USER_ACTION_COPY_TO,
    334             USER_ACTION_MOVE_TO,
    335             USER_ACTION_DELETE,
    336             USER_ACTION_RENAME,
    337             USER_ACTION_CREATE_DIR,
    338             USER_ACTION_SELECT_ALL,
    339             USER_ACTION_SHARE,
    340             USER_ACTION_OPEN,
    341             USER_ACTION_SHOW_ADVANCED,
    342             USER_ACTION_HIDE_ADVANCED,
    343             USER_ACTION_NEW_WINDOW,
    344             USER_ACTION_PASTE_CLIPBOARD,
    345             USER_ACTION_COPY_CLIPBOARD,
    346             USER_ACTION_DRAG_N_DROP,
    347             USER_ACTION_DRAG_N_DROP_MULTI_WINDOW,
    348             USER_ACTION_CUT_CLIPBOARD,
    349             USER_ACTION_COMPRESS,
    350             USER_ACTION_EXTRACT_TO,
    351             USER_ACTION_VIEW_IN_APPLICATION,
    352             USER_ACTION_INSPECTOR
    353     })
    354     @Retention(RetentionPolicy.SOURCE)
    355     public @interface UserAction {}
    356 
    357     // Codes representing different approaches to copy/move a document. OPMODE_PROVIDER indicates
    358     // it's an optimized operation provided by providers; OPMODE_CONVERTED means it's converted from
    359     // a virtual file; and OPMODE_CONVENTIONAL means it's byte copied.
    360     public static final int OPMODE_PROVIDER = 1;
    361     public static final int OPMODE_CONVERTED = 2;
    362     public static final int OPMODE_CONVENTIONAL = 3;
    363     @IntDef({OPMODE_PROVIDER, OPMODE_CONVERTED, OPMODE_CONVENTIONAL})
    364     @Retention(RetentionPolicy.SOURCE)
    365     public @interface FileOpMode {}
    366 
    367     // Codes representing different menu actions. These are used for bucketing stats in the
    368     // COUNT_MENU_ACTION histogram.
    369     // Do not change or rearrange these values, that will break historical data. Only add to the
    370     // list.
    371     // Do not use negative numbers or zero; clearcut only handles positive integers.
    372     private static final int ACTION_OTHER = 1;
    373     private static final int ACTION_OPEN = 2;
    374     private static final int ACTION_CREATE = 3;
    375     private static final int ACTION_GET_CONTENT = 4;
    376     private static final int ACTION_OPEN_TREE = 5;
    377     @Deprecated private static final int ACTION_MANAGE = 6;
    378     private static final int ACTION_BROWSE = 7;
    379     private static final int ACTION_PICK_COPY_DESTINATION = 8;
    380 
    381     @IntDef(flag = true, value = {
    382             ACTION_OTHER,
    383             ACTION_OPEN,
    384             ACTION_CREATE,
    385             ACTION_GET_CONTENT,
    386             ACTION_OPEN_TREE,
    387             ACTION_MANAGE,
    388             ACTION_BROWSE,
    389             ACTION_PICK_COPY_DESTINATION
    390     })
    391     @Retention(RetentionPolicy.SOURCE)
    392     public @interface MetricsAction {}
    393 
    394     // Codes representing different actions to open the drawer. They are used for bucketing stats in
    395     // the COUNT_DRAWER_OPENED histogram.
    396     // Do not change or rearrange these values, that will break historical data. Only add to the
    397     // list.
    398     // Do not use negative numbers or zero; clearcut only handles positive integers.
    399     private static final int DRAWER_OPENED_HAMBURGER = 1;
    400     private static final int DRAWER_OPENED_SWIPE = 2;
    401 
    402     @IntDef(flag = true, value = {
    403             DRAWER_OPENED_HAMBURGER,
    404             DRAWER_OPENED_SWIPE
    405     })
    406     @Retention(RetentionPolicy.SOURCE)
    407     public @interface DrawerTrigger {}
    408 
    409     /**
    410      * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
    411      *
    412      * @param context
    413      * @param state
    414      * @param intent
    415      */
    416     public static void logActivityLaunch(Context context, State state, Intent intent) {
    417         // Log the launch action.
    418         logHistogram(context, COUNT_LAUNCH_ACTION, toMetricsAction(state.action));
    419         // Then log auxiliary data (roots/mime types) associated with some actions.
    420         Uri uri = intent.getData();
    421         switch (state.action) {
    422             case State.ACTION_OPEN:
    423                 logHistogram(context, COUNT_OPEN_MIME, sanitizeMime(intent.getType()));
    424                 break;
    425             case State.ACTION_CREATE:
    426                 logHistogram(context, COUNT_CREATE_MIME, sanitizeMime(intent.getType()));
    427                 break;
    428             case State.ACTION_GET_CONTENT:
    429                 logHistogram(context, COUNT_GET_CONTENT_MIME, sanitizeMime(intent.getType()));
    430                 break;
    431             case State.ACTION_BROWSE:
    432                 logHistogram(context, COUNT_BROWSE_ROOT, sanitizeRoot(uri));
    433                 break;
    434             default:
    435                 break;
    436         }
    437     }
    438 
    439     /**
    440      * Logs when DocumentsUI are launched with {@link DocumentsContract#EXTRA_INITIAL_URI}.
    441      *
    442      * @param context
    443      * @param state used to resolve action
    444      * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't
    445      *                support {@link DocumentsProvider#findDocumentPath(String, String)}
    446      */
    447     public static void logLaunchAtLocation(Context context, State state, @Nullable Uri rootUri) {
    448         switch (state.action) {
    449             case State.ACTION_BROWSE:
    450                 logHistogram(context, COUNT_BROWSE_AT_LOCATION, sanitizeRoot(rootUri));
    451                 break;
    452             case State.ACTION_CREATE:
    453                 logHistogram(context, COUNT_CREATE_AT_LOCATION, sanitizeRoot(rootUri));
    454                 break;
    455             case State.ACTION_GET_CONTENT:
    456                 logHistogram(context, COUNT_GET_CONTENT_AT_LOCATION, sanitizeRoot(rootUri));
    457                 break;
    458             case State.ACTION_OPEN:
    459                 logHistogram(context, COUNT_OPEN_AT_LOCATION, sanitizeRoot(rootUri));
    460                 break;
    461         }
    462     }
    463 
    464     /**
    465      * Logs a root visited event in file managers. Call this when the user
    466      * taps on a root in {@link com.android.documentsui.sidebar.RootsFragment}.
    467      *
    468      * @param context
    469      * @param scope
    470      * @param info
    471      */
    472     public static void logRootVisited(
    473             Context context, @ContextScope int scope, RootInfo info) {
    474         switch (scope) {
    475             case FILES_SCOPE:
    476                 logHistogram(context, COUNT_ROOT_VISITED_IN_MANAGER,
    477                         sanitizeRoot(info));
    478                 break;
    479             case PICKER_SCOPE:
    480                 logHistogram(context, COUNT_ROOT_VISITED_IN_PICKER,
    481                         sanitizeRoot(info));
    482                 break;
    483         }
    484     }
    485 
    486     /**
    487      * Logs an app visited event in file pickers. Call this when the user visits
    488      * on an app in the RootsFragment.
    489      *
    490      * @param context
    491      * @param info
    492      */
    493     public static void logAppVisited(Context context, ResolveInfo info) {
    494         logHistogram(context, COUNT_ROOT_VISITED_IN_PICKER, sanitizeRoot(info));
    495     }
    496 
    497     /**
    498      * Logs file operation stats. Call this when a file operation has completed. The given
    499      * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
    500      * provider to another vs copying within a given provider).  No PII is logged.
    501      *
    502      * @param context
    503      * @param operationType
    504      * @param srcs
    505      * @param dst
    506      */
    507     public static void logFileOperation(
    508             Context context,
    509             @OpType int operationType,
    510             List<DocumentInfo> srcs,
    511             @Nullable DocumentInfo dst) {
    512 
    513         ProviderCounts counts = new ProviderCounts();
    514         countProviders(counts, srcs, dst);
    515 
    516         if (counts.intraProvider > 0) {
    517             logIntraProviderFileOps(context, dst.authority, operationType);
    518         }
    519         if (counts.systemProvider > 0) {
    520             // Log file operations on system providers.
    521             logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType);
    522         }
    523         if (counts.externalProvider > 0) {
    524             // Log file operations on external providers.
    525             logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType);
    526         }
    527     }
    528 
    529     public static void logFileOperated(
    530             Context context, @OpType int operationType, @FileOpMode int approach) {
    531         switch (operationType) {
    532             case FileOperationService.OPERATION_COPY:
    533                 logHistogram(context, COUNT_FILE_COPIED, approach);
    534                 break;
    535             case FileOperationService.OPERATION_MOVE:
    536                 logHistogram(context, COUNT_FILE_MOVED, approach);
    537                 break;
    538         }
    539     }
    540 
    541     /**
    542      * Logs create directory operation. It is a part of file operation stats. We do not
    543      * differentiate between internal and external locations, all create directory operations are
    544      * logged under COUNT_FILEOP_SYSTEM. Call this when a create directory operation has completed.
    545      *
    546      * @param context
    547      */
    548     public static void logCreateDirOperation(Context context) {
    549         logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR);
    550     }
    551 
    552     /**
    553      * Logs rename file operation. It is a part of file operation stats. We do not differentiate
    554      * between internal and external locations, all rename operations are logged under
    555      * COUNT_FILEOP_SYSTEM. Call this when a rename file operation has completed.
    556      *
    557      * @param context
    558      */
    559     public static void logRenameFileOperation(Context context) {
    560         logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME);
    561     }
    562 
    563     /**
    564      * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete)
    565      * fails.
    566      *
    567      * @param context
    568      * @param operationType
    569      * @param failedFiles
    570      */
    571     public static void logFileOperationErrors(Context context, @OpType int operationType,
    572             List<DocumentInfo> failedFiles, List<Uri> failedUris) {
    573 
    574         ProviderCounts counts = new ProviderCounts();
    575         countProviders(counts, failedFiles, null);
    576 
    577         // TODO: Report URI errors separate from file operation errors.
    578         countProviders(counts, failedUris);
    579 
    580         @FileOp int opCode = FILEOP_OTHER_ERROR;
    581         switch (operationType) {
    582             case FileOperationService.OPERATION_COPY:
    583                 opCode = FILEOP_COPY_ERROR;
    584                 break;
    585             case FileOperationService.OPERATION_COMPRESS:
    586                 opCode = FILEOP_COMPRESS_ERROR;
    587                 break;
    588             case FileOperationService.OPERATION_EXTRACT:
    589                 opCode = FILEOP_EXTRACT_ERROR;
    590                 break;
    591             case FileOperationService.OPERATION_DELETE:
    592                 opCode = FILEOP_DELETE_ERROR;
    593                 break;
    594             case FileOperationService.OPERATION_MOVE:
    595                 opCode = FILEOP_MOVE_ERROR;
    596                 break;
    597         }
    598         if (counts.systemProvider > 0) {
    599             logHistogram(context, COUNT_FILEOP_SYSTEM, opCode);
    600         }
    601         if (counts.externalProvider > 0) {
    602             logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode);
    603         }
    604     }
    605 
    606     public static void logFileOperationFailure(
    607             Context context, @SubFileOp int subFileOp, Uri docUri) {
    608         final String authority = docUri.getAuthority();
    609         switch (authority) {
    610             case Providers.AUTHORITY_MEDIA:
    611                 logHistogram(context, COUNT_MEDIA_FILEOP_FAILURE, subFileOp);
    612                 break;
    613             case Providers.AUTHORITY_STORAGE:
    614                 logStorageFileOperationFailure(context, subFileOp, docUri);
    615                 break;
    616             case Providers.AUTHORITY_DOWNLOADS:
    617                 logHistogram(context, COUNT_DOWNLOADS_FILEOP_FAILURE, subFileOp);
    618                 break;
    619             case Providers.AUTHORITY_MTP:
    620                 logHistogram(context, COUNT_MTP_FILEOP_FAILURE, subFileOp);
    621                 break;
    622             default:
    623                 logHistogram(context, COUNT_OTHER_FILEOP_FAILURE, subFileOp);
    624                 break;
    625         }
    626     }
    627 
    628     /**
    629      * Logs create directory operation error. We do not differentiate between internal and external
    630      * locations, all create directory errors are logged under COUNT_FILEOP_SYSTEM. Call this when a
    631      * create directory operation fails.
    632      *
    633      * @param context
    634      */
    635     public static void logCreateDirError(Context context) {
    636         logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR_ERROR);
    637     }
    638 
    639     /**
    640      * Logs rename file operation error. We do not differentiate between internal and external
    641      * locations, all rename errors are logged under COUNT_FILEOP_SYSTEM. Call this
    642      * when a rename file operation fails.
    643      *
    644      * @param context
    645      */
    646     public static void logRenameFileError(Context context) {
    647         logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME_ERROR);
    648     }
    649 
    650     /**
    651      * Logs the cancellation of a file operation.  Call this when a Job is canceled.
    652      * @param context
    653      * @param operationType
    654      */
    655     public static void logFileOperationCancelled(Context context, @OpType int operationType) {
    656         logHistogram(context, COUNT_FILEOP_CANCELED, toMetricsOpType(operationType));
    657     }
    658 
    659     /**
    660      * Logs startup time in milliseconds.
    661      * @param context
    662      * @param startupMs Startup time in milliseconds.
    663      */
    664     public static void logStartupMs(Context context, int startupMs) {
    665         logHistogram(context, COUNT_STARTUP_MS, startupMs);
    666     }
    667 
    668     private static void logInterProviderFileOps(
    669             Context context,
    670             String histogram,
    671             DocumentInfo dst,
    672             @OpType int operationType) {
    673         if (operationType == FileOperationService.OPERATION_DELETE) {
    674             logHistogram(context, histogram, FILEOP_DELETE);
    675         } else {
    676             assert(dst != null);
    677             @Provider int providerType =
    678                     isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL;
    679             logHistogram(context, histogram, getOpCode(operationType, providerType));
    680         }
    681     }
    682 
    683     private static void logIntraProviderFileOps(
    684             Context context, String authority, @OpType int operationType) {
    685         // Find the right histogram to log to, then log the operation.
    686         String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL;
    687         logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA));
    688     }
    689 
    690     /**
    691      * Logs the action that was started by user.
    692      * @param context
    693      * @param userAction
    694      */
    695     public static void logUserAction(Context context, @UserAction int userAction) {
    696         logHistogram(context, COUNT_USER_ACTION, userAction);
    697     }
    698 
    699     private static void logStorageFileOperationFailure(
    700             Context context, @SubFileOp int subFileOp, Uri docUri) {
    701         assert(Providers.AUTHORITY_STORAGE.equals(docUri.getAuthority()));
    702 
    703         boolean isInternal;
    704         try (ContentProviderClient client = acquireUnstableProviderOrThrow(
    705                 context.getContentResolver(), Providers.AUTHORITY_STORAGE)) {
    706             final Path path = DocumentsContract.findDocumentPath(client, docUri);
    707 
    708             final ProvidersAccess providers = DocumentsApplication.getProvidersCache(context);
    709             final RootInfo root = providers.getRootOneshot(
    710                     Providers.AUTHORITY_STORAGE, path.getRootId());
    711             isInternal = !root.supportsEject();
    712         } catch (RemoteException | RuntimeException e) {
    713             Log.e(TAG, "Failed to obtain its root info. Log the metrics as internal.", e);
    714             // It's not very likely to have an external storage so log it as internal.
    715             isInternal = true;
    716         }
    717 
    718         final String histogram = isInternal
    719                 ? COUNT_INTERNAL_STORAGE_FILEOP_FAILURE
    720                 : COUNT_EXTERNAL_STORAGE_FILEOP_FAILURE;
    721         logHistogram(context, histogram, subFileOp);
    722     }
    723 
    724     /**
    725      * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
    726      *
    727      * @param context
    728      * @param name The counter to increment.
    729      */
    730     private static void logCount(Context context, String name) {
    731         if (DEBUG) Log.d(TAG, name + ": " + 1);
    732         MetricsLogger.count(context, name, 1);
    733     }
    734 
    735     /**
    736      * Internal method for making a MetricsLogger.histogram call.
    737      *
    738      * @param context
    739      * @param name The name of the histogram.
    740      * @param bucket The bucket to increment.
    741      */
    742     private static void logHistogram(Context context, String name, @ActionType int bucket) {
    743         if (DEBUG) Log.d(TAG, name + ": " + bucket);
    744         MetricsLogger.histogram(context, name, bucket);
    745     }
    746 
    747     /**
    748      * Generates an integer identifying the given root. For privacy, this function only recognizes a
    749      * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
    750      * a single ROOT_OTHER bucket.
    751      */
    752     private static @Root int sanitizeRoot(Uri uri) {
    753         if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) {
    754             return ROOT_NONE;
    755         }
    756 
    757         switch (uri.getAuthority()) {
    758             case Providers.AUTHORITY_MEDIA:
    759                 switch (DocumentsContract.getRootId(uri)) {
    760                     case Providers.ROOT_ID_AUDIO:
    761                         return ROOT_AUDIO;
    762                     case Providers.ROOT_ID_IMAGES:
    763                         return ROOT_IMAGES;
    764                     case Providers.ROOT_ID_VIDEOS:
    765                         return ROOT_VIDEOS;
    766                     default:
    767                         return ROOT_OTHER;
    768                 }
    769             case Providers.AUTHORITY_STORAGE:
    770                 if (Providers.ROOT_ID_HOME.equals(DocumentsContract.getRootId(uri))) {
    771                     return ROOT_HOME;
    772                 } else {
    773                     return ROOT_DEVICE_STORAGE;
    774                 }
    775             case Providers.AUTHORITY_DOWNLOADS:
    776                 return ROOT_DOWNLOADS;
    777             case Providers.AUTHORITY_MTP:
    778                 return ROOT_MTP;
    779             default:
    780                 return ROOT_OTHER;
    781         }
    782     }
    783 
    784     /** @see #sanitizeRoot(Uri) */
    785     private static @Root int sanitizeRoot(RootInfo root) {
    786         if (root.isRecents()) {
    787             // Recents root is special and only identifiable via this method call. Other roots are
    788             // identified by URI.
    789             return ROOT_RECENTS;
    790         } else {
    791             return sanitizeRoot(root.getUri());
    792         }
    793     }
    794 
    795     /** @see #sanitizeRoot(Uri) */
    796     private static @Root int sanitizeRoot(ResolveInfo info) {
    797         // Log all apps under a single bucket in the roots histogram.
    798         return ROOT_THIRD_PARTY_APP;
    799     }
    800 
    801     /**
    802      * Generates an int identifying a mime type. For privacy, this function only recognizes a small
    803      * set of hard-coded types. For any other type, this function returns "other".
    804      *
    805      * @param mimeType
    806      * @return
    807      */
    808     private static @Mime int sanitizeMime(String mimeType) {
    809         if (mimeType == null) {
    810             return MIME_NONE;
    811         } else if ("*/*".equals(mimeType)) {
    812             return MIME_ANY;
    813         } else {
    814             String type = mimeType.substring(0, mimeType.indexOf('/'));
    815             switch (type) {
    816                 case "application":
    817                     return MIME_APPLICATION;
    818                 case "audio":
    819                     return MIME_AUDIO;
    820                 case "image":
    821                     return MIME_IMAGE;
    822                 case "message":
    823                     return MIME_MESSAGE;
    824                 case "multipart":
    825                     return MIME_MULTIPART;
    826                 case "text":
    827                     return MIME_TEXT;
    828                 case "video":
    829                     return MIME_VIDEO;
    830             }
    831         }
    832         // Bucket all other types into one bucket.
    833         return MIME_OTHER;
    834     }
    835 
    836     private static boolean isSystemProvider(String authority) {
    837         switch (authority) {
    838             case Providers.AUTHORITY_MEDIA:
    839             case Providers.AUTHORITY_STORAGE:
    840             case Providers.AUTHORITY_DOWNLOADS:
    841                 return true;
    842             default:
    843                 return false;
    844         }
    845     }
    846 
    847     /**
    848      * @param operation
    849      * @param providerType
    850      * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
    851      *         combination.
    852      */
    853     private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
    854         switch (operation) {
    855             case FileOperationService.OPERATION_COPY:
    856                 switch (providerType) {
    857                     case PROVIDER_INTRA:
    858                         return FILEOP_COPY_INTRA_PROVIDER;
    859                     case PROVIDER_SYSTEM:
    860                         return FILEOP_COPY_SYSTEM_PROVIDER;
    861                     case PROVIDER_EXTERNAL:
    862                         return FILEOP_COPY_EXTERNAL_PROVIDER;
    863                 }
    864             case FileOperationService.OPERATION_COMPRESS:
    865                 switch (providerType) {
    866                     case PROVIDER_INTRA:
    867                         return FILEOP_COMPRESS_INTRA_PROVIDER;
    868                     case PROVIDER_SYSTEM:
    869                         return FILEOP_COMPRESS_SYSTEM_PROVIDER;
    870                     case PROVIDER_EXTERNAL:
    871                         return FILEOP_COMPRESS_EXTERNAL_PROVIDER;
    872                 }
    873              case FileOperationService.OPERATION_EXTRACT:
    874                 switch (providerType) {
    875                     case PROVIDER_INTRA:
    876                         return FILEOP_EXTRACT_INTRA_PROVIDER;
    877                     case PROVIDER_SYSTEM:
    878                         return FILEOP_EXTRACT_SYSTEM_PROVIDER;
    879                     case PROVIDER_EXTERNAL:
    880                         return FILEOP_EXTRACT_EXTERNAL_PROVIDER;
    881                 }
    882             case FileOperationService.OPERATION_MOVE:
    883                 switch (providerType) {
    884                     case PROVIDER_INTRA:
    885                         return FILEOP_MOVE_INTRA_PROVIDER;
    886                     case PROVIDER_SYSTEM:
    887                         return FILEOP_MOVE_SYSTEM_PROVIDER;
    888                     case PROVIDER_EXTERNAL:
    889                         return FILEOP_MOVE_EXTERNAL_PROVIDER;
    890                 }
    891             case FileOperationService.OPERATION_DELETE:
    892                 return FILEOP_DELETE;
    893             default:
    894                 Log.w(TAG, "Unrecognized operation type when logging a file operation");
    895                 return FILEOP_OTHER;
    896         }
    897     }
    898 
    899     /**
    900      * Maps FileOperationService OpType values, to MetricsOpType values.
    901      */
    902     private static @MetricsOpType int toMetricsOpType(@OpType int operation) {
    903         switch (operation) {
    904             case FileOperationService.OPERATION_COPY:
    905                 return OPERATION_COPY;
    906             case FileOperationService.OPERATION_MOVE:
    907                 return OPERATION_MOVE;
    908             case FileOperationService.OPERATION_DELETE:
    909                 return OPERATION_DELETE;
    910             case FileOperationService.OPERATION_UNKNOWN:
    911             default:
    912                 return OPERATION_UNKNOWN;
    913         }
    914     }
    915 
    916     private static @MetricsAction int toMetricsAction(int action) {
    917         switch(action) {
    918             case State.ACTION_OPEN:
    919                 return ACTION_OPEN;
    920             case State.ACTION_CREATE:
    921                 return ACTION_CREATE;
    922             case State.ACTION_GET_CONTENT:
    923                 return ACTION_GET_CONTENT;
    924             case State.ACTION_OPEN_TREE:
    925                 return ACTION_OPEN_TREE;
    926             case State.ACTION_BROWSE:
    927                 return ACTION_BROWSE;
    928             case State.ACTION_PICK_COPY_DESTINATION:
    929                 return ACTION_PICK_COPY_DESTINATION;
    930             default:
    931                 return ACTION_OTHER;
    932         }
    933     }
    934 
    935     /**
    936      * Count the given src documents and provide a tally of how many come from the same provider as
    937      * the dst document (if a dst is provided), how many come from system providers, and how many
    938      * come from external 3rd-party providers.
    939      */
    940     private static void countProviders(
    941             ProviderCounts counts, List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
    942         for (DocumentInfo doc: srcs) {
    943             countForAuthority(counts, doc.authority, dst);
    944         }
    945     }
    946 
    947     /**
    948      * Count the given uris and provide a tally of how many come from the same provider as
    949      * the dst document (if a dst is provided), how many come from system providers, and how many
    950      * come from external 3rd-party providers.
    951      */
    952     private static void countProviders(ProviderCounts counts, List<Uri> uris) {
    953         for (Uri uri: uris) {
    954             countForAuthority(counts, uri.getAuthority(), null);
    955         }
    956     }
    957 
    958     private static void countForAuthority(
    959             ProviderCounts counts, String authority, @Nullable DocumentInfo dst) {
    960         if (dst != null && authority.equals(dst.authority)) {
    961             counts.intraProvider++;
    962         } else if (isSystemProvider(authority)){
    963             counts.systemProvider++;
    964         } else {
    965             counts.externalProvider++;
    966         }
    967     }
    968 
    969     private static class ProviderCounts {
    970         int intraProvider;
    971         int systemProvider;
    972         int externalProvider;
    973     }
    974 }
    975