diff --git a/res/menu/search_options_menu.xml b/res/menu/search_options_menu.xml deleted file mode 100644 index 25a79d4efe8..00000000000 --- a/res/menu/search_options_menu.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - diff --git a/src/com/android/settings/dashboard/DashboardContainerFragment.java b/src/com/android/settings/dashboard/DashboardContainerFragment.java index 45c423f6967..b3ee808d5dc 100644 --- a/src/com/android/settings/dashboard/DashboardContainerFragment.java +++ b/src/com/android/settings/dashboard/DashboardContainerFragment.java @@ -28,9 +28,9 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.logging.nano.MetricsProto; -import com.android.settings.core.InstrumentedFragment; import com.android.settings.R; import com.android.settings.SettingsActivity; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.SupportFeatureProvider; @@ -77,7 +77,7 @@ public final class DashboardContainerFragment extends InstrumentedFragment { // check if support tab needs to be selected final String selectedTab = getArguments(). - getString(EXTRA_SELECT_SETTINGS_TAB, ARG_SUMMARY_TAB); + getString(EXTRA_SELECT_SETTINGS_TAB, ARG_SUMMARY_TAB); if (TextUtils.equals(selectedTab, ARG_SUPPORT_TAB)) { mViewPager.setCurrentItem(INDEX_SUPPORT_FRAGMENT); } else { diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index b98de225abc..136ccaf60ea 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -22,7 +22,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.SimpleItemAnimator; import android.util.Log; import android.view.LayoutInflater; import android.view.View; diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java index 14f5d134ecb..569a627c338 100644 --- a/src/com/android/settings/search2/SearchFeatureProvider.java +++ b/src/com/android/settings/search2/SearchFeatureProvider.java @@ -16,7 +16,7 @@ package com.android.settings.search2; import android.app.Activity; -import android.widget.SearchView; +import android.content.Context; import android.view.Menu; /** @@ -31,8 +31,14 @@ public interface SearchFeatureProvider { /** * Inserts the Menu items into Settings activity. + * * @param menu Items will be inserted into this menu. * @param activity The activity that precedes SearchActivity. */ void setUpSearchMenu(Menu menu, Activity activity); + + /** + * Returns a new loader to search in index database. + */ + DatabaseResultLoader getDatabaseSearchLoader(Context context, String query); } diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java index 3c6dc3501ea..81a41dccd5b 100644 --- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java +++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java @@ -24,6 +24,9 @@ import android.view.Menu; import android.view.MenuItem; import com.android.settings.R; +import com.android.settings.utils.AsyncLoader; + +import java.util.List; /** * FeatureProvider for the refactored search code. @@ -60,4 +63,9 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider { menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } + + @Override + public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) { + return new DatabaseResultLoader(context, query); + } } diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java index 18f20bef60d..1fb123cb2c9 100644 --- a/src/com/android/settings/search2/SearchFragment.java +++ b/src/com/android/settings/search2/SearchFragment.java @@ -16,107 +16,110 @@ package com.android.settings.search2; +import android.app.ActionBar; import android.app.Activity; +import android.app.LoaderManager; +import android.content.Context; import android.content.Loader; import android.os.Bundle; -import android.app.LoaderManager; +import android.support.annotation.VisibleForTesting; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.Menu; -import android.view.MenuInflater; +import android.widget.LinearLayout.LayoutParams; import android.widget.SearchView; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.core.InstrumentedFragment; +import com.android.settings.overlay.FeatureFactory; import java.util.List; public class SearchFragment extends InstrumentedFragment implements - SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener, - LoaderManager.LoaderCallbacks> { + SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks> { + // State values + static final String STATE_QUERY = "query"; + + // Loader IDs private static final int DATABASE_LOADER_ID = 0; - private SearchResultsAdapter mSearchAdapter; + @VisibleForTesting + String mQuery; + private SearchFeatureProvider mSearchFeatureProvider; private DatabaseResultLoader mSearchLoader; + private SearchResultsAdapter mSearchAdapter; private RecyclerView mResultsRecyclerView; - private SearchView mSearchView; - private MenuItem mSearchMenuItem; - private String mQuery; + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mSearchFeatureProvider = FeatureFactory.getFactory(context) + .getSearchFeatureProvider(context); + } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); - mSearchAdapter = new SearchResultsAdapter(); - - final LoaderManager loaderManager = getLoaderManager(); - loaderManager.initLoader(DATABASE_LOADER_ID, null, this); + if (savedInstanceState != null) { + mQuery = savedInstanceState.getString(STATE_QUERY); + getLoaderManager().initLoader(DATABASE_LOADER_ID, null, this); + } + final ActionBar actionBar = getActivity().getActionBar(); + actionBar.setCustomView(makeSearchView(actionBar, mQuery)); + actionBar.setDisplayShowCustomEnabled(true); + actionBar.setDisplayShowTitleEnabled(false); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.search_panel_2, container, false); mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results); - mResultsRecyclerView.setAdapter(mSearchAdapter); mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); return view; } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.search_options_menu, menu); - - - mSearchMenuItem = menu.findItem(R.id.search); - - mSearchView = (SearchView) mSearchMenuItem.getActionView(); - mSearchView.setOnQueryTextListener(this); - mSearchView.setMaxWidth(Integer.MAX_VALUE); - mSearchMenuItem.expandActionView(); - } - - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - // Return false to prevent the search box from collapsing. - return false; + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(STATE_QUERY, mQuery); } @Override public boolean onQueryTextChange(String query) { - if (query == null || query.equals(mQuery)) { - return false; + if (TextUtils.equals(query, mQuery)) { + return true; } - mQuery = query; - clearLoaders(); + mSearchAdapter.clearResults(); - final LoaderManager loaderManager = getLoaderManager(); - loaderManager.restartLoader(DATABASE_LOADER_ID, null, this); + if (TextUtils.isEmpty(mQuery)) { + getLoaderManager().destroyLoader(DATABASE_LOADER_ID); + } else { + restartLoaders(); + } return true; } @Override public boolean onQueryTextSubmit(String query) { - return false; + return true; } @Override @@ -125,7 +128,7 @@ public class SearchFragment extends InstrumentedFragment implements switch (id) { case DATABASE_LOADER_ID: - mSearchLoader = new DatabaseResultLoader(activity, mQuery); + mSearchLoader = mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery); return mSearchLoader; default: return null; @@ -142,17 +145,22 @@ public class SearchFragment extends InstrumentedFragment implements } @Override - public void onLoaderReset(Loader> loader) { } - - @Override - public int getMetricsCategory() { - return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS; + public void onLoaderReset(Loader> loader) { } - private void clearLoaders() { - if (mSearchLoader != null) { - mSearchLoader.cancelLoad(); - mSearchLoader = null; - } + private void restartLoaders() { + final LoaderManager loaderManager = getLoaderManager(); + loaderManager.restartLoader(DATABASE_LOADER_ID, null /* args */, this /* callback */); + } + + private SearchView makeSearchView(ActionBar actionBar, String query) { + final SearchView searchView = new SearchView(actionBar.getThemedContext()); + searchView.setIconifiedByDefault(false); + searchView.setQuery(query, false /* submitQuery */); + searchView.setOnQueryTextListener(this); + final LayoutParams lp = + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + searchView.setLayoutParams(lp); + return searchView; } } diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java index 22f106b49bd..62b79b32d78 100644 --- a/src/com/android/settings/search2/SearchResultsAdapter.java +++ b/src/com/android/settings/search2/SearchResultsAdapter.java @@ -28,10 +28,11 @@ import com.android.settings.search2.ResultPayload.PayloadType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; public class SearchResultsAdapter extends Adapter { - private ArrayList mSearchResults; - private HashMap> mResultsMap; + private final List mSearchResults; + private final Map> mResultsMap; public SearchResultsAdapter() { mSearchResults = new ArrayList<>(); @@ -45,13 +46,19 @@ public class SearchResultsAdapter extends Adapter { return; } mResultsMap.put(loaderClassName, freshResults); - mSearchResults = mergeMappedResults(); + mSearchResults.addAll(mergeMappedResults()); + notifyDataSetChanged(); + } + + public void clearResults() { + mSearchResults.clear(); + mResultsMap.clear(); notifyDataSetChanged(); } private ArrayList mergeMappedResults() { ArrayList mergedResults = new ArrayList<>(); - for(String key : mResultsMap.keySet()) { + for (String key : mResultsMap.keySet()) { mergedResults.addAll(mResultsMap.get(key)); } return mergedResults; @@ -60,7 +67,7 @@ public class SearchResultsAdapter extends Adapter { @Override public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - switch(viewType) { + switch (viewType) { case PayloadType.INTENT: View view = inflater.inflate(R.layout.search_intent_item, parent, false); return new IntentSearchViewHolder(view); @@ -95,7 +102,7 @@ public class SearchResultsAdapter extends Adapter { } @VisibleForTesting - public ArrayList getSearchResults() { + public List getSearchResults() { return mSearchResults; } } diff --git a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java index b3da4eb0f62..81e918054ea 100644 --- a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java +++ b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java @@ -20,20 +20,25 @@ package com.android.settings.search; import android.app.Activity; import android.content.Context; import android.graphics.drawable.Drawable; + +import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; -import com.android.settings.search2.*; +import com.android.settings.search2.DatabaseResultLoader; +import com.android.settings.search2.IntentPayload; +import com.android.settings.search2.ResultPayload; +import com.android.settings.search2.SearchResult; import com.android.settings.search2.SearchResult.Builder; -import com.android.settings.R; - -import java.util.ArrayList; +import com.android.settings.search2.SearchResultsAdapter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - -import org.robolectric.annotation.Config; import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; import static com.google.common.truth.Truth.assertThat; @@ -78,7 +83,7 @@ public class SearchAdapterTest { @Test public void testNoResultsAdded_EmptyListReturned() { - ArrayList updatedResults = mAdapter.getSearchResults(); + List updatedResults = mAdapter.getSearchResults(); assertThat(updatedResults).isEmpty(); } @@ -88,7 +93,7 @@ public class SearchAdapterTest { ArrayList intentResults = getIntentSampleResults(); mAdapter.mergeResults(intentResults, mLoaderClassName); - ArrayList updatedResults = mAdapter.getSearchResults(); + List updatedResults = mAdapter.getSearchResults(); assertThat(updatedResults).containsAllIn(intentResults); } @@ -98,7 +103,7 @@ public class SearchAdapterTest { mAdapter.mergeResults(intentResults, mLoaderClassName); mAdapter.mergeResults(intentResults, mLoaderClassName); - ArrayList updatedResults = mAdapter.getSearchResults(); + List updatedResults = mAdapter.getSearchResults(); assertThat(updatedResults).containsAllIn(intentResults); } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java new file mode 100644 index 00000000000..979b7e5dde6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 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.os.Bundle; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; +import org.robolectric.util.ActivityController; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SearchFragmentTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock + private DatabaseResultLoader mDatabaseResultLoader; + private FakeFeatureFactory mFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(mContext); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + } + + @Test + public void screenRotate_shouldPersistQuery() { + when(mFeatureFactory.searchFeatureProvider + .getDatabaseSearchLoader(any(Context.class), anyString())) + .thenReturn(mDatabaseResultLoader); + + final Bundle bundle = new Bundle(); + final String testQuery = "test"; + ActivityController activityController = + Robolectric.buildActivity(SearchActivity.class); + activityController.setup(); + SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager() + .findFragmentById(R.id.main_content); + + fragment.mQuery = testQuery; + + activityController.saveInstanceState(bundle).pause().stop().destroy(); + + activityController = Robolectric.buildActivity(SearchActivity.class); + activityController.setup(bundle); + + verify(mFeatureFactory.searchFeatureProvider) + .getDatabaseSearchLoader(any(Context.class), anyString()); + } + + @Test + public void queryTextChange_shouldTriggerLoader() { + when(mFeatureFactory.searchFeatureProvider + .getDatabaseSearchLoader(any(Context.class), anyString())) + .thenReturn(mDatabaseResultLoader); + + final String testQuery = "test"; + ActivityController activityController = + Robolectric.buildActivity(SearchActivity.class); + activityController.setup(); + SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager() + .findFragmentById(R.id.main_content); + + fragment.onQueryTextChange(testQuery); + + verify(mFeatureFactory.searchFeatureProvider) + .getDatabaseSearchLoader(any(Context.class), anyString()); + } +}