diff options
author | 2019-01-25 15:21:26 +0800 | |
---|---|---|
committer | 2019-02-20 20:08:51 +0800 | |
commit | e0ba46db948ffa0f55be23ac19a0386ff704798e (patch) | |
tree | a691b5ecc087a9bc7132e0f36ea7a71e668f67aa | |
parent | 2c0b485dd056da2d41841bb58488942ea413739d (diff) |
Migration to Westworld (2/2)
Add new metrics for Q
1. log app package name that launches docsui picker
2. log search type(chip, search history, search string)
3. log search mode(chip, keyword, both)
4. log PickResult including failed case
Bug: 111552654
Test: atest DocumentsUITests
Change-Id: Ibb93ec2b1a5c0b8d60c8e3b51e47cd21acd871c1
23 files changed, 1008 insertions, 31 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d18472672..6a54cd410 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 e166b8734..3d14f0ef9 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -36,6 +36,7 @@ import android.view.Menu; 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.Preferences; 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.SortController; 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 @@ public abstract class BaseActivity 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 abstract class BaseActivity 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 5c2a53752..d6c631247 100644 --- a/src/com/android/documentsui/Injector.java +++ b/src/com/android/documentsui/Injector.java @@ -31,6 +31,7 @@ import com.android.documentsui.base.Features; 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 class Injector<T extends ActionHandler> { 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 6db30fab1..f95f6e5ed 100644 --- a/src/com/android/documentsui/MetricConsts.java +++ b/src/com/android/documentsui/MetricConsts.java @@ -354,4 +354,40 @@ public class MetricConsts { @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 5737fcbfc..63442406c 100644 --- a/src/com/android/documentsui/Metrics.java +++ b/src/com/android/documentsui/Metrics.java @@ -38,6 +38,7 @@ import com.android.documentsui.base.Providers; 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 @@ public final class Metrics { 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 @@ public final class Metrics { } } + 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 c3d76a524..1523b7a00 100644 --- a/src/com/android/documentsui/base/Shared.java +++ b/src/com/android/documentsui/base/Shared.java @@ -108,6 +108,11 @@ public final class Shared { 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 5e0511507..a781b191a 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -620,6 +620,9 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On } 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 d4cf7ed56..4668c3206 100644 --- a/src/com/android/documentsui/picker/ActionHandler.java +++ b/src/com/android/documentsui/picker/ActionHandler.java @@ -82,22 +82,25 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH 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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH } } + 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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH @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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH // 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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH @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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH @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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH @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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH 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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH void saveDocument( String mimeType, String displayName, BooleanConsumer inProgressStateListener) { assert(mState.action == ACTION_CREATE); + mInjector.pickResult.increaseActionCount(); new CreatePickedDocumentTask( mActivity, mDocs, @@ -346,6 +387,7 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH // 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 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH 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 b61acf849..7565be05f 100644 --- a/src/com/android/documentsui/picker/ConfirmFragment.java +++ b/src/com/android/documentsui/picker/ConfirmFragment.java @@ -64,6 +64,7 @@ public class ConfirmFragment extends DialogFragment { 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 @@ public class ConfirmFragment extends DialogFragment { 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 @@ public class ConfirmFragment extends DialogFragment { 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 124279a5d..8a38e1393 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -25,6 +25,7 @@ import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION; 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 androidx.fragment.app.FragmentManager; 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.DocumentsApplication; 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 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { private Injector<ActionHandler<PickActivity>> mInjector; private SharedInputHandler mSharedInputHandler; - private LastAccessedStorage mLastAccessed; - public PickActivity() { super(R.layout.documents_activity, TAG); } @@ -113,7 +114,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { mInjector.menuManager, mInjector.messages); - mLastAccessed = LastAccessedStorage.create(); + mInjector.pickResult = getPickResult(icicle); mInjector.actions = new ActionHandler<>( this, mState, @@ -122,7 +123,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { mSearchManager, ProviderExecutor::forAuthority, mInjector, - mLastAccessed); + LastAccessedStorage.create()); mInjector.searchManager = mSearchManager; @@ -141,6 +142,41 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { 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 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { } @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 000000000..0a8e21505 --- /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 000000000..01476ee1f --- /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 9dd7f170e..3ac59748f 100644 --- a/src/com/android/documentsui/picker/PickFragment.java +++ b/src/com/android/documentsui/picker/PickFragment.java @@ -63,6 +63,7 @@ public class PickFragment extends Fragment { 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 000000000..af61a082f --- /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 000000000..ff3513779 --- /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 0d315beea..7dc0cc6ea 100644 --- a/src/com/android/documentsui/queries/SearchChipViewManager.java +++ b/src/com/android/documentsui/queries/SearchChipViewManager.java @@ -29,6 +29,7 @@ import androidx.annotation.Nullable; 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 @@ public class SearchChipViewManager { 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 @@ public class SearchChipViewManager { reorderCheckedChips(chip, true /* hasAnim */); if (mListener != null) { - mListener.onChipCheckStateChanged(); + mListener.onChipCheckStateChanged(v); } } @@ -389,7 +390,7 @@ public class SearchChipViewManager { /** * 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 e46acb023..99d8286fe 100644 --- a/src/com/android/documentsui/queries/SearchViewManager.java +++ b/src/com/android/documentsui/queries/SearchViewManager.java @@ -122,7 +122,8 @@ public class SearchViewManager implements } } - private void onChipCheckedStateChanged() { + private void onChipCheckedStateChanged(View v) { + mListener.onSearchChipStateChanged(v); performSearch(mCurrentSearch); } @@ -487,6 +488,10 @@ public class SearchViewManager implements return mCurrentSearch != null || mChipViewManager.hasCheckedItems(); } + public boolean hasCheckedChip() { + return mChipViewManager.hasCheckedItems(); + } + public boolean isExpanded() { return mSearchExpanded; } @@ -497,5 +502,7 @@ public class SearchViewManager implements 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 c4ee0188f..ccfc3f146 100644 --- a/src/com/android/documentsui/sorting/SortController.java +++ b/src/com/android/documentsui/sorting/SortController.java @@ -21,6 +21,8 @@ import androidx.fragment.app.FragmentActivity; 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 @@ public final class SortController { @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 515fab1e5..ddc29b897 100644 --- a/tests/common/com/android/documentsui/testing/TestSearchViewManager.java +++ b/tests/common/com/android/documentsui/testing/TestSearchViewManager.java @@ -18,6 +18,7 @@ package com.android.documentsui.testing; import static org.mockito.Mockito.mock; +import android.view.View; import android.view.ViewGroup; import com.android.documentsui.base.DocumentStack; @@ -51,6 +52,10 @@ public class TestSearchViewManager extends SearchViewManager { @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 2e183a803..2ef835109 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.os.AsyncTask; 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 @@ public class ActionHandlerTest { 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 @@ public class ActionHandlerTest { mEnv.searchViewManager, mEnv::lookupExecutor, mEnv.injector, - mLastAccessed + mLastAccessed, + mPickCountRecord ); mEnv.dialogs.confirmNext(); @@ -84,6 +98,32 @@ public class ActionHandlerTest { 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); @@ -206,6 +246,23 @@ public class ActionHandlerTest { } @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 000000000..05cbe38fa --- /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 000000000..62a795641 --- /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 94163a9dc..b0043c348 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.Bundle; 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 @@ public final class SearchViewManagerTest { @Override public void onSearchViewChanged(boolean opened) { } + + @Override + public void onSearchChipStateChanged(View v) { + } }; ViewGroup chipGroup = mock(ViewGroup.class); |