From 35c066c9974d45f085b2a1e4e5f86a7515d4fd72 Mon Sep 17 00:00:00 2001
From: Doris Ling
Date: Tue, 4 Dec 2018 15:40:34 -0800
Subject: [PATCH] Add search to app info list.
- add a search option menu to the manage applications page.
- add a search filter to the app list adapter to remove any app whose
app name does not contain the search query.
Change-Id: Ie749daeef2cdc4a22fade45422ae90f44d00ceb2
Fixes: 119598311
Test: make RunSettingsRoboTests
---
res/drawable/ic_find_in_page_24px.xml | 27 +++++
res/menu/manage_apps.xml | 7 ++
.../ManageApplications.java | 74 +++++++++++-
.../ManageApplicationsTest.java | 114 +++++++++++++++++-
4 files changed, 220 insertions(+), 2 deletions(-)
create mode 100644 res/drawable/ic_find_in_page_24px.xml
diff --git a/res/drawable/ic_find_in_page_24px.xml b/res/drawable/ic_find_in_page_24px.xml
new file mode 100644
index 00000000000..18895e49a1c
--- /dev/null
+++ b/res/drawable/ic_find_in_page_24px.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/res/menu/manage_apps.xml b/res/menu/manage_apps.xml
index 99dba37d3a4..51189a0722d 100644
--- a/res/menu/manage_apps.xml
+++ b/res/menu/manage_apps.xml
@@ -15,6 +15,13 @@
-->
+ */
+ private class SearchFilter extends Filter {
+ @WorkerThread
+ @Override
+ protected FilterResults performFiltering(CharSequence query) {
+ final ArrayList matchedEntries;
+ if (TextUtils.isEmpty(query)) {
+ matchedEntries = mOriginalEntries;
+ } else {
+ matchedEntries = new ArrayList<>();
+ for (ApplicationsState.AppEntry entry : mOriginalEntries) {
+ if (entry.label.toLowerCase().contains(query.toString().toLowerCase())) {
+ matchedEntries.add(entry);
+ }
+ }
+ }
+ final FilterResults results = new FilterResults();
+ results.values = matchedEntries;
+ results.count = matchedEntries.size();
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ mEntries = (ArrayList) results.values;
+ notifyDataSetChanged();
+ }
+ }
}
private static class SummaryProvider implements SummaryLoader.SummaryProvider {
diff --git a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
index b3f5b5e72e8..4c9cacb6926 100644
--- a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
@@ -28,11 +28,12 @@ import static com.android.settings.applications.manageapplications.ManageApplica
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -48,9 +49,11 @@ import android.os.Looper;
import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.SearchView;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
@@ -158,6 +161,35 @@ public class ManageApplicationsTest {
verify(loadingContainer, never()).setVisibility(View.VISIBLE);
}
+ @Test
+ public void onCreateOptionsMenu_shouldSetSearchQueryListener() {
+ final SearchView searchView = mock(SearchView.class);
+ final MenuItem searchMenu = mock(MenuItem.class);
+ final MenuItem helpMenu = mock(MenuItem.class);
+ when(searchMenu.getActionView()).thenReturn(searchView);
+ when(mMenu.findItem(R.id.search_app_list_menu)).thenReturn(searchMenu);
+ when(mMenu.add(anyInt() /* groupId */, anyInt() /* itemId */, anyInt() /* order */,
+ anyInt() /* titleRes */)).thenReturn(helpMenu);
+ doReturn("Test").when(mFragment).getText(anyInt() /* resId */);
+ doNothing().when(mFragment).updateOptionsMenu();
+
+ mFragment.onCreateOptionsMenu(mMenu, mock(MenuInflater.class));
+
+ verify(searchView).setOnQueryTextListener(mFragment);
+ }
+
+ @Test
+ public void onQueryTextChange_shouldFilterSearchInApplicationsAdapter() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ mock(ManageApplications.ApplicationsAdapter.class);
+ final String query = "Test App";
+ ReflectionHelpers.setField(mFragment, "mApplications", adapter);
+
+ mFragment.onQueryTextChange(query);
+
+ verify(adapter).filterSearch(query);
+ }
+
@Test
public void updateLoading_appLoaded_shouldNotDelayCallToHandleLoadingContainer() {
ReflectionHelpers.setField(mFragment, "mLoadingContainer", mock(View.class));
@@ -249,6 +281,34 @@ public class ManageApplicationsTest {
verify(loadingViewController).showContent(true /* animate */);
}
+ @Test
+ public void onRebuildComplete_hasSearchQuery_shouldFilterSearch() {
+ final String query = "Test";
+ final RecyclerView recyclerView = mock(RecyclerView.class);
+ final View emptyView = mock(View.class);
+ ReflectionHelpers.setField(mFragment, "mRecyclerView", recyclerView);
+ ReflectionHelpers.setField(mFragment, "mEmptyView", emptyView);
+ final SearchView searchView = mock(SearchView.class);
+ ReflectionHelpers.setField(mFragment, "mSearchView", searchView);
+ when(searchView.isVisibleToUser()).thenReturn(true);
+ when(searchView.getQuery()).thenReturn(query);
+ final View listContainer = mock(View.class);
+ when(listContainer.getVisibility()).thenReturn(View.VISIBLE);
+ ReflectionHelpers.setField(mFragment, "mListContainer", listContainer);
+ ReflectionHelpers.setField(
+ mFragment, "mFilterAdapter", mock(ManageApplications.FilterSpinnerAdapter.class));
+ final ArrayList appList = new ArrayList<>();
+ appList.add(mock(ApplicationsState.AppEntry.class));
+ final ManageApplications.ApplicationsAdapter adapter =
+ spy(new ManageApplications.ApplicationsAdapter(mState, mFragment,
+ AppFilterRegistry.getInstance().get(FILTER_APPS_ALL),
+ null /* savedInstanceState */));
+
+ adapter.onRebuildComplete(appList);
+
+ verify(adapter).filterSearch(query);
+ }
+
@Test
public void notifyItemChange_recyclerViewIdle_shouldNotify() {
final RecyclerView recyclerView = mock(RecyclerView.class);
@@ -343,6 +403,48 @@ public class ManageApplicationsTest {
verify(holder, never()).updateSwitch(any(), anyBoolean(), anyBoolean());
}
+ @Test
+ public void applicationsAdapter_filterSearch_emptyQuery_shouldShowFullList() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ new ManageApplications.ApplicationsAdapter(
+ mState, mFragment, mock(AppFilterItem.class), Bundle.EMPTY);
+ final String[] appNames = {"Apricot", "Banana", "Cantaloupe", "Fig", "Mango"};
+ ReflectionHelpers.setField(adapter, "mOriginalEntries", getTestAppList(appNames));
+
+ adapter.filterSearch("");
+
+ assertThat(adapter.getItemCount()).isEqualTo(5);
+ }
+
+ @Test
+ public void applicationsAdapter_filterSearch_noMatch_shouldShowEmptyList() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ new ManageApplications.ApplicationsAdapter(
+ mState, mFragment, mock(AppFilterItem.class), Bundle.EMPTY);
+ final String[] appNames = {"Apricot", "Banana", "Cantaloupe", "Fig", "Mango"};
+ ReflectionHelpers.setField(adapter, "mOriginalEntries", getTestAppList(appNames));
+
+ adapter.filterSearch("orange");
+
+ assertThat(adapter.getItemCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void applicationsAdapter_filterSearch_shouldShowMatchedItemsOnly() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ new ManageApplications.ApplicationsAdapter(
+ mState, mFragment, mock(AppFilterItem.class), Bundle.EMPTY);
+ final String[] appNames = {"Apricot", "Banana", "Cantaloupe", "Fig", "Mango"};
+ ReflectionHelpers.setField(adapter, "mOriginalEntries", getTestAppList(appNames));
+
+ adapter.filterSearch("an");
+
+ assertThat(adapter.getItemCount()).isEqualTo(3);
+ assertThat(adapter.getAppEntry(0).label).isEqualTo("Banana");
+ assertThat(adapter.getAppEntry(1).label).isEqualTo("Cantaloupe");
+ assertThat(adapter.getAppEntry(2).label).isEqualTo("Mango");
+ }
+
@Test
public void sortOrderSavedOnRebuild() {
when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{});
@@ -375,4 +477,14 @@ public class ManageApplicationsTest {
return new RoboMenuItem(id);
});
}
+
+ private ArrayList getTestAppList(String[] appNames) {
+ final ArrayList appList = new ArrayList<>();
+ for (String name : appNames) {
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.label = name;
+ appList.add(appEntry);
+ }
+ return appList;
+ }
}