diff --git a/src/com/android/settings/search2/SavedQueryViewHolder.java b/src/com/android/settings/search2/SavedQueryViewHolder.java index a32ed0523f8..15d6f8b63c7 100644 --- a/src/com/android/settings/search2/SavedQueryViewHolder.java +++ b/src/com/android/settings/search2/SavedQueryViewHolder.java @@ -25,7 +25,7 @@ public class SavedQueryViewHolder extends SearchViewHolder { public SavedQueryViewHolder(View view) { super(view); - titleView = (TextView) view.findViewById(android.R.id.title); + titleView = view.findViewById(android.R.id.title); } @Override diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java index 957713bd6b0..7eb9698a964 100644 --- a/src/com/android/settings/search2/SearchFragment.java +++ b/src/com/android/settings/search2/SearchFragment.java @@ -45,9 +45,8 @@ import com.android.settings.overlay.FeatureFactory; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -public class SearchFragment extends InstrumentedFragment implements - SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks> -{ +public class SearchFragment extends InstrumentedFragment implements SearchView.OnQueryTextListener, + LoaderManager.LoaderCallbacks> { private static final String TAG = "SearchFragment"; @VisibleForTesting @@ -66,7 +65,7 @@ public class SearchFragment extends InstrumentedFragment implements private static final int NUM_QUERY_LOADERS = 2; @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);; + AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS); // Logging @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -82,7 +81,7 @@ public class SearchFragment extends InstrumentedFragment implements private int mResultClickCount; private MetricsFeatureProvider mMetricsFeatureProvider; - @VisibleForTesting (otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) SearchFeatureProvider mSearchFeatureProvider; private SearchResultsAdapter mSearchAdapter; @@ -93,8 +92,7 @@ public class SearchFragment extends InstrumentedFragment implements private LinearLayout mNoResultsView; @VisibleForTesting - final RecyclerView.OnScrollListener mScrollListener = - new RecyclerView.OnScrollListener() { + final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (dy != 0) { @@ -120,7 +118,7 @@ public class SearchFragment extends InstrumentedFragment implements super.onCreate(savedInstanceState); setHasOptionsMenu(true); mSearchAdapter = new SearchResultsAdapter(this); - + mSearchFeatureProvider.initFeedbackButton(); final LoaderManager loaderManager = getLoaderManager(); @@ -155,12 +153,12 @@ public class SearchFragment extends InstrumentedFragment implements public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.search_panel_2, container, false); - mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results); + mResultsRecyclerView = view.findViewById(R.id.list_results); mResultsRecyclerView.setAdapter(mSearchAdapter); mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); mResultsRecyclerView.addOnScrollListener(mScrollListener); - mNoResultsView = (LinearLayout) view.findViewById(R.id.no_results_layout); + mNoResultsView = view.findViewById(R.id.no_results_layout); return view; } @@ -245,16 +243,20 @@ public class SearchFragment extends InstrumentedFragment implements @Override public void onLoadFinished(Loader> loader, List data) { - mSearchAdapter.addResultsToMap(data, loader.getClass().getName()); - - if (mUnfinishedLoadersCount.decrementAndGet() == 0) { - final int resultCount = mSearchAdapter.mergeResults(); - mSearchFeatureProvider.showFeedbackButton(this, getView()); - - if (resultCount == 0) { - mNoResultsView.setVisibility(View.VISIBLE); - } + final int resultCount; + switch (loader.getId()) { + case LOADER_ID_RECENTS: + resultCount = mSearchAdapter.displaySavedQuery(data); + break; + default: + mSearchAdapter.addSearchResults(data, loader.getClass().getName()); + if (mUnfinishedLoadersCount.decrementAndGet() != 0) { + return; + } + resultCount = mSearchAdapter.displaySearchResults(); } + mNoResultsView.setVisibility(resultCount == 0 ? View.VISIBLE : View.GONE); + mSearchFeatureProvider.showFeedbackButton(this, getView()); } @Override @@ -267,6 +269,8 @@ public class SearchFragment extends InstrumentedFragment implements public void onSavedQueryClicked(CharSequence query) { final String queryString = query.toString(); + mMetricsFeatureProvider.action(getContext(), + MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_SAVED_QUERY); mSearchView.setQuery(queryString, false /* submit */); onQueryTextChange(queryString); } @@ -286,7 +290,7 @@ public class SearchFragment extends InstrumentedFragment implements return mSearchAdapter.getSearchResults(); } - @VisibleForTesting (otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) SearchView makeSearchView(ActionBar actionBar, String query) { final SearchView searchView = new SearchView(actionBar.getThemedContext()); searchView.setIconifiedByDefault(false); diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java index b76958a60db..6ff68b17bd6 100644 --- a/src/com/android/settings/search2/SearchResultsAdapter.java +++ b/src/com/android/settings/search2/SearchResultsAdapter.java @@ -19,7 +19,7 @@ package com.android.settings.search2; import android.content.Context; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; -import android.support.v7.widget.RecyclerView.Adapter; +import android.support.v7.widget.RecyclerView; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; @@ -32,10 +32,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import static com.android.settings.search2.SearchResult.TOP_RANK; import static com.android.settings.search2.SearchResult.BOTTOM_RANK; +import static com.android.settings.search2.SearchResult.TOP_RANK; -public class SearchResultsAdapter extends Adapter { +public class SearchResultsAdapter extends RecyclerView.Adapter { private final List mSearchResults; private final SearchFragment mFragment; @@ -91,16 +91,28 @@ public class SearchResultsAdapter extends Adapter { /** * Store the results from each of the loaders to be merged when all loaders are finished. - * @param freshResults are the results from the loader. + * + * @param results the results from the loader. * @param loaderClassName class name of the loader. */ @MainThread - public void addResultsToMap(List freshResults, - String loaderClassName) { - if (freshResults == null) { + public void addSearchResults(List results, String loaderClassName) { + if (results == null) { return; } - mResultsMap.put(loaderClassName, freshResults); + mResultsMap.put(loaderClassName, results); + } + + /** + * Displays recent searched queries. + * + * @return The number of saved queries to display + */ + public int displaySavedQuery(List data) { + clearResults(); + mSearchResults.addAll(data); + notifyDataSetChanged(); + return mSearchResults.size(); } /** @@ -109,7 +121,7 @@ public class SearchResultsAdapter extends Adapter { * * @return Number of matched results */ - public int mergeResults() { + public int displaySearchResults() { final List databaseResults = mResultsMap .get(DatabaseResultLoader.class.getName()); final List installedAppResults = mResultsMap @@ -129,7 +141,7 @@ public class SearchResultsAdapter extends Adapter { while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) { results.add(installedAppResults.get(appIndex++)); } - rank ++; + rank++; } while (dbIndex < dbSize) { diff --git a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java index 6100050a5f1..647d68c35a5 100644 --- a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java +++ b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java @@ -21,9 +21,9 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; - import android.view.ViewGroup; import android.widget.FrameLayout; + import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; @@ -34,13 +34,12 @@ import com.android.settings.search2.InstalledAppResultLoader; import com.android.settings.search2.IntentPayload; import com.android.settings.search2.IntentSearchViewHolder; import com.android.settings.search2.ResultPayload; -import com.android.settings.search2.SearchActivity; import com.android.settings.search2.SearchFragment; import com.android.settings.search2.SearchResult; import com.android.settings.search2.SearchResult.Builder; import com.android.settings.search2.SearchResultsAdapter; - import com.android.settings.search2.SearchViewHolder; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,15 +47,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; -import org.robolectric.shadows.ShadowViewGroup; -import org.robolectric.util.ActivityController; import java.util.ArrayList; import java.util.List; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.doReturn; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @@ -85,15 +80,15 @@ public class SearchResultsAdapterTest { @Test public void testSingleSourceMerge_ExactCopyReturned() { ArrayList intentResults = getIntentSampleResults(); - mAdapter.addResultsToMap(intentResults, mLoaderClassName); - mAdapter.mergeResults(); + mAdapter.addSearchResults(intentResults, mLoaderClassName); + mAdapter.displaySearchResults(); List updatedResults = mAdapter.getSearchResults(); assertThat(updatedResults).containsAllIn(intentResults); } @Test - public void testCreatViewHolder_ReturnsIntentResult() { + public void testCreateViewHolder_ReturnsIntentResult() { ViewGroup group = new FrameLayout(mContext); SearchViewHolder view = mAdapter.onCreateViewHolder(group, ResultPayload.PayloadType.INTENT); @@ -101,7 +96,7 @@ public class SearchResultsAdapterTest { } @Test - public void testCreatViewHolder_ReturnsInlineSwitchResult() { + public void testCreateViewHolder_ReturnsInlineSwitchResult() { ViewGroup group = new FrameLayout(mContext); SearchViewHolder view = mAdapter.onCreateViewHolder(group, ResultPayload.PayloadType.INLINE_SWITCH); @@ -110,11 +105,11 @@ public class SearchResultsAdapterTest { @Test public void testEndToEndSearch_ProperResultsMerged() { - mAdapter.addResultsToMap(getDummyAppResults(), + mAdapter.addSearchResults(getDummyAppResults(), InstalledAppResultLoader.class.getName()); - mAdapter.addResultsToMap(getDummyDbResults(), + mAdapter.addSearchResults(getDummyDbResults(), DatabaseResultLoader.class.getName()); - int count = mAdapter.mergeResults(); + int count = mAdapter.displaySearchResults(); List results = mAdapter.getSearchResults(); assertThat(results.get(0).title).isEqualTo("alpha"); diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryViewHolderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryViewHolderTest.java new file mode 100644 index 00000000000..cc3cd33e40e --- /dev/null +++ b/tests/robotests/src/com/android/settings/search2/SavedQueryViewHolderTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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.settings.search2; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SavedQueryViewHolderTest { + + @Mock + private SearchFragment mSearchFragment; + private Context mContext; + private SavedQueryViewHolder mHolder; + private View mView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mView = LayoutInflater.from(mContext) + .inflate(R.layout.search_saved_query_item, null); + mHolder = new SavedQueryViewHolder(mView); + } + + @Test + public void onBind_shouldBindClickCallback() { + final SearchResult result = mock(SearchResult.class); + mHolder.onBind(mSearchFragment, result); + + mView.performClick(); + + verify(mSearchFragment).onSavedQueryClicked(any(CharSequence.class)); + } +} diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java index 6a61f528b0c..e7565c72b0e 100644 --- a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java +++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java @@ -17,12 +17,11 @@ package com.android.settings.search2; import android.app.LoaderManager; - import android.content.Context; import android.content.Loader; import android.os.Bundle; - import android.view.View; + import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; @@ -39,10 +38,12 @@ import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.ActivityController; +import org.robolectric.util.ReflectionHelpers; import java.util.List; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyList; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -186,20 +187,23 @@ public class SearchFragmentTest { ActivityController activityController = Robolectric.buildActivity(SearchActivity.class); activityController.setup(); - SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager() - .findFragmentById(R.id.main_content); - fragment.onQueryTextChange(""); - activityController.get().onBackPressed(); - activityController.pause().stop().destroy(); + SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager() + .findFragmentById(R.id.main_content)); + + final SearchResultsAdapter adapter = mock(SearchResultsAdapter.class); + ReflectionHelpers.setField(fragment, "mSearchAdapter", adapter); verify(mFeatureFactory.searchFeatureProvider, never()) .getDatabaseSearchLoader(any(Context.class), anyString()); verify(mFeatureFactory.searchFeatureProvider, never()) .getInstalledAppSearchLoader(any(Context.class), anyString()); - // Saved query loaded 2 times: fragment start, and query change to empty. - verify(mFeatureFactory.searchFeatureProvider, times(2)) + verify(mFeatureFactory.searchFeatureProvider) .getSavedQueryLoader(any(Context.class)); + + fragment.onLoadFinished(mSavedQueryLoader, null /* data */); + + verify(adapter).displaySavedQuery(anyList()); } @Test @@ -225,7 +229,6 @@ public class SearchFragmentTest { @Test public void syncLoaders_MergeWhenAllLoadersDone() { - when(mFeatureFactory.searchFeatureProvider .getDatabaseSearchLoader(any(Context.class), anyString())) .thenReturn(new MockDBLoader(RuntimeEnvironment.application)); @@ -236,6 +239,7 @@ public class SearchFragmentTest { ActivityController activityController = Robolectric.buildActivity(SearchActivity.class); activityController.setup(); + SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager() .findFragmentById(R.id.main_content));