summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ivan Chiang <chiangi@google.com> 2018-12-12 11:10:01 +0800
committer Ivan Chiang <chiangi@google.com> 2018-12-21 18:14:13 +0800
commitf0ea0ed73a15a57136668519f7ed24df937e015c (patch)
treec24216ebe2240324df260fd2a1aff68636018a56
parent74e97e0f92d7fab09d7112622a5d7b118f103d61 (diff)
Add chips to support mime type query
* Add SearchChipViewManager * Implement mime type query with checked chips * Filter chips base on the derivedMimeTypes of the root If there is only one matched chip, hide the chip row. * Add chip move animation Fix: 111862054 Fix: 120895178 Test: atest DocumentsUITests Change-Id: If37a7c092eee101306963e4aa05810a86930c693
-rw-r--r--res/color/search_chip_background_color.xml22
-rw-r--r--res/color/search_chip_ripple_color.xml38
-rw-r--r--res/color/search_chip_stroke_color.xml23
-rw-r--r--res/color/search_chip_text_color.xml22
-rw-r--r--res/drawable/ic_check.xml25
-rw-r--r--res/layout/directory_header.xml3
-rw-r--r--res/layout/search_chip_item.xml31
-rw-r--r--res/layout/search_chip_row.xml34
-rw-r--r--res/values-night/colors.xml21
-rw-r--r--res/values/colors.xml4
-rw-r--r--res/values/dimens.xml5
-rw-r--r--res/values/strings.xml9
-rw-r--r--src/com/android/documentsui/AbstractActionHandler.java13
-rw-r--r--src/com/android/documentsui/BaseActivity.java13
-rw-r--r--src/com/android/documentsui/DirectoryLoader.java21
-rw-r--r--src/com/android/documentsui/GlobalSearchLoader.java18
-rw-r--r--src/com/android/documentsui/base/Shared.java5
-rw-r--r--src/com/android/documentsui/queries/SearchChipData.java46
-rw-r--r--src/com/android/documentsui/queries/SearchChipViewManager.java315
-rw-r--r--src/com/android/documentsui/queries/SearchViewManager.java96
-rw-r--r--tests/common/com/android/documentsui/testing/TestSearchViewManager.java18
-rw-r--r--tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java5
-rw-r--r--tests/unit/com/android/documentsui/queries/SearchChipViewManagerTest.java91
-rw-r--r--tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java75
24 files changed, 900 insertions, 53 deletions
diff --git a/res/color/search_chip_background_color.xml b/res/color/search_chip_background_color.xml
new file mode 100644
index 000000000..b45ebdf23
--- /dev/null
+++ b/res/color/search_chip_background_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 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
+
+ https://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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true" android:color="@color/chip_selected_background_color"/>
+ <item android:state_enabled="true" android:color="?attr/colorBackgroundFloating"/>
+ <item android:state_enabled="false" android:color="@color/g_light_grey"/>
+</selector> \ No newline at end of file
diff --git a/res/color/search_chip_ripple_color.xml b/res/color/search_chip_ripple_color.xml
new file mode 100644
index 000000000..acfb86655
--- /dev/null
+++ b/res/color/search_chip_ripple_color.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 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
+
+ https://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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Selected. -->
+ <item android:state_pressed="true" android:state_selected="true"
+ android:alpha="0.16" android:color="@color/chip_ripple_color"/>
+ <item android:state_focused="true" android:state_hovered="true" android:state_selected="true"
+ android:alpha="0.16" android:color="@color/chip_ripple_color"/>
+ <item android:state_focused="true" android:state_selected="true"
+ android:alpha="0.12" android:color="@color/chip_ripple_color"/>
+ <item android:state_hovered="true" android:state_selected="true"
+ android:alpha="0.04" android:color="@color/chip_ripple_color"/>
+ <item android:state_selected="true"
+ android:alpha="0.00" android:color="@color/chip_ripple_color"/>
+
+ <!-- Unselected. -->
+ <item android:state_pressed="true" android:alpha="0.16" android:color="@color/text_hint"/>
+ <item android:state_focused="true" android:state_hovered="true"
+ android:alpha="0.16" android:color="@color/text_hint"/>
+ <item android:state_focused="true" android:alpha="0.12" android:color="@color/text_hint"/>
+ <item android:state_hovered="true" android:alpha="0.04" android:color="@color/text_hint"/>
+ <item android:alpha="0.00" android:color="@color/text_hint"/>
+</selector> \ No newline at end of file
diff --git a/res/color/search_chip_stroke_color.xml b/res/color/search_chip_stroke_color.xml
new file mode 100644
index 000000000..b5e5d7886
--- /dev/null
+++ b/res/color/search_chip_stroke_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 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
+
+ https://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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true" android:color="@android:color/transparent"/>
+ <item android:state_pressed="true" android:color="@android:color/transparent"/>
+ <item android:state_enabled="true" android:color="@color/chip_stroke_color"/>
+ <item android:state_enabled="false" android:color="@android:color/transparent"/>
+</selector> \ No newline at end of file
diff --git a/res/color/search_chip_text_color.xml b/res/color/search_chip_text_color.xml
new file mode 100644
index 000000000..b9893eda8
--- /dev/null
+++ b/res/color/search_chip_text_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 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
+
+ https://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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true" android:color="?attr/colorAccent"/>
+ <item android:state_enabled="true" android:color="?attr/colorOnSurface"/>
+ <item android:state_enabled="false" android:color="@color/text_hint_dark"/>
+</selector> \ No newline at end of file
diff --git a/res/drawable/ic_check.xml b/res/drawable/ic_check.xml
new file mode 100644
index 000000000..fd378eeb0
--- /dev/null
+++ b/res/drawable/ic_check.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2018 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorAccent">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="vM9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+</vector>
diff --git a/res/layout/directory_header.xml b/res/layout/directory_header.xml
index 3a7c1e68d..de1da718f 100644
--- a/res/layout/directory_header.xml
+++ b/res/layout/directory_header.xml
@@ -21,6 +21,9 @@
<!-- used for top padding. -->
<include layout="@layout/action_bar_space"/>
+ <!-- used for search chip. -->
+ <include layout="@layout/search_chip_row"/>
+
<TextView
android:id="@+id/header_title"
android:layout_width="wrap_content"
diff --git a/res/layout/search_chip_item.xml b/res/layout/search_chip_item.xml
new file mode 100644
index 000000000..d80d382a1
--- /dev/null
+++ b/res/layout/search_chip_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<com.google.android.material.chip.Chip
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:checkable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/search_chip_text_color"
+ app:checkedIcon="@drawable/ic_check"
+ app:chipBackgroundColor="@color/search_chip_background_color"
+ app:chipCornerRadius="8dp"
+ app:chipStrokeColor="@color/search_chip_stroke_color"
+ app:chipStrokeWidth="1dp"
+ app:iconStartPadding="@dimen/search_chip_icon_padding"
+ app:rippleColor="@color/search_chip_ripple_color"
+/> \ No newline at end of file
diff --git a/res/layout/search_chip_row.xml b/res/layout/search_chip_row.xml
new file mode 100644
index 000000000..24202b676
--- /dev/null
+++ b/res/layout/search_chip_row.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<HorizontalScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="none">
+ <com.google.android.material.chip.ChipGroup
+ android:id="@+id/search_chip_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/search_chip_group_margin"
+ android:layout_marginBottom="@dimen/search_chip_group_margin"
+ android:paddingStart="@dimen/search_chip_spacing"
+ android:paddingEnd="@dimen/search_chip_spacing"
+ android:gravity="center"
+ app:chipSpacing="@dimen/search_chip_spacing"
+ app:singleLine="true"/>
+</HorizontalScrollView> \ No newline at end of file
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
new file mode 100644
index 000000000..9acc28059
--- /dev/null
+++ b/res/values-night/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<resources>
+ <color name="chip_stroke_color">@android:color/transparent</color>
+ <color name="chip_selected_background_color">#FF3D4657</color>
+ <color name="chip_ripple_color">#FF5195EA</color>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index a55081547..9cd64c1f5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -92,4 +92,8 @@
<color name="inspector_section_divider">#E0E0E0</color>
<color name="inspector_title_background">#40000000</color>
<color name="inspector_debug_mode_color">#607d8b</color>
+
+ <color name="chip_stroke_color">#FFDADCE0</color>
+ <color name="chip_selected_background_color">#FFE8F0FE</color>
+ <color name="chip_ripple_color">#FF4285f4</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 9a885d174..bc3ab5f65 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -79,4 +79,9 @@
<dimen name="root_info_header_height">48dp</dimen>
<dimen name="root_info_header_horizontal_padding">24dp</dimen>
+
+ <dimen name="search_chip_group_margin">8dp</dimen>
+ <dimen name="search_chip_spacing">8dp</dimen>
+ <dimen name="search_chip_icon_padding">4dp</dimen>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6106682bd..b568a553c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -412,4 +412,13 @@
<string name="root_info_header_app">Files from <xliff:g id="label" example="Drive">%1$s</xliff:g></string>
<!-- Header title for list of documents 3 party provider root eg. Drive, Box. with root summary.[CHAR_LIMIT=60] -->
<string name="root_info_header_app_with_summary">Files from <xliff:g id="label" example="Drive">%1$s</xliff:g> / <xliff:g id="summary" example="example@com">%2$s</xliff:g></string>
+
+ <!-- Title for images chip. [CHAR_LIMIT=25] -->
+ <string name="chip_title_images">Images</string>
+ <!-- Title for audio chip. [CHAR_LIMIT=25] -->
+ <string name="chip_title_audio">Audio</string>
+ <!-- Title for videos chip. [CHAR_LIMIT=25] -->
+ <string name="chip_title_videos">Videos</string>
+ <!-- Title for image chip. [CHAR_LIMIT=25] -->
+ <string name="chip_title_documents">Documents</string>
</resources>
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index a956b1717..b6ea20e6b 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -566,19 +566,17 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
Context context = mActivity;
if (mState.stack.isRecents()) {
-
if (mSearchMgr.isSearching()) {
if (DEBUG) {
- Log.d(TAG, "Creating new GlobalSearchloader.");
+ Log.d(TAG, "Creating new GlobalSearchLoader.");
}
-
return new GlobalSearchLoader(
context,
mProviders,
mState,
mExecutors,
mInjector.fileTypeLookup,
- mSearchMgr.getCurrentSearch());
+ mSearchMgr.buildQueryArgs());
} else {
if (DEBUG) {
Log.d(TAG, "Creating new loader recents.");
@@ -591,7 +589,6 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
mInjector.fileTypeLookup);
}
} else {
-
Uri contentsUri = mSearchMgr.isSearching()
? DocumentsContract.buildSearchDocumentsUri(
mState.stack.getRoot().authority,
@@ -601,6 +598,10 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
mState.stack.peek().authority,
mState.stack.peek().documentId);
+ final Bundle queryArgs = mSearchMgr.isSearching()
+ ? mSearchMgr.buildQueryArgs()
+ : null;
+
if (mInjector.config.managedModeEnabled(mState.stack)) {
contentsUri = DocumentsContract.setManageMode(contentsUri);
}
@@ -618,7 +619,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
mState.sortModel,
mInjector.fileTypeLookup,
mContentLock,
- mSearchMgr.isSearching());
+ queryArgs);
}
}
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 8b8b02979..20d8f0d6f 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -46,6 +46,7 @@ import androidx.fragment.app.Fragment;
import com.android.documentsui.AbstractActionHandler.CommonAddons;
import com.android.documentsui.Injector.Injected;
import com.android.documentsui.NavigationViewManager.Breadcrumb;
+import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.RootInfo;
@@ -61,13 +62,13 @@ import com.android.documentsui.prefs.ScopedPreferences;
import com.android.documentsui.queries.CommandInterceptor;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.queries.SearchViewManager.SearchManagerListener;
-import com.android.documentsui.R;
import com.android.documentsui.roots.ProvidersCache;
import com.android.documentsui.sidebar.RootsFragment;
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.ChipGroup;
import java.util.ArrayList;
import java.util.Date;
@@ -188,7 +189,12 @@ public abstract class BaseActivity
mInjector.features,
mInjector.debugHelper::toggleDebugMode,
cmdInterceptor);
- mSearchManager = new SearchViewManager(searchListener, queryInterceptor, icicle);
+
+ ChipGroup chipGroup = findViewById(R.id.search_chip_group);
+ mSearchManager = new SearchViewManager(searchListener, queryInterceptor,
+ chipGroup, icicle);
+ mSearchManager.updateChips(getCurrentRoot().derivedMimeTypes);
+
mSortController = SortController.create(this, mState.derivedMode, mState.sortModel);
mPreferencesMonitor = new PreferencesMonitor(
@@ -327,7 +333,10 @@ public abstract class BaseActivity
if (appBarLayout != null) {
appBarLayout.setExpanded(true);
}
+
updateHeaderTitle();
+
+ mSearchManager.updateChips(root.derivedMimeTypes);
}
@Override
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 233d4d138..4f12c4678 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -21,20 +21,21 @@ import static com.android.documentsui.base.SharedMinimal.VERBOSE;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.RemoteException;
-import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.util.Log;
+import androidx.loader.content.AsyncTaskLoader;
+
import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.DocumentInfo;
@@ -45,10 +46,6 @@ import com.android.documentsui.base.RootInfo;
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.sorting.SortModel;
-import android.os.FileUtils;
-
-import androidx.loader.content.AsyncTaskLoader;
-
import java.util.concurrent.Executor;
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
@@ -63,6 +60,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
private final SortModel mModel;
private final Lookup<String, String> mFileTypeLookup;
private final boolean mSearchMode;
+ private final Bundle mQueryArgs;
private DocumentInfo mDoc;
private CancellationSignal mSignal;
@@ -79,7 +77,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
SortModel model,
Lookup<String, String> fileTypeLookup,
ContentLock lock,
- boolean inSearchMode) {
+ Bundle queryArgs) {
super(context);
mFeatures = features;
@@ -88,7 +86,8 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
mModel = model;
mDoc = doc;
mFileTypeLookup = fileTypeLookup;
- mSearchMode = inSearchMode;
+ mSearchMode = queryArgs != null;
+ mQueryArgs = queryArgs;
mObserver = new LockingContentObserver(lock, this::onContentChanged);
}
@@ -121,15 +120,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
}
result.client = client;
- Resources resources = getContext().getResources();
-
final Bundle queryArgs = new Bundle();
mModel.addQuerySortArgs(queryArgs);
if (mSearchMode) {
- // add the search string into the query bundle.
- queryArgs.putString(DocumentsContract.QUERY_ARG_DISPLAY_NAME,
- DocumentsContract.getSearchDocumentsQuery(mUri));
+ queryArgs.putAll(mQueryArgs);
}
if (mFeatures.isContentPagingEnabled()) {
diff --git a/src/com/android/documentsui/GlobalSearchLoader.java b/src/com/android/documentsui/GlobalSearchLoader.java
index 364d5ebc4..073b3d0f3 100644
--- a/src/com/android/documentsui/GlobalSearchLoader.java
+++ b/src/com/android/documentsui/GlobalSearchLoader.java
@@ -39,7 +39,7 @@ import java.util.concurrent.Executor;
* {@link android.provider.DocumentsProvider}.
*/
public class GlobalSearchLoader extends MultiRootDocumentsLoader {
- private final String mSearchString;
+ private final Bundle mQueryArgs;
/*
* Create the loader to query multiple roots support
@@ -52,14 +52,14 @@ public class GlobalSearchLoader extends MultiRootDocumentsLoader {
* @param state current state
* @param features the feature flags
* @param executors the executors of authorities
- * @param fileTypeMap the map of mime types and file types.
- * @param searchString the string for searching
+ * @param fileTypeMap the map of mime types and file types
+ * @param queryArgs the bundle of query arguments
*/
- public GlobalSearchLoader(Context context, ProvidersAccess providers, State state,
+ GlobalSearchLoader(Context context, ProvidersAccess providers, State state,
Lookup<String, Executor> executors, Lookup<String, String> fileTypeMap,
- String searchString) {
+ @NonNull Bundle queryArgs) {
super(context, providers, state, executors, fileTypeMap);
- mSearchString = searchString;
+ mQueryArgs = queryArgs;
}
@Override
@@ -91,14 +91,16 @@ public class GlobalSearchLoader extends MultiRootDocumentsLoader {
@Override
protected void addQueryArgs(@NonNull Bundle queryArgs) {
- queryArgs.putString(DocumentsContract.QUERY_ARG_DISPLAY_NAME, mSearchString);
queryArgs.putBoolean(DocumentsContract.QUERY_ARG_EXCLUDE_MEDIA, true);
+ queryArgs.putAll(mQueryArgs);
}
@Override
protected Uri getQueryUri(RootInfo rootInfo) {
+ // For the new querySearchDocuments, we put the query string into queryArgs.
+ // Use the empty string to build the query uri.
return DocumentsContract.buildSearchDocumentsUri(authority,
- rootInfo.rootId, mSearchString);
+ rootInfo.rootId, "" /* query */);
}
@Override
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index 67bc4b7f8..546a5cb9e 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -78,6 +78,11 @@ public final class Shared {
public static final String EXTRA_QUERY = "query";
/**
+ * Extra flag used to store chip's title of type String array in the bundle.
+ */
+ public static final String EXTRA_QUERY_CHIPS = "query_chips";
+
+ /**
* Extra flag used to store state of type State in the bundle.
*/
public static final String EXTRA_STATE = "state";
diff --git a/src/com/android/documentsui/queries/SearchChipData.java b/src/com/android/documentsui/queries/SearchChipData.java
new file mode 100644
index 000000000..1ce9b74ed
--- /dev/null
+++ b/src/com/android/documentsui/queries/SearchChipData.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.queries;
+
+/**
+ * A data class stored data which search chip row required.
+ * Used by {@link SearchChipViewManager}.
+ */
+public class SearchChipData {
+
+ private final int mChipType;
+ private final int mTitleRes;
+ private final String[] mMimeTypes;
+
+ public SearchChipData(int chipType, int titleRes, String[] mimeTypes) {
+ mChipType = chipType;
+ mTitleRes = titleRes;
+ mMimeTypes = mimeTypes;
+ }
+
+ public final int getTitleRes() {
+ return mTitleRes;
+ }
+
+ public final String[] getMimeTypes() {
+ return mMimeTypes;
+ }
+
+ public final int getChipType() {
+ return mChipType;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/documentsui/queries/SearchChipViewManager.java b/src/com/android/documentsui/queries/SearchChipViewManager.java
new file mode 100644
index 000000000..d94600e91
--- /dev/null
+++ b/src/com/android/documentsui/queries/SearchChipViewManager.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2018 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.queries;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.documentsui.IconUtils;
+import com.android.documentsui.R;
+import com.android.documentsui.base.MimeTypes;
+import com.android.documentsui.base.Shared;
+
+import com.google.android.material.chip.Chip;
+import com.google.android.material.chip.ChipGroup;
+import com.google.common.primitives.Ints;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages search chip behavior.
+ */
+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 ChipComparator CHIP_COMPARATOR = new ChipComparator();
+
+ // we will get the icon drawable with the first mimeType
+ private static final String[] IMAGES_MIMETYPES = new String[]{"image/*"};
+ private static final String[] VIDEOS_MIMETYPES = new String[]{"video/*"};
+ private static final String[] AUDIO_MIMETYPES =
+ new String[]{"audio/*", "application/ogg", "application/x-flac"};
+ private static final String[] DOCUMENTS_MIMETYPES = new String[]{"application/*", "text/*"};
+
+ private static final Map<Integer, SearchChipData> sChipItems = new HashMap<>();
+
+ private final ChipGroup mChipGroup;
+ private SearchChipViewManagerListener mListener;
+
+ @VisibleForTesting
+ Set<SearchChipData> mCheckedChipItems = new HashSet<>();
+
+ static {
+ sChipItems.put(TYPE_IMAGES,
+ new SearchChipData(TYPE_IMAGES, R.string.chip_title_images, IMAGES_MIMETYPES));
+ sChipItems.put(TYPE_DOCUMENTS,
+ new SearchChipData(TYPE_DOCUMENTS, R.string.chip_title_documents,
+ DOCUMENTS_MIMETYPES));
+ sChipItems.put(TYPE_AUDIO,
+ new SearchChipData(TYPE_AUDIO, R.string.chip_title_audio, AUDIO_MIMETYPES));
+ sChipItems.put(TYPE_VIDEOS,
+ new SearchChipData(TYPE_VIDEOS, R.string.chip_title_videos, VIDEOS_MIMETYPES));
+ }
+
+
+
+ public SearchChipViewManager(@NonNull ChipGroup chipGroup) {
+ mChipGroup = chipGroup;
+ }
+
+ /**
+ * Restore the checked chip items by the saved state.
+ *
+ * @param savedState the saved state to restore.
+ */
+ public void restoreCheckedChipItems(Bundle savedState) {
+ final int[] chipTypes = savedState.getIntArray(Shared.EXTRA_QUERY_CHIPS);
+ if (chipTypes != null) {
+ clearCheckedChips();
+ for (int chipType : chipTypes) {
+ final SearchChipData chipData = sChipItems.get(chipType);
+ mCheckedChipItems.add(chipData);
+ setCheckedChip(chipData.getChipType());
+ }
+ }
+ }
+
+ /**
+ * Set the visibility of the chips row. If the count of chips is less than 2,
+ * we will hide the chips row.
+ *
+ * @param show the value to show/hide the chips row.
+ */
+ public void setChipsRowVisible(boolean show) {
+ // if there is only one matched chip, hide the chip group.
+ mChipGroup.setVisibility(show && mChipGroup.getChildCount() > 1 ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Check Whether the checked item list has contents.
+ *
+ * @return True, if the checked item list is not empty. Otherwise, return false.
+ */
+ public boolean hasCheckedItems() {
+ return !mCheckedChipItems.isEmpty();
+ }
+
+ /**
+ * Clear the checked state of Chips and the checked list.
+ */
+ public void clearCheckedChips() {
+ final int count = mChipGroup.getChildCount();
+ for (int i = 0; i < count; i++) {
+ Chip child = (Chip) mChipGroup.getChildAt(i);
+ setChipChecked(child, false /* isChecked */);
+ }
+ mCheckedChipItems.clear();
+ }
+
+ /**
+ * Get the mime types of checked chips
+ *
+ * @return the string array of mime types
+ */
+ public String[] getCheckedMimeTypes() {
+ final ArrayList<String> args = new ArrayList<>();
+ for (SearchChipData data : mCheckedChipItems) {
+ for (String mimeType : data.getMimeTypes()) {
+ args.add(mimeType);
+ }
+ }
+ return args.toArray(new String[0]);
+ }
+
+ /**
+ * Called when owning activity is saving state to be used to restore state during creation.
+ *
+ * @param state Bundle to save state
+ */
+ public void onSaveInstanceState(Bundle state) {
+ List<Integer> checkedChipList = new ArrayList<>();
+
+ for (SearchChipData item : mCheckedChipItems) {
+ checkedChipList.add(item.getChipType());
+ }
+
+ if (checkedChipList.size() > 0) {
+ state.putIntArray(Shared.EXTRA_QUERY_CHIPS, Ints.toArray(checkedChipList));
+ }
+ }
+
+ /**
+ * Update the search chips base on the mime types.
+ *
+ * @param acceptMimeTypes use this values to filter chips
+ */
+ public void updateChips(String[] acceptMimeTypes) {
+ final Context context = mChipGroup.getContext();
+ mChipGroup.removeAllViews();
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ for (SearchChipData chipData : sChipItems.values()) {
+ final String[] mimeTypes = chipData.getMimeTypes();
+ final boolean isMatched = MimeTypes.mimeMatches(acceptMimeTypes, mimeTypes);
+ if (isMatched) {
+ Chip chip = (Chip) inflater.inflate(R.layout.search_chip_item, mChipGroup, false);
+ bindChip(chip, chipData);
+ mChipGroup.addView(chip);
+ }
+ }
+ reorderCheckedChips(false /* hasAnim */);
+ }
+
+
+ /**
+ * Set the listener.
+ *
+ * @param listener the listener
+ */
+ public void setSearchChipViewManagerListener(SearchChipViewManagerListener listener) {
+ mListener = listener;
+ }
+
+ private static void setChipChecked(Chip chip, boolean isChecked) {
+ chip.setChecked(isChecked);
+ chip.setCheckedIconVisible(isChecked);
+ chip.setChipIconVisible(!isChecked);
+ }
+
+ private void setCheckedChip(int chipType) {
+ final int count = mChipGroup.getChildCount();
+ for (int i = 0; i < count; i++) {
+ Chip child = (Chip) mChipGroup.getChildAt(i);
+ SearchChipData item = (SearchChipData) child.getTag();
+ if (item.getChipType() == chipType) {
+ setChipChecked(child, true /* isChecked */);
+ break;
+ }
+ }
+ }
+
+ private void onChipClick(View v) {
+ final Chip chip = (Chip) v;
+ final SearchChipData item = (SearchChipData) chip.getTag();
+ if (chip.isChecked()) {
+ mCheckedChipItems.add(item);
+ } else {
+ mCheckedChipItems.remove(item);
+ }
+
+ setChipChecked(chip, chip.isChecked());
+ reorderCheckedChips(true /* hasAnim */);
+ if (mListener != null) {
+ mListener.onChipCheckStateChanged();
+ }
+ }
+
+ private void bindChip(Chip chip, SearchChipData chipData) {
+ chip.setTag(chipData);
+ chip.setText(mChipGroup.getContext().getString(chipData.getTitleRes()));
+ // get the icon drawable with the first mimeType
+ chip.setChipIcon(
+ IconUtils.loadMimeIcon(mChipGroup.getContext(), chipData.getMimeTypes()[0]));
+ chip.setOnClickListener(this::onChipClick);
+
+ if (mCheckedChipItems.contains(chipData)) {
+ setChipChecked(chip, true);
+ }
+ }
+
+ /**
+ * Reorder the chips in chip group. The checked chip has higher order.
+ *
+ * @param hasAnim if true, play move animation. Otherwise, not.
+ */
+ private void reorderCheckedChips(boolean hasAnim) {
+ final ArrayList<Chip> chipList = new ArrayList<>();
+ final int count = mChipGroup.getChildCount();
+ final boolean playAnimation = hasAnim && mChipGroup.isAttachedToWindow();
+ final Map<String, Float> originalXList = new HashMap<>();
+ Chip item;
+ for (int i = 0; i < count; i++) {
+ item = (Chip) mChipGroup.getChildAt(i);
+ chipList.add(item);
+ if (playAnimation) {
+ originalXList.put(item.getText().toString(), item.getX());
+ }
+ }
+
+ final int chipSpacing = mChipGroup.getChipSpacingHorizontal();
+ float lastX = chipList.get(0).getX();
+ Collections.sort(chipList, CHIP_COMPARATOR);
+
+ mChipGroup.removeAllViews();
+ for (Chip chip : chipList) {
+ mChipGroup.addView(chip);
+ if (playAnimation) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(chip, "x",
+ originalXList.get(chip.getText().toString()), lastX);
+ animator.setDuration(CHIP_MOVE_ANIMATION_DURATION);
+ animator.start();
+ }
+ lastX += chipSpacing + chip.getMeasuredWidth();
+ }
+
+ if (playAnimation) {
+ // Let the first checked chip can be seen.
+ View parent = (View) mChipGroup.getParent();
+ if (parent != null && parent instanceof HorizontalScrollView) {
+ ((HorizontalScrollView) mChipGroup.getParent()).smoothScrollTo(0, 0);
+ }
+ }
+ }
+
+ /**
+ * The listener of SearchChipViewManager.
+ */
+ public interface SearchChipViewManagerListener {
+ /**
+ * It will be triggered when the checked state of chips changes.
+ */
+ void onChipCheckStateChanged();
+ }
+
+ private static class ChipComparator implements Comparator<Chip> {
+
+ @Override
+ public int compare(Chip lhs, Chip rhs) {
+ return (lhs.isChecked() == rhs.isChecked()) ? 0 : (lhs.isChecked() ? -1 : 1);
+ }
+ }
+}
diff --git a/src/com/android/documentsui/queries/SearchViewManager.java b/src/com/android/documentsui/queries/SearchViewManager.java
index 70c893a43..9cabf2c0a 100644
--- a/src/com/android/documentsui/queries/SearchViewManager.java
+++ b/src/com/android/documentsui/queries/SearchViewManager.java
@@ -18,10 +18,10 @@ package com.android.documentsui.queries;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
-import androidx.annotation.Nullable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
import android.text.TextUtils;
import android.util.Log;
@@ -32,16 +32,20 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.widget.SearchView;
+import androidx.appcompat.widget.SearchView.OnQueryTextListener;
+
import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.widget.SearchView;
-import androidx.appcompat.widget.SearchView.OnQueryTextListener;
+
+import com.google.android.material.chip.ChipGroup;
import java.util.Timer;
import java.util.TimerTask;
@@ -60,6 +64,7 @@ public class SearchViewManager implements
private final SearchManagerListener mListener;
private final EventHandler<String> mCommandProcessor;
+ private final SearchChipViewManager mChipViewManager;
private final Timer mTimer;
private final Handler mUiHandler;
@@ -80,15 +85,17 @@ public class SearchViewManager implements
public SearchViewManager(
SearchManagerListener listener,
EventHandler<String> commandProcessor,
+ ChipGroup chipGroup,
@Nullable Bundle savedState) {
- this(listener, commandProcessor, savedState, new Timer(),
- new Handler(Looper.getMainLooper()));
+ this(listener, commandProcessor, new SearchChipViewManager(chipGroup), savedState,
+ new Timer(), new Handler(Looper.getMainLooper()));
}
@VisibleForTesting
protected SearchViewManager(
SearchManagerListener listener,
EventHandler<String> commandProcessor,
+ SearchChipViewManager chipViewManager,
@Nullable Bundle savedState,
Timer timer,
Handler handler) {
@@ -100,7 +107,49 @@ public class SearchViewManager implements
mCommandProcessor = commandProcessor;
mTimer = timer;
mUiHandler = handler;
- mCurrentSearch = savedState != null ? savedState.getString(Shared.EXTRA_QUERY) : null;
+ mChipViewManager = chipViewManager;
+ mChipViewManager.setSearchChipViewManagerListener(this::onChipCheckedStateChanged);
+
+ if (savedState != null) {
+ mCurrentSearch = savedState.getString(Shared.EXTRA_QUERY);
+ mChipViewManager.restoreCheckedChipItems(savedState);
+ } else {
+ mCurrentSearch = null;
+ }
+ }
+
+ private void onChipCheckedStateChanged() {
+ performSearch(mCurrentSearch);
+ }
+
+ /**
+ * Build the bundle of query arguments.
+ * Example: search string and mime types
+ *
+ * @return the bundle of query arguments
+ */
+ public Bundle buildQueryArgs() {
+ final Bundle queryArgs = new Bundle();
+ if (!TextUtils.isEmpty(mCurrentSearch)) {
+ queryArgs.putString(DocumentsContract.QUERY_ARG_DISPLAY_NAME, mCurrentSearch);
+ }
+
+ final String[] checkedMimeTypes = mChipViewManager.getCheckedMimeTypes();
+ if (checkedMimeTypes != null && checkedMimeTypes.length > 0) {
+ queryArgs.putStringArray(DocumentsContract.QUERY_ARG_MIME_TYPES, checkedMimeTypes);
+ }
+ return queryArgs;
+ }
+
+ /**
+ * Update the search chips base on the acceptMimeTypes.
+ * If the count of matched chips is less than two, we will
+ * hide the chip row.
+ *
+ * @param acceptMimeTypes use to filter chips
+ */
+ public void updateChips(String[] acceptMimeTypes) {
+ mChipViewManager.updateChips(acceptMimeTypes);
}
public void install(Menu menu, boolean isFullBarSearch) {
@@ -190,6 +239,15 @@ public class SearchViewManager implements
}
mMenuItem.setVisible(supportsSearch);
+
+ // Only Storage roots, Downloads root, media roots and recent root
+ // support mime type query now.
+ // TODO: b/121234248 add check for whether the root supports new search method.
+ if (supportsSearch && !root.isDownloads() && !root.isStorage() && !root.isLibrary()) {
+ supportsSearch = false;
+ }
+
+ mChipViewManager.setChipsRowVisible(supportsSearch);
}
/**
@@ -204,11 +262,12 @@ public class SearchViewManager implements
mSearchView.setQuery("", false);
if (mFullBar) {
- onClose();
+ onClose();
} else {
// Causes calling onClose(). onClose() is triggering directory content update.
mSearchView.setIconified(true);
}
+
return true;
}
return false;
@@ -253,6 +312,7 @@ public class SearchViewManager implements
/**
* Clears the search. Triggers refreshing of the directory content.
+ *
* @return True if the default behavior of clearing/dismissing SearchView should be overridden.
* False otherwise.
*/
@@ -265,7 +325,9 @@ public class SearchViewManager implements
}
// Refresh the directory if a search was done
- if (mCurrentSearch != null) {
+ if (mCurrentSearch != null || mChipViewManager.hasCheckedItems()) {
+ // Clear checked chips
+ mChipViewManager.clearCheckedChips();
mCurrentSearch = null;
mListener.onSearchChanged(mCurrentSearch);
}
@@ -282,10 +344,12 @@ public class SearchViewManager implements
/**
* Called when owning activity is saving state to be used to restore state during creation.
+ *
* @param state Bundle to save state too
*/
public void onSaveInstanceState(Bundle state) {
state.putString(Shared.EXTRA_QUERY, mCurrentSearch);
+ mChipViewManager.onSaveInstanceState(state);
}
/**
@@ -320,7 +384,7 @@ public class SearchViewManager implements
*/
@Override
public void onFocusChange(View v, boolean hasFocus) {
- if (!hasFocus) {
+ if (!hasFocus && !mChipViewManager.hasCheckedItems()) {
if (mCurrentSearch == null) {
mSearchView.setIconified(true);
} else if (TextUtils.isEmpty(mSearchView.getQuery())) {
@@ -351,13 +415,17 @@ public class SearchViewManager implements
@Override
public boolean onQueryTextChange(String newText) {
+ performSearch(newText);
+ return true;
+ }
+
+ private void performSearch(String newText) {
cancelQueuedSearch();
synchronized (mSearchLock) {
mQueuedSearchTask = createSearchTask(newText);
mTimer.schedule(mQueuedSearchTask, SEARCH_DELAY_MS);
}
- return true;
}
@Override
@@ -382,7 +450,7 @@ public class SearchViewManager implements
}
public boolean isSearching() {
- return mCurrentSearch != null;
+ return mCurrentSearch != null || mChipViewManager.hasCheckedItems();
}
public boolean isExpanded() {
@@ -391,7 +459,9 @@ public class SearchViewManager implements
public interface SearchManagerListener {
void onSearchChanged(@Nullable String query);
+
void onSearchFinished();
+
void onSearchViewChanged(boolean opened);
}
}
diff --git a/tests/common/com/android/documentsui/testing/TestSearchViewManager.java b/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
index 6c37ef7b2..af23b46ec 100644
--- a/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
+++ b/tests/common/com/android/documentsui/testing/TestSearchViewManager.java
@@ -16,10 +16,14 @@
package com.android.documentsui.testing;
+import static org.mockito.Mockito.mock;
+
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.queries.CommandInterceptor;
import com.android.documentsui.queries.SearchViewManager;
+import com.google.android.material.chip.ChipGroup;
+
/**
* Test copy of {@link com.android.documentsui.queries.SearchViewManager}
*
@@ -37,14 +41,20 @@ public class TestSearchViewManager extends SearchViewManager {
super(
new SearchManagerListener() {
@Override
- public void onSearchChanged(String query) { }
+ public void onSearchChanged(String query) {
+ }
+
@Override
- public void onSearchFinished() { }
+ public void onSearchFinished() {
+ }
+
@Override
- public void onSearchViewChanged(boolean opened) { }
+ public void onSearchViewChanged(boolean opened) {
+ }
},
new CommandInterceptor(new TestFeatures()),
- null);
+ mock(ChipGroup.class),
+ null /* savedState */);
}
@Override
diff --git a/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java b/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java
index 36a36ea7f..d0b8f1731 100644
--- a/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java
+++ b/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java
@@ -20,6 +20,7 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import android.database.Cursor;
+import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -62,8 +63,10 @@ public class GlobalSearchLoaderTest {
mEnv.state.action = State.ACTION_BROWSE;
mEnv.state.acceptMimes = new String[]{"*/*"};
+ final Bundle queryArgs = new Bundle();
+ queryArgs.putString(DocumentsContract.QUERY_ARG_DISPLAY_NAME, SEARCH_STRING);
mLoader = new GlobalSearchLoader(mActivity, mEnv.providers, mEnv.state,
- TestImmediateExecutor.createLookup(), new TestFileTypeLookup(), SEARCH_STRING);
+ TestImmediateExecutor.createLookup(), new TestFileTypeLookup(), queryArgs);
final DocumentInfo doc = mEnv.model.createFile(SEARCH_STRING + ".jpg", FILE_FLAG);
doc.lastModified = System.currentTimeMillis();
diff --git a/tests/unit/com/android/documentsui/queries/SearchChipViewManagerTest.java b/tests/unit/com/android/documentsui/queries/SearchChipViewManagerTest.java
new file mode 100644
index 000000000..bf11cc2a3
--- /dev/null
+++ b/tests/unit/com/android/documentsui/queries/SearchChipViewManagerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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.queries;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+
+import android.os.Bundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.base.MimeTypes;
+import com.android.documentsui.base.Shared;
+
+import com.google.android.material.chip.ChipGroup;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class SearchChipViewManagerTest {
+
+ private static final String[] TEST_MIMETYPES = new String[]{"image/png", "video/mpeg"};
+ private static int CHIP_TYPE = 1000;
+
+ private SearchChipViewManager mSearchChipViewManager;
+
+ @Before
+ public void setUp() {
+ ChipGroup chipGroup = mock(ChipGroup.class);
+ mSearchChipViewManager = new SearchChipViewManager(chipGroup);
+ }
+
+ @Test
+ public void testGetCheckedChipMimeTypes_HasCorrectValue() throws Exception {
+ mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
+ final String[] checkedMimeTypes = mSearchChipViewManager.getCheckedMimeTypes();
+ assertTrue(MimeTypes.mimeMatches(TEST_MIMETYPES, checkedMimeTypes[0]));
+ assertTrue(MimeTypes.mimeMatches(TEST_MIMETYPES, checkedMimeTypes[1]));
+ }
+
+ @Test
+ public void testRestoreCheckedChipItems_HasCorrectValue() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putIntArray(Shared.EXTRA_QUERY_CHIPS, new int[] {2});
+ mSearchChipViewManager.restoreCheckedChipItems(bundle);
+
+ assertEquals(1, mSearchChipViewManager.mCheckedChipItems.size());
+ Iterator<SearchChipData> iterator = mSearchChipViewManager.mCheckedChipItems.iterator();
+ assertEquals(2, iterator.next().getChipType());
+ }
+
+ @Test
+ public void testSaveInstanceState_HasCorrectValue() throws Exception {
+ mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
+ Bundle bundle = new Bundle();
+ mSearchChipViewManager.onSaveInstanceState(bundle);
+ final int[] chipTypes = bundle.getIntArray(Shared.EXTRA_QUERY_CHIPS);
+ assertEquals(1, chipTypes.length);
+ assertEquals(CHIP_TYPE, chipTypes[0]);
+ }
+
+ private static Set<SearchChipData> getFakeSearchChipDataList() {
+ final Set<SearchChipData> chipDataList = new HashSet<>();
+ chipDataList.add(new SearchChipData(CHIP_TYPE, 0, TEST_MIMETYPES));
+ return chipDataList;
+ }
+}
diff --git a/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java b/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java
index 00a2166f6..44ec387cb 100644
--- a/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java
+++ b/tests/unit/com/android/documentsui/queries/SearchViewManagerTest.java
@@ -16,28 +16,35 @@
package com.android.documentsui.queries;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
import android.os.Bundle;
import android.os.Handler;
+import android.provider.DocumentsContract;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.documentsui.base.EventHandler;
-import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.queries.SearchViewManager.SearchManagerListener;
import com.android.documentsui.testing.TestEventHandler;
import com.android.documentsui.testing.TestHandler;
import com.android.documentsui.testing.TestMenu;
import com.android.documentsui.testing.TestTimer;
+import com.google.android.material.chip.ChipGroup;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashSet;
+import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
@@ -49,6 +56,7 @@ public final class SearchViewManagerTest {
private TestTimer mTestTimer;
private TestHandler mTestHandler;
private SearchViewManager mSearchViewManager;
+ private SearchChipViewManager mSearchChipViewManager;
private boolean mListenerOnSearchChangedCalled;
@@ -63,14 +71,20 @@ public final class SearchViewManagerTest {
public void onSearchChanged(@Nullable String query) {
mListenerOnSearchChangedCalled = true;
}
+
@Override
- public void onSearchFinished() {}
+ public void onSearchFinished() {
+ }
+
@Override
- public void onSearchViewChanged(boolean opened) {}
+ public void onSearchViewChanged(boolean opened) {
+ }
};
- mSearchViewManager = new TestableSearchViewManager(
- searchListener, mTestEventHandler, null, mTestTimer, mTestHandler);
+ ChipGroup chipGroup = mock(ChipGroup.class);
+ mSearchChipViewManager = new SearchChipViewManager(chipGroup);
+ mSearchViewManager = new TestableSearchViewManager(searchListener, mTestEventHandler,
+ mSearchChipViewManager, null /* savedState */, mTestTimer, mTestHandler);
final TestMenu testMenu = TestMenu.create();
mSearchViewManager.install(testMenu, true);
@@ -80,10 +94,11 @@ public final class SearchViewManagerTest {
public TestableSearchViewManager(
SearchManagerListener listener,
EventHandler<String> commandProcessor,
+ SearchChipViewManager chipViewManager,
@Nullable Bundle savedState,
Timer timer,
Handler handler) {
- super(listener, commandProcessor, savedState, timer, handler);
+ super(listener, commandProcessor, chipViewManager, savedState, timer, handler);
}
@Override
@@ -113,6 +128,12 @@ public final class SearchViewManagerTest {
}
@Test
+ public void testIsSearching_TrueHasCheckedChip() throws Exception {
+ mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
+ assertTrue(mSearchViewManager.isSearching());
+ }
+
+ @Test
public void testIsSearching_FalseOnClick() throws Exception {
mSearchViewManager.onClick(null);
assertFalse(mSearchViewManager.isSearching());
@@ -214,4 +235,46 @@ public final class SearchViewManagerTest {
mSearchViewManager.onQueryTextSubmit("q");
assertFalse(mListenerOnSearchChangedCalled);
}
+
+ @Test
+ public void testCheckedChipItems_IsEmptyIfSearchCanceled() throws Exception {
+ mSearchViewManager.onClick(null);
+ mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
+ mSearchViewManager.cancelSearch();
+ fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
+ assertTrue(!mSearchChipViewManager.hasCheckedItems());
+ }
+
+ @Test
+ public void testBuildQueryArgs_hasSearchString() throws Exception {
+ final String query = "q";
+ mSearchViewManager.onClick(null);
+ mSearchViewManager.onQueryTextChange("q");
+ fastForwardTo(SearchViewManager.SEARCH_DELAY_MS);
+
+ final Bundle queryArgs = mSearchViewManager.buildQueryArgs();
+ assertFalse(queryArgs.isEmpty());
+
+ final String queryString = queryArgs.getString(DocumentsContract.QUERY_ARG_DISPLAY_NAME);
+ assertEquals(query, queryString);
+ }
+
+ @Test
+ public void testBuildQueryArgs_hasMimeType() throws Exception {
+ mSearchViewManager.onClick(null);
+ mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList();
+
+ final Bundle queryArgs = mSearchViewManager.buildQueryArgs();
+ assertFalse(queryArgs.isEmpty());
+
+ final String[] mimeTypes = queryArgs.getStringArray(DocumentsContract.QUERY_ARG_MIME_TYPES);
+ assertTrue(mimeTypes.length > 0);
+ assertEquals("image/*", mimeTypes[0]);
+ }
+
+ private static Set<SearchChipData> getFakeSearchChipDataList() {
+ final Set<SearchChipData> chipDataList = new HashSet<>();
+ chipDataList.add(new SearchChipData(0 /* chipType */, 0, new String[]{"image/*"}));
+ return chipDataList;
+ }
}