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);