Merge "Migration to Westworld (2/2)"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d184726..6a54cd4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -140,6 +140,11 @@
             android:exported="false"/>
 
         <provider
+            android:name=".picker.PickCountRecordProvider"
+            android:authorities="com.android.documentsui.pickCountRecord"
+            android:exported="false"/>
+
+        <provider
             android:name=".archives.ArchivesProvider"
             android:authorities="com.android.documentsui.archives"
             android:grantUriPermissions="true"
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index e166b87..3d14f0e 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -36,6 +36,7 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Checkable;
 import android.widget.TextView;
 
 import androidx.annotation.CallSuper;
@@ -63,6 +64,7 @@
 import com.android.documentsui.prefs.PreferencesMonitor;
 import com.android.documentsui.prefs.ScopedPreferences;
 import com.android.documentsui.queries.CommandInterceptor;
+import com.android.documentsui.queries.SearchChipData;
 import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.queries.SearchViewManager.SearchManagerListener;
 import com.android.documentsui.roots.ProvidersCache;
@@ -73,6 +75,7 @@
 import com.android.documentsui.sorting.SortModel;
 
 import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.chip.Chip;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -166,6 +169,14 @@
                     Metrics.logUserAction(MetricConsts.USER_ACTION_SEARCH);
                 }
 
+
+                if (mSearchManager.isSearching()) {
+                    Metrics.logSearchMode(query != null, mSearchManager.hasCheckedChip());
+                    if (mInjector.pickResult != null) {
+                        mInjector.pickResult.increaseActionCount();
+                    }
+                }
+
                 mInjector.actions.loadDocumentsForCurrentStack();
             }
 
@@ -179,6 +190,16 @@
             public void onSearchViewChanged(boolean opened) {
                 mNavigator.update();
             }
+
+            @Override
+            public void onSearchChipStateChanged(View v) {
+                final Checkable chip = (Checkable) v;
+                if (chip.isChecked()) {
+                    final SearchChipData item = (SearchChipData) v.getTag();
+                    Metrics.logUserAction(MetricConsts.USER_ACTION_SEARCH_CHIP);
+                    Metrics.logSearchType(item.getChipType());
+                }
+            }
         };
 
         // "Commands" are meta input for controlling system behavior.
diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java
index 5c2a537..d6c6312 100644
--- a/src/com/android/documentsui/Injector.java
+++ b/src/com/android/documentsui/Injector.java
@@ -31,6 +31,7 @@
 import com.android.documentsui.base.Lookup;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.dirlist.AppsRowManager;
+import com.android.documentsui.picker.PickResult;
 import com.android.documentsui.prefs.ScopedPreferences;
 import com.android.documentsui.queries.SearchViewManager;
 import com.android.documentsui.ui.DialogController;
@@ -59,6 +60,8 @@
     public SearchViewManager searchManager;
     public AppsRowManager appsRowManager;
 
+    public PickResult pickResult;
+
     public final DebugHelper debugHelper;
 
     @ContentScoped
diff --git a/src/com/android/documentsui/MetricConsts.java b/src/com/android/documentsui/MetricConsts.java
index 6db30fa..f95f6e5 100644
--- a/src/com/android/documentsui/MetricConsts.java
+++ b/src/com/android/documentsui/MetricConsts.java
@@ -354,4 +354,40 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface InvalidScopedAccess {
     }
+
+    // Codes representing different search types
+    public static final int TYPE_UNKNOWN = 0;
+    public static final int TYPE_CHIP_IMAGES = 1;
+    public static final int TYPE_CHIP_AUDIOS = 2;
+    public static final int TYPE_CHIP_VIDEOS = 3;
+    public static final int TYPE_CHIP_DOCS = 4;
+    public static final int TYPE_SEARCH_HISTORY = 5;
+    public static final int TYPE_SEARCH_STRING = 6;
+
+    @IntDef(flag = true, value = {
+            TYPE_UNKNOWN,
+            TYPE_CHIP_IMAGES,
+            TYPE_CHIP_AUDIOS,
+            TYPE_CHIP_VIDEOS,
+            TYPE_CHIP_DOCS,
+            TYPE_SEARCH_HISTORY,
+            TYPE_SEARCH_STRING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SearchType {}
+
+    // Codes representing different search types
+    public static final int SEARCH_UNKNOWN = 0;
+    public static final int SEARCH_KEYWORD = 1;
+    public static final int SEARCH_CHIPS = 2;
+    public static final int SEARCH_KEYWORD_N_CHIPS = 3;
+
+    @IntDef(flag = true, value = {
+            SEARCH_UNKNOWN,
+            SEARCH_KEYWORD,
+            SEARCH_CHIPS,
+            SEARCH_KEYWORD_N_CHIPS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SearchMode {}
 }
\ No newline at end of file
diff --git a/src/com/android/documentsui/Metrics.java b/src/com/android/documentsui/Metrics.java
index 5737fcb..6344240 100644
--- a/src/com/android/documentsui/Metrics.java
+++ b/src/com/android/documentsui/Metrics.java
@@ -38,6 +38,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.State;
 import com.android.documentsui.files.LauncherActivity;
+import com.android.documentsui.picker.PickResult;
 import com.android.documentsui.roots.ProvidersAccess;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
@@ -287,6 +288,30 @@
         DocumentsStatsLog.logUserAction(userAction);
     }
 
+    public static void logPickerLaunchedFrom(String packgeName) {
+        DocumentsStatsLog.logPickerLaunchedFrom(packgeName);
+    }
+
+    public static void logSearchType(int searchType) {
+        // TODO:waiting for search history implementation, it's one of the search types.
+        DocumentsStatsLog.logSearchType(searchType);
+    }
+
+    public static void logSearchMode(boolean isKeywordSearch, boolean isChipsSearch) {
+        DocumentsStatsLog.logSearchMode(getSearchMode(isKeywordSearch, isChipsSearch));
+    }
+
+    public static void logPickResult(PickResult result) {
+        DocumentsStatsLog.logFilePick(
+                result.getActionCount(),
+                result.getDuration(),
+                result.getFileCount(),
+                result.isSearching(),
+                result.getRoot(),
+                result.getMimeType(),
+                result.getRepeatedPickTimes());
+    }
+
     private static void logStorageFileOperationFailure(
             Context context, @MetricConsts.SubFileOp int subFileOp, Uri docUri) {
         assert(Providers.AUTHORITY_STORAGE.equals(docUri.getAuthority()));
@@ -504,6 +529,18 @@
         }
     }
 
+    private static int getSearchMode(boolean isKeyword, boolean isChip) {
+        if (isKeyword && isChip) {
+            return MetricConsts.SEARCH_KEYWORD_N_CHIPS;
+        } else if (isKeyword) {
+            return MetricConsts.SEARCH_KEYWORD;
+        } else if (isChip) {
+            return MetricConsts.SEARCH_CHIPS;
+        } else {
+            return MetricConsts.SEARCH_UNKNOWN;
+        }
+    }
+
     /**
      * Count the given src documents and provide a tally of how many come from the same provider as
      * the dst document (if a dst is provided), how many come from system providers, and how many
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index 795b98e..ec13a50 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -108,6 +108,11 @@
     public static final String EXTRA_IGNORE_STATE = "ignoreState";
 
     /**
+     * Extra flag used to store pick result state of PickResult type in the bundle.
+     */
+    public static final String EXTRA_PICK_RESULT = "pickResult";
+
+    /**
      * Extra for an Intent for enabling performance benchmark. Used only by tests.
      */
     public static final String EXTRA_BENCHMARK = "com.android.documentsui.benchmark";
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 5e05115..a781b19 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -620,6 +620,9 @@
     }
 
     private boolean handleMenuItemClick(MenuItem item) {
+        if (mInjector.pickResult != null) {
+            mInjector.pickResult.increaseActionCount();
+        }
         MutableSelection<String> selection = new MutableSelection<>();
         mSelectionMgr.copySelection(selection);
 
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 302fa9e..ecac93a 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -82,22 +82,25 @@
     private final Model mModel;
     private final LastAccessedStorage mLastAccessed;
 
-    ActionHandler(
-            T activity,
-            State state,
-            ProvidersAccess providers,
-            DocumentsAccess docs,
-            SearchViewManager searchMgr,
-            Lookup<String, Executor> executors,
-            Injector injector,
-            LastAccessedStorage lastAccessed) {
+    private UpdatePickResultTask mUpdatePickResultTask;
 
+    ActionHandler(
+        T activity,
+        State state,
+        ProvidersAccess providers,
+        DocumentsAccess docs,
+        SearchViewManager searchMgr,
+        Lookup<String, Executor> executors,
+        Injector injector,
+        LastAccessedStorage lastAccessed) {
         super(activity, state, providers, docs, searchMgr, executors, injector);
 
         mConfig = injector.config;
         mFeatures = injector.features;
         mModel = injector.getModel();
         mLastAccessed = lastAccessed;
+        mUpdatePickResultTask = new UpdatePickResultTask(
+            activity.getApplicationContext(), mInjector.pickResult);
     }
 
     @Override
@@ -179,6 +182,35 @@
         }
     }
 
+    public UpdatePickResultTask getUpdatePickResultTask() {
+        return mUpdatePickResultTask;
+    }
+
+    private void updatePickResult(Intent intent, boolean isSearching, int root) {
+        ClipData cdata = intent.getClipData();
+        int fileCount = 0;
+        Uri uri = null;
+
+        // There are 2 cases that would be single-select:
+        // 1. getData() isn't null and getClipData() is null.
+        // 2. getClipData() isn't null and the item count of it is 1.
+        if (intent.getData() != null && cdata == null) {
+            fileCount = 1;
+            uri = intent.getData();
+        } else if (cdata != null) {
+            fileCount = cdata.getItemCount();
+            if (fileCount == 1) {
+                uri = cdata.getItemAt(0).getUri();
+            }
+        }
+
+        mInjector.pickResult.setFileCount(fileCount);
+        mInjector.pickResult.setIsSearching(isSearching);
+        mInjector.pickResult.setRoot(root);
+        mInjector.pickResult.setFileUri(uri);
+        getUpdatePickResultTask().execute();
+    }
+
     private void loadDefaultLocation() {
         switch (mState.action) {
             case ACTION_CREATE:
@@ -196,6 +228,7 @@
 
     @Override
     public void showAppDetails(ResolveInfo info) {
+        mInjector.pickResult.increaseActionCount();
         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
         intent.setData(Uri.fromParts("package", info.activityInfo.packageName, null));
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
@@ -222,6 +255,8 @@
             // Remember that we last picked via external app
             mLastAccessed.setLastAccessedToExternalApp(mActivity);
 
+            updatePickResult(data, false, MetricConsts.ROOT_THIRD_PARTY_APP);
+
             // Pass back result to original caller
             mActivity.setResult(resultCode, data, 0);
             mActivity.finish();
@@ -239,12 +274,14 @@
     @Override
     public void openRoot(RootInfo root) {
         Metrics.logRootVisited(MetricConsts.PICKER_SCOPE, root);
+        mInjector.pickResult.increaseActionCount();
         mActivity.onRootPicked(root);
     }
 
     @Override
     public void openRoot(ResolveInfo info) {
         Metrics.logAppVisited(info);
+        mInjector.pickResult.increaseActionCount();
         final Intent intent = new Intent(mActivity.getIntent());
         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
         intent.setComponent(new ComponentName(
@@ -259,6 +296,7 @@
     @Override
     public boolean openItem(ItemDetails<String> details, @ViewType int type,
             @ViewType int fallback) {
+        mInjector.pickResult.increaseActionCount();
         DocumentInfo doc = mModel.getDocument(details.getSelectionKey());
         if (doc == null) {
             Log.w(TAG, "Can't view item. No Document available for modeId: "
@@ -276,6 +314,7 @@
 
     @Override
     public boolean previewItem(ItemDetails<String> details) {
+        mInjector.pickResult.increaseActionCount();
         final DocumentInfo doc = mModel.getDocument(details.getSelectionKey());
         if (doc == null) {
             Log.w(TAG, "Can't view item. No Document available for modeId: "
@@ -312,6 +351,7 @@
 
     void pickDocument(FragmentManager fm, DocumentInfo pickTarget) {
         assert(pickTarget != null);
+        mInjector.pickResult.increaseActionCount();
         Uri result;
         switch (mState.action) {
             case ACTION_OPEN_TREE:
@@ -330,6 +370,7 @@
     void saveDocument(
             String mimeType, String displayName, BooleanConsumer inProgressStateListener) {
         assert(mState.action == ACTION_CREATE);
+        mInjector.pickResult.increaseActionCount();
         new CreatePickedDocumentTask(
                 mActivity,
                 mDocs,
@@ -346,6 +387,7 @@
     // called.
     void saveDocument(FragmentManager fm, DocumentInfo replaceTarget) {
         assert(mState.action == ACTION_CREATE);
+        mInjector.pickResult.increaseActionCount();
         assert(replaceTarget != null);
 
         // Adding a confirmation dialog breaks an inherited CTS test (testCreateExisting), so we
@@ -383,6 +425,9 @@
             intent.setClipData(clipData);
         }
 
+        updatePickResult(
+            intent, mSearchMgr.isSearching(), Metrics.sanitizeRoot(mState.stack.getRoot()));
+
         // TODO: Separate this piece of logic per action.
         // We don't instantiate different objects for different actions at the first place, so it's
         // not a easy task to separate this logic cleanly.
diff --git a/src/com/android/documentsui/picker/ConfirmFragment.java b/src/com/android/documentsui/picker/ConfirmFragment.java
index b61acf8..7565be0 100644
--- a/src/com/android/documentsui/picker/ConfirmFragment.java
+++ b/src/com/android/documentsui/picker/ConfirmFragment.java
@@ -64,6 +64,7 @@
 
         mTarget = arg.getParcelable(Shared.EXTRA_DOC);
         mType = arg.getInt(CONFIRM_TYPE);
+        final PickResult pickResult = ((PickActivity) getActivity()).getInjector().pickResult;
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
         switch (mType) {
@@ -74,8 +75,10 @@
                 builder.setMessage(message);
                 builder.setPositiveButton(
                         android.R.string.ok,
-                        (DialogInterface dialog, int id) ->
-                                mActions.finishPicking(mTarget.derivedUri));
+                        (DialogInterface dialog, int id) -> {
+                            pickResult.increaseActionCount();
+                            mActions.finishPicking(mTarget.derivedUri);
+                        });
                 break;
             case TYPE_OEPN_TREE:
                 final Uri uri = DocumentsContract.buildTreeDocumentUri(
@@ -91,12 +94,15 @@
                 builder.setMessage(message);
                 builder.setPositiveButton(
                         R.string.allow,
-                        (DialogInterface dialog, int id) ->
-                                mActions.finishPicking(uri));
+                        (DialogInterface dialog, int id) -> {
+                            pickResult.increaseActionCount();
+                            mActions.finishPicking(uri);
+                        });
                 break;
 
         }
-        builder.setNegativeButton(android.R.string.cancel, null);
+        builder.setNegativeButton(android.R.string.cancel,
+                (DialogInterface dialog, int id) -> pickResult.increaseActionCount());
 
         return builder.create();
     }
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 124279a..8a38e13 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.provider.DocumentsContract;
 import androidx.annotation.CallSuper;
 import androidx.fragment.app.Fragment;
@@ -32,6 +33,7 @@
 
 import android.view.KeyEvent;
 import android.view.Menu;
+import android.view.MenuItem;
 
 import com.android.documentsui.ActionModeController;
 import com.android.documentsui.BaseActivity;
@@ -40,6 +42,7 @@
 import com.android.documentsui.FocusManager;
 import com.android.documentsui.Injector;
 import com.android.documentsui.MenuManager.DirectoryDetails;
+import com.android.documentsui.Metrics;
 import com.android.documentsui.ProviderExecutor;
 import com.android.documentsui.R;
 import com.android.documentsui.SharedInputHandler;
@@ -69,8 +72,6 @@
     private Injector<ActionHandler<PickActivity>> mInjector;
     private SharedInputHandler mSharedInputHandler;
 
-    private LastAccessedStorage mLastAccessed;
-
     public PickActivity() {
         super(R.layout.documents_activity, TAG);
     }
@@ -113,7 +114,7 @@
                 mInjector.menuManager,
                 mInjector.messages);
 
-        mLastAccessed = LastAccessedStorage.create();
+        mInjector.pickResult = getPickResult(icicle);
         mInjector.actions = new ActionHandler<>(
                 this,
                 mState,
@@ -122,7 +123,7 @@
                 mSearchManager,
                 ProviderExecutor::forAuthority,
                 mInjector,
-                mLastAccessed);
+                LastAccessedStorage.create());
 
         mInjector.searchManager = mSearchManager;
 
@@ -141,6 +142,41 @@
                         mDrawer);
         setupLayout(intent);
         mInjector.actions.initLocation(intent);
+        Metrics.logPickerLaunchedFrom(Shared.getCallingPackageName(this));
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        // log the case of user picking nothing.
+        mInjector.actions.getUpdatePickResultTask().execute();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle state) {
+        super.onSaveInstanceState(state);
+        state.putParcelable(Shared.EXTRA_PICK_RESULT, mInjector.pickResult);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mInjector.pickResult.setPickStartTime(SystemClock.uptimeMillis());
+    }
+
+    @Override
+    protected void onPause() {
+        mInjector.pickResult.increaseDuration(SystemClock.uptimeMillis());
+        super.onPause();
+    }
+
+    private static PickResult getPickResult(Bundle icicle) {
+        if (icicle != null) {
+            PickResult result = icicle.getParcelable(Shared.EXTRA_PICK_RESULT);
+            return result;
+        }
+
+        return new PickResult();
     }
 
     private void setupLayout(Intent intent) {
@@ -251,6 +287,12 @@
     }
 
     @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        mInjector.pickResult.increaseActionCount();
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
     protected void refreshDirectory(int anim) {
         final FragmentManager fm = getSupportFragmentManager();
         final RootInfo root = getCurrentRoot();
diff --git a/src/com/android/documentsui/picker/PickCountRecordProvider.java b/src/com/android/documentsui/picker/PickCountRecordProvider.java
new file mode 100644
index 0000000..0a8e215
--- /dev/null
+++ b/src/com/android/documentsui/picker/PickCountRecordProvider.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.picker;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.util.Log;
+
+public class PickCountRecordProvider extends ContentProvider {
+    private static final String TAG = "PickCountRecordProvider";
+
+    private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+    private static final int URI_PICK_RECORD = 1;
+
+    private static final String PATH_PICK_COUNT_RECORD = "pickCountRecord";
+
+    private static final String TABLE_PICK_COUNT_RECORD = "pickCountRecordTable";
+
+    static final String AUTHORITY = "com.android.documentsui.pickCountRecord";
+
+    static {
+        MATCHER.addURI(AUTHORITY, "pickCountRecord/*", URI_PICK_RECORD);
+    }
+
+    public static class Columns {
+        public static final String FILE_HASH_ID = "file_hash_id";
+        public static final String PICK_COUNT = "pick_count";
+    }
+
+    /**
+     * Build pickRecord uri.
+     *
+     * @param hashFileId the file hash id.
+     * @return return an pick record uri.
+     */
+    public static Uri buildPickRecordUri(int hashFileId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(AUTHORITY).appendPath(PATH_PICK_COUNT_RECORD)
+                .appendPath(Integer.toString(hashFileId))
+                .build();
+    }
+
+    private PickCountRecordProvider.DatabaseHelper mHelper;
+
+    private static class DatabaseHelper extends SQLiteOpenHelper {
+        private static final String DB_NAME = "pickCountRecord.db";
+
+        private static final int VERSION_INIT = 1;
+
+        public DatabaseHelper(Context context) {
+            super(context, DB_NAME, null, VERSION_INIT);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_PICK_COUNT_RECORD + " ("
+                    + PickCountRecordProvider.Columns.FILE_HASH_ID + " TEXT NOT NULL PRIMARY KEY,"
+                    + PickCountRecordProvider.Columns.PICK_COUNT + " INTEGER" + ")");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.w(TAG, "Upgrading database; wiping app data");
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_PICK_COUNT_RECORD);
+            onCreate(db);
+        }
+    }
+
+    @Override
+    public boolean onCreate() {
+        mHelper = new DatabaseHelper(getContext());
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        if (MATCHER.match(uri) != URI_PICK_RECORD) {
+            throw new UnsupportedOperationException("Unsupported Uri " + uri);
+        }
+        final SQLiteDatabase db = mHelper.getReadableDatabase();
+        final String fileHashId = uri.getPathSegments().get(1);
+        return db.query(TABLE_PICK_COUNT_RECORD, projection, Columns.FILE_HASH_ID + "=?",
+                new String[] { fileHashId }, null, null, sortOrder);
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        if (MATCHER.match(uri) != URI_PICK_RECORD) {
+            throw new UnsupportedOperationException("Unsupported Uri " + uri);
+        }
+        final SQLiteDatabase db = mHelper.getWritableDatabase();
+        final ContentValues key = new ContentValues();
+
+        final String hashId = uri.getPathSegments().get(1);
+        key.put(Columns.FILE_HASH_ID, hashId);
+
+        // Ensure that row exists, then update with changed values
+        db.insertWithOnConflict(TABLE_PICK_COUNT_RECORD, null, key, SQLiteDatabase.CONFLICT_IGNORE);
+        db.update(TABLE_PICK_COUNT_RECORD, values, Columns.FILE_HASH_ID + "=?",
+            new String[] { hashId });
+        return null;
+    }
+
+    static void setPickRecord(ContentResolver resolver, int fileHashId, int pickCount) {
+        final ContentValues values = new ContentValues();
+        values.clear();
+        values.put(Columns.PICK_COUNT, pickCount);
+        resolver.insert(buildPickRecordUri(fileHashId), values);
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("Unsupported Uri " + uri);
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        final SQLiteDatabase db = mHelper.getWritableDatabase();
+        final String hashId = uri.getPathSegments().get(1);
+        return db.delete(TABLE_PICK_COUNT_RECORD, Columns.FILE_HASH_ID + "=?",
+            new String[] { hashId });
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/documentsui/picker/PickCountRecordStorage.java b/src/com/android/documentsui/picker/PickCountRecordStorage.java
new file mode 100644
index 0000000..01476ee
--- /dev/null
+++ b/src/com/android/documentsui/picker/PickCountRecordStorage.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.picker;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+public interface PickCountRecordStorage {
+    int getPickCountRecord(Context context, Uri uri);
+    void setPickCountRecord(Context context, Uri uri, int pickCount);
+    int increasePickCountRecord(Context context, Uri uri);
+
+    static PickCountRecordStorage create() {
+        return new PickCountRecordStorage() {
+            private static final String TAG = "PickCountRecordStorage";
+
+            @Override
+            public int getPickCountRecord(Context context, Uri uri) {
+                int fileHashId = uri.hashCode();
+                Uri pickRecordUri = PickCountRecordProvider.buildPickRecordUri(fileHashId);
+                final ContentResolver resolver = context.getContentResolver();
+                int count = 0;
+                try (Cursor cursor = resolver.query(pickRecordUri, null, null, null, null)) {
+                    if (cursor != null && cursor.moveToFirst()) {
+                        final int index = cursor
+                            .getColumnIndex(PickCountRecordProvider.Columns.PICK_COUNT);
+                        if (index != -1) {
+                            count = cursor.getInt(index);
+                        }
+                    }
+                }
+                return count;
+            }
+
+            @Override
+            public void setPickCountRecord(Context context, Uri uri, int pickCount) {
+                PickCountRecordProvider.setPickRecord(
+                    context.getContentResolver(), uri.hashCode(), pickCount);
+            }
+
+            @Override
+            public int increasePickCountRecord(Context context, Uri uri) {
+                int pickCount = getPickCountRecord(context, uri) + 1;
+                setPickCountRecord(context, uri, pickCount);
+                return pickCount;
+            }
+        };
+    }
+}
diff --git a/src/com/android/documentsui/picker/PickFragment.java b/src/com/android/documentsui/picker/PickFragment.java
index 9dd7f17..3ac5974 100644
--- a/src/com/android/documentsui/picker/PickFragment.java
+++ b/src/com/android/documentsui/picker/PickFragment.java
@@ -63,6 +63,7 @@
     private final View.OnClickListener mCancelListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
+            mInjector.pickResult.increaseActionCount();
             final BaseActivity activity = BaseActivity.get(PickFragment.this);
             activity.setResult(FragmentActivity.RESULT_CANCELED);
             activity.finish();
diff --git a/src/com/android/documentsui/picker/PickResult.java b/src/com/android/documentsui/picker/PickResult.java
new file mode 100644
index 0000000..af61a08
--- /dev/null
+++ b/src/com/android/documentsui/picker/PickResult.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.picker;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.documentsui.MetricConsts;
+
+public class PickResult implements android.os.Parcelable {
+    private int mActionCount;
+    private long mDuration;
+    private int mFileCount;
+    private boolean mIsSearching;
+    private @MetricConsts.Root int mRoot;
+    private @MetricConsts.Mime int mMimeType;
+    private int mRepeatedPickTimes;
+
+    // only used for single-select case to get the mRepeatedPickTimes and mMimeType
+    private Uri mFileUri;
+    private long mPickStartTime;
+
+    /**
+     * get total action count during picking.
+     *
+     * @return action count
+     */
+    public int getActionCount() {
+        return mActionCount;
+    }
+
+    /**
+     * increase action count.
+     */
+    public void increaseActionCount() {
+        mActionCount++;
+    }
+
+    /**
+     * get pick duration
+     *
+     * @return pick duration
+     */
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * increase pick duration.
+     *
+     * @param currentMillis current time millis
+     */
+    public void increaseDuration(long currentMillis) {
+        mDuration += currentMillis - mPickStartTime;
+        setPickStartTime(currentMillis);
+    }
+
+    /**
+     * set the pick start time.
+     *
+     * @param millis
+     */
+    public void setPickStartTime(long millis) {
+        mPickStartTime = millis;
+    }
+
+    /**
+     * get number of files picked.
+     *
+     * @return file count
+     */
+    public int getFileCount() {
+        return mFileCount;
+    }
+
+    /**
+     * set number of files picked.
+     *
+     * @param count
+     */
+    public void setFileCount(int count) {
+        mFileCount = count;
+    }
+
+    /**
+     * check whether this pick is under searching.
+     *
+     * @return under searching or not
+     */
+    public boolean isSearching() {
+        return mIsSearching;
+    }
+
+    /**
+     * set whether this pick is under searching.
+     *
+     * @param isSearching
+     */
+    public void setIsSearching(boolean isSearching) {
+        this.mIsSearching = isSearching;
+    }
+
+    /**
+     * get the root where the file is picked.
+     *
+     * @return root
+     */
+    public int getRoot() {
+        return mRoot;
+    }
+
+    /**
+     * set the root where the file is picked.
+     *
+     * @param root
+     */
+    public void setRoot(@MetricConsts.Root int root) {
+        this.mRoot = root;
+    }
+
+    /**
+     * get the mime type of the pick file.
+     *
+     * @return mime type
+     */
+    public int getMimeType() {
+        return mMimeType;
+    }
+
+    /**
+     * set the mime type of the pick file.
+     *
+     * @param mimeType
+     */
+    public void setMimeType(@MetricConsts.Mime int mimeType) {
+        this.mMimeType = mimeType;
+    }
+
+    /**
+     * get number of time the selected file is picked repeatedly.
+     *
+     * @return repeatedly pick count
+     */
+    public int getRepeatedPickTimes() {
+        return mRepeatedPickTimes;
+    }
+
+    /**
+     * set number of time the selected file is picked repeatedly.
+     *
+     * @param times the repeatedly pick times
+     */
+    public void setRepeatedPickTimes(int times) {
+        mRepeatedPickTimes = times;
+    }
+
+    /**
+     * get the uri of the selected doc.
+     *
+     * @return file uri
+     */
+    public Uri getFileUri() {
+        return mFileUri;
+    }
+
+    /**
+     * set the uri of the selected doc.
+     *
+     * @param fileUri the selected doc uri
+     */
+    public void setFileUri(Uri fileUri) {
+        this.mFileUri = fileUri;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mActionCount);
+        out.writeLong(mDuration);
+        out.writeInt(mFileCount);
+        out.writeInt(mIsSearching ? 1 : 0);
+        out.writeInt(mRoot);
+        out.writeInt(mMimeType);
+        out.writeInt(mRepeatedPickTimes);
+    }
+
+    public static final Parcelable.ClassLoaderCreator<PickResult>
+            CREATOR = new Parcelable.ClassLoaderCreator<PickResult>() {
+        @Override
+        public PickResult createFromParcel(Parcel in) {
+            return createFromParcel(in, null);
+        }
+
+        @Override
+        public PickResult createFromParcel(Parcel in, ClassLoader loader) {
+            final PickResult result = new PickResult();
+            result.mActionCount = in.readInt();
+            result.mDuration = in.readLong();
+            result.mFileCount = in.readInt();
+            result.mIsSearching = in.readInt() != 0;
+            result.mRoot = in.readInt();
+            result.mMimeType = in.readInt();
+            result.mRepeatedPickTimes = in.readInt();
+            return result;
+        }
+
+        @Override
+        public PickResult[] newArray(int size) {
+            return new PickResult[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "PickResults{" +
+                "actionCount=" + mActionCount +
+                ", mDuration=" + mDuration +
+                ", mFileCount=" + mFileCount +
+                ", mIsSearching=" + mIsSearching +
+                ", mRoot=" + mRoot +
+                ", mMimeType=" + mMimeType +
+                ", mRepeatedPickTimes=" + mRepeatedPickTimes +
+                '}';
+    }
+}
diff --git a/src/com/android/documentsui/picker/UpdatePickResultTask.java b/src/com/android/documentsui/picker/UpdatePickResultTask.java
new file mode 100644
index 0000000..ff35137
--- /dev/null
+++ b/src/com/android/documentsui/picker/UpdatePickResultTask.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.picker;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.SystemClock;
+import com.android.documentsui.Metrics;
+
+// load & update mime type & repeatedly pick count in background
+public class UpdatePickResultTask extends AsyncTask<Void, Void, Void> {
+    private Context mContext;
+    private PickResult mPickResult;
+    private PickCountRecordStorage mPickCountRecord;
+
+    public UpdatePickResultTask(Context context, PickResult pickResult) {
+        this(context, pickResult, PickCountRecordStorage.create());
+    }
+
+    public UpdatePickResultTask(Context context, PickResult pickResult,
+        PickCountRecordStorage pickCountRecord) {
+        mContext = context.getApplicationContext();
+        mPickResult = pickResult;
+        mPickCountRecord = pickCountRecord;
+    }
+
+    @Override
+    protected void onPreExecute() {
+        mPickResult.increaseDuration(SystemClock.uptimeMillis());
+    }
+
+    @Override
+    protected Void doInBackground(Void... voids) {
+        Uri fileUri = mPickResult.getFileUri();
+        if (fileUri != null) {
+            mPickResult.setMimeType(
+                Metrics.sanitizeMime(mContext.getContentResolver().getType(fileUri)));
+
+            mPickResult.setRepeatedPickTimes(
+                mPickCountRecord.increasePickCountRecord(mContext, fileUri));
+        }
+        return null;
+    }
+
+    @Override
+    protected void onPostExecute(Void aVoid) {
+        Metrics.logPickResult(mPickResult);
+    }
+
+}
diff --git a/src/com/android/documentsui/queries/SearchChipViewManager.java b/src/com/android/documentsui/queries/SearchChipViewManager.java
index 0d315be..7dc0cc6 100644
--- a/src/com/android/documentsui/queries/SearchChipViewManager.java
+++ b/src/com/android/documentsui/queries/SearchChipViewManager.java
@@ -29,6 +29,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.documentsui.IconUtils;
+import com.android.documentsui.MetricConsts;
 import com.android.documentsui.R;
 import com.android.documentsui.base.MimeTypes;
 import com.android.documentsui.base.Shared;
@@ -53,10 +54,10 @@
 
     private static final int CHIP_MOVE_ANIMATION_DURATION = 250;
 
-    private static final int TYPE_IMAGES = 0;
-    private static final int TYPE_DOCUMENTS = 1;
-    private static final int TYPE_AUDIO = 2;
-    private static final int TYPE_VIDEOS = 3;
+    private static final int TYPE_IMAGES = MetricConsts.TYPE_CHIP_IMAGES;;
+    private static final int TYPE_DOCUMENTS = MetricConsts.TYPE_CHIP_DOCS;
+    private static final int TYPE_AUDIO = MetricConsts.TYPE_CHIP_AUDIOS;
+    private static final int TYPE_VIDEOS = MetricConsts.TYPE_CHIP_VIDEOS;
 
     private static final ChipComparator CHIP_COMPARATOR = new ChipComparator();
 
@@ -270,7 +271,7 @@
         reorderCheckedChips(chip, true /* hasAnim */);
 
         if (mListener != null) {
-            mListener.onChipCheckStateChanged();
+            mListener.onChipCheckStateChanged(v);
         }
     }
 
@@ -389,7 +390,7 @@
         /**
          * It will be triggered when the checked state of chips changes.
          */
-        void onChipCheckStateChanged();
+        void onChipCheckStateChanged(View v);
     }
 
     private static class ChipComparator implements Comparator<Chip> {
diff --git a/src/com/android/documentsui/queries/SearchViewManager.java b/src/com/android/documentsui/queries/SearchViewManager.java
index e46acb0..99d8286 100644
--- a/src/com/android/documentsui/queries/SearchViewManager.java
+++ b/src/com/android/documentsui/queries/SearchViewManager.java
@@ -122,7 +122,8 @@
         }
     }
 
-    private void onChipCheckedStateChanged() {
+    private void onChipCheckedStateChanged(View v) {
+        mListener.onSearchChipStateChanged(v);
         performSearch(mCurrentSearch);
     }
 
@@ -487,6 +488,10 @@
         return mCurrentSearch != null || mChipViewManager.hasCheckedItems();
     }
 
+    public boolean hasCheckedChip() {
+        return mChipViewManager.hasCheckedItems();
+    }
+
     public boolean isExpanded() {
         return mSearchExpanded;
     }
@@ -497,5 +502,7 @@
         void onSearchFinished();
 
         void onSearchViewChanged(boolean opened);
+
+        void onSearchChipStateChanged(View v);
     }
 }
diff --git a/src/com/android/documentsui/sorting/SortController.java b/src/com/android/documentsui/sorting/SortController.java
index c4ee018..ccfc3f1 100644
--- a/src/com/android/documentsui/sorting/SortController.java
+++ b/src/com/android/documentsui/sorting/SortController.java
@@ -21,6 +21,8 @@
 
 import android.view.View;
 
+import com.android.documentsui.BaseActivity;
+import com.android.documentsui.Injector;
 import com.android.documentsui.MetricConsts;
 import com.android.documentsui.Metrics;
 import com.android.documentsui.R;
@@ -62,17 +64,27 @@
             @ViewMode int initialMode,
             SortModel sortModel) {
 
+        final Injector<?> injector = ((BaseActivity)activity).getInjector();
         sortModel.setMetricRecorder((SortDimension dimension) -> {
+            int sortType = MetricConsts.USER_ACTION_UNKNOWN;
             switch (dimension.getId()) {
                 case SortModel.SORT_DIMENSION_ID_TITLE:
-                    Metrics.logUserAction(MetricConsts.USER_ACTION_SORT_NAME);
+                    sortType = MetricConsts.USER_ACTION_SORT_NAME;
                     break;
                 case SortModel.SORT_DIMENSION_ID_SIZE:
-                    Metrics.logUserAction(MetricConsts.USER_ACTION_SORT_SIZE);
+                    sortType = MetricConsts.USER_ACTION_SORT_SIZE;
                     break;
                 case SortModel.SORT_DIMENSION_ID_DATE:
-                    Metrics.logUserAction(MetricConsts.USER_ACTION_SORT_DATE);
+                    sortType = MetricConsts.USER_ACTION_SORT_DATE;
                     break;
+                case SortModel.SORT_DIMENSION_ID_FILE_TYPE:
+                    sortType = MetricConsts.USER_ACTION_SORT_TYPE;
+                    break;
+            }
+
+            Metrics.logUserAction(sortType);
+            if (injector.pickResult != null) {
+                injector.pickResult.increaseActionCount();
             }
         });
 
diff --git a/tests/common/com/android/documentsui/testing/TestSearchViewManager.java b/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
index 515fab1..ddc29b8 100644
--- a/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
+++ b/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.mock;
 
+import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.documentsui.base.DocumentStack;
@@ -51,6 +52,10 @@
                     @Override
                     public void onSearchViewChanged(boolean opened) {
                     }
+
+                    @Override
+                    public void onSearchChipStateChanged(View v) {
+                    }
                 },
                 new CommandInterceptor(new TestFeatures()), mock(ViewGroup.class),
                 null /* savedState */);
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index 41057ee..0d07061 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -16,8 +16,10 @@
 
 package com.android.documentsui.picker;
 
+import static org.mockito.Mockito.verify;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
 
 import android.app.Activity;
 import android.content.ClipData;
@@ -27,22 +29,30 @@
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Path;
 
+import androidx.fragment.app.FragmentActivity;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.documentsui.AbstractActionHandler;
+import com.android.documentsui.DocumentsAccess;
+import com.android.documentsui.Injector;
 import com.android.documentsui.R;
 import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.Lookup;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
 import com.android.documentsui.base.State.ActionType;
+import com.android.documentsui.picker.ActionHandler.Addons;
+import com.android.documentsui.queries.SearchViewManager;
+import com.android.documentsui.roots.ProvidersAccess;
 import com.android.documentsui.testing.DocumentStackAsserts;
 import com.android.documentsui.testing.TestEnv;
 import com.android.documentsui.testing.TestLastAccessedStorage;
 import com.android.documentsui.testing.TestProvidersAccess;
 import com.android.documentsui.testing.TestResolveInfo;
 
+import java.util.concurrent.Executor;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.Test;
@@ -56,17 +66,20 @@
 
     private TestEnv mEnv;
     private TestActivity mActivity;
-    private ActionHandler<TestActivity> mHandler;
+    private TestableActionHandler<TestActivity> mHandler;
     private TestLastAccessedStorage mLastAccessed;
+    private PickCountRecordStorage mPickCountRecord;
 
     @Before
     public void setUp() {
         mEnv = TestEnv.create();
         mActivity = TestActivity.create(mEnv);
         mEnv.providers.configurePm(mActivity.packageMgr);
+        mEnv.injector.pickResult = new PickResult();
         mLastAccessed = new TestLastAccessedStorage();
+        mPickCountRecord = mock(PickCountRecordStorage.class);
 
-        mHandler = new ActionHandler<>(
+        mHandler = new TestableActionHandler<>(
                 mActivity,
                 mEnv.state,
                 mEnv.providers,
@@ -74,7 +87,8 @@
                 mEnv.searchViewManager,
                 mEnv::lookupExecutor,
                 mEnv.injector,
-                mLastAccessed
+                mLastAccessed,
+                mPickCountRecord
         );
 
         mEnv.dialogs.confirmNext();
@@ -84,6 +98,32 @@
         AsyncTask.setDefaultExecutor(mEnv.mExecutor);
     }
 
+    private static class TestableActionHandler<T extends FragmentActivity & Addons>
+        extends ActionHandler {
+
+        private UpdatePickResultTask mTask;
+
+        TestableActionHandler(
+            T activity,
+            State state,
+            ProvidersAccess providers,
+            DocumentsAccess docs,
+            SearchViewManager searchMgr,
+            Lookup<String, Executor> executors,
+            Injector injector,
+            LastAccessedStorage lastAccessed,
+            PickCountRecordStorage pickCountRecordStorage) {
+            super(activity, state, providers, docs, searchMgr, executors, injector, lastAccessed);
+            mTask = new UpdatePickResultTask(
+                mActivity, mInjector.pickResult, pickCountRecordStorage);
+        }
+
+        @Override
+        public UpdatePickResultTask getUpdatePickResultTask() {
+            return mTask;
+        }
+    }
+
     @AfterClass
     public static void tearDownOnce() {
         AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@@ -199,6 +239,23 @@
     }
 
     @Test
+    public void testIncreasePickCountRecordCalled() throws Exception {
+        mEnv.state.action = State.ACTION_GET_CONTENT;
+        mEnv.state.stack.changeRoot(TestProvidersAccess.HOME);
+        mEnv.state.stack.push(TestEnv.FOLDER_1);
+
+        mActivity.finishedHandler.assertNotCalled();
+        mHandler.finishPicking(TestEnv.FILE_JPG.derivedUri);
+
+        mEnv.beforeAsserts();
+
+        verify(mPickCountRecord).increasePickCountRecord(
+            mActivity.getApplicationContext(), TestEnv.FILE_JPG.derivedUri);
+
+        mActivity.finishedHandler.assertCalled();
+    }
+
+    @Test
     public void testPickDocument_SetsCorrectResultAndFinishes_ActionPickCopyDestination()
             throws Exception {
 
diff --git a/tests/unit/com/android/documentsui/picker/PickCountRecordProviderTest.java b/tests/unit/com/android/documentsui/picker/PickCountRecordProviderTest.java
new file mode 100644
index 0000000..05cbe38
--- /dev/null
+++ b/tests/unit/com/android/documentsui/picker/PickCountRecordProviderTest.java
@@ -0,0 +1,89 @@
+package com.android.documentsui.picker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.test.rule.provider.ProviderTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PickCountRecordProviderTest {
+
+    private final static int FAKE_FILE_ID = 1234567;
+
+    private Uri mPickRecordUri;
+
+    @Rule
+    public ProviderTestRule mProviderTestRule = new ProviderTestRule.Builder(
+            PickCountRecordProvider.class, PickCountRecordProvider.AUTHORITY)
+            .build();
+
+    @Before
+    public void setUp() {
+        mPickRecordUri = PickCountRecordProvider.buildPickRecordUri(FAKE_FILE_ID);
+        final ContentValues values = new ContentValues();
+        values.clear();
+        values.put(PickCountRecordProvider.Columns.PICK_COUNT, 1);
+        mProviderTestRule.getResolver().insert(mPickRecordUri, values);
+    }
+
+    @After
+    public void tearDown() {
+        mProviderTestRule.getResolver().delete(mPickRecordUri, null, null);
+    }
+
+    @Test
+    public void testInsert() {
+        final ContentValues values = new ContentValues();
+        values.clear();
+        values.put(PickCountRecordProvider.Columns.PICK_COUNT, 3);
+        mProviderTestRule.getResolver().insert(mPickRecordUri, values);
+        Cursor cursor = mProviderTestRule.getResolver().query(
+            mPickRecordUri, null, null, null, null);
+        cursor.moveToNext();
+        int index = cursor.getColumnIndex(PickCountRecordProvider.Columns.PICK_COUNT);
+        assertThat(cursor.getInt(index)).isEqualTo(3);
+    }
+
+    @Test
+    public void testQuery() {
+        Cursor cursor = mProviderTestRule.getResolver().query(
+            mPickRecordUri, null, null, null, null);
+        cursor.moveToNext();
+        int index = cursor.getColumnIndex(PickCountRecordProvider.Columns.PICK_COUNT);
+        assertThat(cursor.getInt(index)).isEqualTo(1);
+    }
+
+    @Test
+    public void testDelete() {
+        Cursor cursor = mProviderTestRule.getResolver().query(
+            mPickRecordUri, null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        mProviderTestRule.getResolver().delete(mPickRecordUri, null, null);
+
+        cursor = mProviderTestRule.getResolver().query(
+            mPickRecordUri, null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testUpdate() {
+        boolean unsupportExcetionCaught = false;
+        try {
+            mProviderTestRule.getResolver().update(mPickRecordUri, null, null, null);
+        } catch (UnsupportedOperationException e) {
+            unsupportExcetionCaught = true;
+        }
+        assertThat(unsupportExcetionCaught).isTrue();
+    }
+}
\ No newline at end of file
diff --git a/tests/unit/com/android/documentsui/picker/PickResultTest.java b/tests/unit/com/android/documentsui/picker/PickResultTest.java
new file mode 100644
index 0000000..62a7956
--- /dev/null
+++ b/tests/unit/com/android/documentsui/picker/PickResultTest.java
@@ -0,0 +1,71 @@
+package com.android.documentsui.picker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class PickResultTest {
+    private PickResult mPickResult;
+
+    @Before
+    public void setUp() {
+        mPickResult = new PickResult();
+    }
+
+    @Test
+    public void testActionCount() {
+        mPickResult.increaseActionCount();
+        assertThat(mPickResult.getActionCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testDuration() {
+        mPickResult.setPickStartTime(487);
+        mPickResult.increaseDuration(9487);
+        assertThat(mPickResult.getDuration()).isEqualTo(9000);
+    }
+
+    @Test
+    public void testFileCount() {
+        mPickResult.setFileCount(10);
+        assertThat(mPickResult.getFileCount()).isEqualTo(10);
+    }
+
+    @Test
+    public void testIsSearching() {
+        mPickResult.setIsSearching(true);
+        assertThat(mPickResult.isSearching()).isTrue();
+    }
+
+    @Test
+    public void testRoot() {
+        mPickResult.setRoot(2);
+        assertThat(mPickResult.getRoot()).isEqualTo(2);
+    }
+
+    @Test
+    public void testMimeType() {
+        mPickResult.setMimeType(3);
+        assertThat(mPickResult.getMimeType()).isEqualTo(3);
+    }
+
+    @Test
+    public void testRepeatedlyPickTimes() {
+        mPickResult.setRepeatedPickTimes(4);
+        assertThat(mPickResult.getRepeatedPickTimes()).isEqualTo(4);
+    }
+
+    @Test
+    public void testFileUri() {
+        Uri fakeUri = new Uri.Builder().authority("test").appendPath("path").build();
+        mPickResult.setFileUri(fakeUri);
+        assertThat(mPickResult.getFileUri()).isEqualTo(fakeUri);
+    }
+}
diff --git a/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java b/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java
index 94163a9..b0043c3 100644
--- a/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java
+++ b/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java
@@ -39,6 +39,7 @@
 import android.os.Handler;
 import android.provider.DocumentsContract;
 import android.text.TextUtils;
+import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
@@ -99,6 +100,10 @@
             @Override
             public void onSearchViewChanged(boolean opened) {
             }
+
+            @Override
+            public void onSearchChipStateChanged(View v) {
+            }
         };
 
         ViewGroup chipGroup = mock(ViewGroup.class);