Refactor all saved query related logic into a controller

Change-Id: I96f7dd91d7d2715141a9905cb59262d927560da4
Fix: 27391895
Test: make RunSettingsRoboTests
This commit is contained in:
Fan Zhang
2017-03-15 11:39:17 -07:00
parent fe44549626
commit 8c3dc96b21
3 changed files with 144 additions and 88 deletions

View File

@@ -0,0 +1,96 @@
/*
* 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.app.LoaderManager;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import com.android.settings.overlay.FeatureFactory;
import java.util.List;
public class SavedQueryController implements LoaderManager.LoaderCallbacks {
// TODO: make a generic background task manager to handle one-off tasks like this one.
private static final int LOADER_ID_SAVE_QUERY_TASK = 0;
private static final int LOADER_ID_REMOVE_QUERY_TASK = 1;
private static final int LOADER_ID_SAVED_QUERIES = 2;
private static final String ARG_QUERY = "remove_query";
private final Context mContext;
private final LoaderManager mLoaderManager;
private final SearchFeatureProvider mSearchFeatureProvider;
private final SearchResultsAdapter mResultAdapter;
public SavedQueryController(Context context, LoaderManager loaderManager,
SearchResultsAdapter resultsAdapter) {
mContext = context;
mLoaderManager = loaderManager;
mResultAdapter = resultsAdapter;
mSearchFeatureProvider = FeatureFactory.getFactory(context)
.getSearchFeatureProvider();
}
@Override
public Loader onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_SAVE_QUERY_TASK:
return new SavedQueryRecorder(mContext, args.getString(ARG_QUERY));
case LOADER_ID_REMOVE_QUERY_TASK:
return new SavedQueryRemover(mContext, args.getString(ARG_QUERY));
case LOADER_ID_SAVED_QUERIES:
return mSearchFeatureProvider.getSavedQueryLoader(mContext);
}
return null;
}
@Override
public void onLoadFinished(Loader loader, Object data) {
switch (loader.getId()) {
case LOADER_ID_REMOVE_QUERY_TASK:
mLoaderManager.restartLoader(LOADER_ID_SAVED_QUERIES, null, this);
break;
case LOADER_ID_SAVED_QUERIES:
mResultAdapter.displaySavedQuery((List<SearchResult>) data);
break;
}
}
@Override
public void onLoaderReset(Loader loader) {
}
public void saveQuery(String query) {
final Bundle args = new Bundle();
args.putString(ARG_QUERY, query);
mLoaderManager.restartLoader(LOADER_ID_SAVE_QUERY_TASK, args, this);
}
public void removeQuery(String query) {
final Bundle args = new Bundle();
args.putString(ARG_QUERY, query);
mLoaderManager.restartLoader(LOADER_ID_REMOVE_QUERY_TASK, args, this);
}
public void loadSavedQueries() {
mLoaderManager.restartLoader(LOADER_ID_SAVED_QUERIES, null, this);
}
}

View File

@@ -54,11 +54,11 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
// State values // State values
private static final String STATE_QUERY = "state_query"; private static final String STATE_QUERY = "state_query";
private static final String STATE_SHOWING_SAVED_QUERY = "state_showing_saved_query";
private static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query"; private static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query";
private static final String STATE_RESULT_CLICK_COUNT = "state_result_click_count"; private static final String STATE_RESULT_CLICK_COUNT = "state_result_click_count";
// Loader IDs // Loader IDs
private static final int LOADER_ID_RECENTS = 0;
private static final int LOADER_ID_DATABASE = 1; private static final int LOADER_ID_DATABASE = 1;
private static final int LOADER_ID_INSTALLED_APPS = 2; private static final int LOADER_ID_INSTALLED_APPS = 2;
@@ -74,12 +74,11 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
@VisibleForTesting @VisibleForTesting
String mQuery; String mQuery;
private final SaveQueryCallback mSaveQueryCallback =
new SaveQueryCallback();
private boolean mNeverEnteredQuery = true; private boolean mNeverEnteredQuery = true;
private boolean mShowingSavedQuery;
private int mResultClickCount; private int mResultClickCount;
private MetricsFeatureProvider mMetricsFeatureProvider; private MetricsFeatureProvider mMetricsFeatureProvider;
private SavedQueryController mSavedQueryController;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
SearchFeatureProvider mSearchFeatureProvider; SearchFeatureProvider mSearchFeatureProvider;
@@ -117,20 +116,26 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
mSearchAdapter = new SearchResultsAdapter(this);
mSearchFeatureProvider.initFeedbackButton();
final LoaderManager loaderManager = getLoaderManager(); final LoaderManager loaderManager = getLoaderManager();
mSearchAdapter = new SearchResultsAdapter(this);
mSavedQueryController = new SavedQueryController(
getContext(), loaderManager, mSearchAdapter);
mSearchFeatureProvider.initFeedbackButton();
if (savedInstanceState != null) { if (savedInstanceState != null) {
mQuery = savedInstanceState.getString(STATE_QUERY); mQuery = savedInstanceState.getString(STATE_QUERY);
mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY); mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY);
mResultClickCount = savedInstanceState.getInt(STATE_RESULT_CLICK_COUNT); mResultClickCount = savedInstanceState.getInt(STATE_RESULT_CLICK_COUNT);
loaderManager.initLoader(LOADER_ID_DATABASE, null, this); mShowingSavedQuery = savedInstanceState.getBoolean(STATE_SHOWING_SAVED_QUERY);
loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this); if (mShowingSavedQuery) {
mSavedQueryController.loadSavedQueries();
} else {
loaderManager.initLoader(LOADER_ID_DATABASE, null, this);
loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this);
}
} else { } else {
loaderManager.initLoader(LOADER_ID_RECENTS, null, this); mShowingSavedQuery = true;
mSavedQueryController.loadSavedQueries();
} }
final Activity activity = getActivity(); final Activity activity = getActivity();
@@ -180,6 +185,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putString(STATE_QUERY, mQuery); outState.putString(STATE_QUERY, mQuery);
outState.putBoolean(STATE_NEVER_ENTERED_QUERY, mNeverEnteredQuery); outState.putBoolean(STATE_NEVER_ENTERED_QUERY, mNeverEnteredQuery);
outState.putBoolean(STATE_SHOWING_SAVED_QUERY, mShowingSavedQuery);
outState.putInt(STATE_RESULT_CLICK_COUNT, mResultClickCount); outState.putInt(STATE_RESULT_CLICK_COUNT, mResultClickCount);
} }
@@ -206,7 +212,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
final LoaderManager loaderManager = getLoaderManager(); final LoaderManager loaderManager = getLoaderManager();
loaderManager.destroyLoader(LOADER_ID_DATABASE); loaderManager.destroyLoader(LOADER_ID_DATABASE);
loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS); loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS);
loaderManager.restartLoader(LOADER_ID_RECENTS, null /* args */, this /* callback */); mShowingSavedQuery = true;
mSavedQueryController.loadSavedQueries();
mSearchFeatureProvider.hideFeedbackButton(); mSearchFeatureProvider.hideFeedbackButton();
} else { } else {
restartLoaders(); restartLoaders();
@@ -218,8 +225,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
@Override @Override
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
// Save submitted query. // Save submitted query.
getLoaderManager().restartLoader(SaveQueryCallback.LOADER_ID_SAVE_QUERY_TASK, null, mSavedQueryController.saveQuery(mQuery);
mSaveQueryCallback);
hideKeyboard(); hideKeyboard();
return true; return true;
} }
@@ -233,8 +239,6 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery); return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
case LOADER_ID_INSTALLED_APPS: case LOADER_ID_INSTALLED_APPS:
return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery); return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
case LOADER_ID_RECENTS:
return mSearchFeatureProvider.getSavedQueryLoader(activity);
default: default:
return null; return null;
} }
@@ -243,18 +247,11 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
@Override @Override
public void onLoadFinished(Loader<List<? extends SearchResult>> loader, public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
List<? extends SearchResult> data) { List<? extends SearchResult> data) {
final int resultCount; mSearchAdapter.addSearchResults(data, loader.getClass().getName());
switch (loader.getId()) { if (mUnfinishedLoadersCount.decrementAndGet() != 0) {
case LOADER_ID_RECENTS: return;
resultCount = mSearchAdapter.displaySavedQuery(data);
break;
default:
mSearchAdapter.addSearchResults(data, loader.getClass().getName());
if (mUnfinishedLoadersCount.decrementAndGet() != 0) {
return;
}
resultCount = mSearchAdapter.displaySearchResults();
} }
final int resultCount = mSearchAdapter.displaySearchResults();
mNoResultsView.setVisibility(resultCount == 0 ? View.VISIBLE : View.GONE); mNoResultsView.setVisibility(resultCount == 0 ? View.VISIBLE : View.GONE);
mSearchFeatureProvider.showFeedbackButton(this, getView()); mSearchFeatureProvider.showFeedbackButton(this, getView());
} }
@@ -264,8 +261,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
} }
public void onSearchResultClicked() { public void onSearchResultClicked() {
getLoaderManager().restartLoader(SaveQueryCallback.LOADER_ID_SAVE_QUERY_TASK, null, mSavedQueryController.saveQuery(mQuery);
mSaveQueryCallback);
mResultClickCount++; mResultClickCount++;
} }
@@ -278,13 +274,11 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
} }
public void onRemoveSavedQueryClicked(CharSequence title) { public void onRemoveSavedQueryClicked(CharSequence title) {
final Bundle args = new Bundle(); mSavedQueryController.removeQuery(title.toString());
args.putString(SaveQueryCallback.ARG_REMOVE_QUERY, title.toString());
getLoaderManager().restartLoader(SaveQueryCallback.LOADER_ID_REMOVE_QUERY_TASK,
args, mSaveQueryCallback);
} }
private void restartLoaders() { private void restartLoaders() {
mShowingSavedQuery = false;
final LoaderManager loaderManager = getLoaderManager(); final LoaderManager loaderManager = getLoaderManager();
mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS); mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS);
loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */); loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
@@ -325,37 +319,4 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
mResultsRecyclerView.requestFocus(); mResultsRecyclerView.requestFocus();
} }
} }
private class SaveQueryCallback implements LoaderManager.LoaderCallbacks<Void> {
// TODO: make a generic background task manager to handle one-off tasks like this one.
private static final int LOADER_ID_SAVE_QUERY_TASK = 0;
private static final int LOADER_ID_REMOVE_QUERY_TASK = 1;
private static final String ARG_REMOVE_QUERY = "remove_query";
@Override
public Loader<Void> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_SAVE_QUERY_TASK:
return new SavedQueryRecorder(getActivity(), mQuery);
case LOADER_ID_REMOVE_QUERY_TASK:
return new SavedQueryRemover(getActivity(), args.getString(ARG_REMOVE_QUERY));
}
return null;
}
@Override
public void onLoadFinished(Loader<Void> loader, Void data) {
switch (loader.getId()) {
case LOADER_ID_REMOVE_QUERY_TASK:
getLoaderManager().restartLoader(LOADER_ID_RECENTS, null, SearchFragment.this);
break;
}
}
@Override
public void onLoaderReset(Loader<Void> loader) {
}
}
} }

View File

@@ -43,7 +43,6 @@ import org.robolectric.util.ReflectionHelpers;
import java.util.List; import java.util.List;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -66,7 +65,8 @@ public class SearchFragmentTest {
@Mock @Mock
private SavedQueryLoader mSavedQueryLoader; private SavedQueryLoader mSavedQueryLoader;
@Mock
private SavedQueryController mSavedQueryController;
private FakeFeatureFactory mFeatureFactory; private FakeFeatureFactory mFeatureFactory;
@Before @Before
@@ -96,6 +96,7 @@ public class SearchFragmentTest {
SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager() SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
.findFragmentById(R.id.main_content); .findFragmentById(R.id.main_content);
ReflectionHelpers.setField(fragment, "mShowingSavedQuery", false);
fragment.mQuery = testQuery; fragment.mQuery = testQuery;
activityController.saveInstanceState(bundle).pause().stop().destroy(); activityController.saveInstanceState(bundle).pause().stop().destroy();
@@ -111,12 +112,6 @@ public class SearchFragmentTest {
@Test @Test
public void screenRotateEmptyString_ShouldNotCrash() { public void screenRotateEmptyString_ShouldNotCrash() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInstalledAppSearchLoader(any(Context.class), anyString()))
.thenReturn(mInstalledAppResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class))) when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader); .thenReturn(mSavedQueryLoader);
@@ -134,10 +129,12 @@ public class SearchFragmentTest {
activityController = Robolectric.buildActivity(SearchActivity.class); activityController = Robolectric.buildActivity(SearchActivity.class);
activityController.setup(bundle); activityController.setup(bundle);
verify(mFeatureFactory.searchFeatureProvider) verify(mFeatureFactory.searchFeatureProvider, never())
.getDatabaseSearchLoader(any(Context.class), anyString()); .getDatabaseSearchLoader(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider) verify(mFeatureFactory.searchFeatureProvider, never())
.getInstalledAppSearchLoader(any(Context.class), anyString()); .getInstalledAppSearchLoader(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider, times(2))
.getSavedQueryLoader(any(Context.class));
} }
@Test @Test
@@ -160,6 +157,7 @@ public class SearchFragmentTest {
fragment.onQueryTextChange(testQuery); fragment.onQueryTextChange(testQuery);
activityController.get().onBackPressed(); activityController.get().onBackPressed();
activityController.pause().stop().destroy(); activityController.pause().stop().destroy();
verify(mFeatureFactory.metricsFeatureProvider, never()).action( verify(mFeatureFactory.metricsFeatureProvider, never()).action(
@@ -174,7 +172,7 @@ public class SearchFragmentTest {
} }
@Test @Test
public void queryTextChangeToEmpty_shouldTriggerSavedQueryLoader() { public void queryTextChangeToEmpty_shouldLoadSavedQuery() {
when(mFeatureFactory.searchFeatureProvider when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString())) .getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader); .thenReturn(mDatabaseResultLoader);
@@ -190,20 +188,15 @@ public class SearchFragmentTest {
SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager() SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
.findFragmentById(R.id.main_content)); .findFragmentById(R.id.main_content));
ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
final SearchResultsAdapter adapter = mock(SearchResultsAdapter.class); fragment.mQuery = "123";
ReflectionHelpers.setField(fragment, "mSearchAdapter", adapter); fragment.onQueryTextChange("");
verify(mFeatureFactory.searchFeatureProvider, never()) verify(mFeatureFactory.searchFeatureProvider, never())
.getDatabaseSearchLoader(any(Context.class), anyString()); .getDatabaseSearchLoader(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider, never()) verify(mFeatureFactory.searchFeatureProvider, never())
.getInstalledAppSearchLoader(any(Context.class), anyString()); .getInstalledAppSearchLoader(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider) verify(mSavedQueryController).loadSavedQueries();
.getSavedQueryLoader(any(Context.class));
fragment.onLoadFinished(mSavedQueryLoader, null /* data */);
verify(adapter).displaySavedQuery(anyList());
} }
@Test @Test
@@ -235,6 +228,8 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider when(mFeatureFactory.searchFeatureProvider
.getInstalledAppSearchLoader(any(Context.class), anyString())) .getInstalledAppSearchLoader(any(Context.class), anyString()))
.thenReturn(new MockAppLoader(RuntimeEnvironment.application)); .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController = ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class); Robolectric.buildActivity(SearchActivity.class);
@@ -258,6 +253,8 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider when(mFeatureFactory.searchFeatureProvider
.getInstalledAppSearchLoader(any(Context.class), anyString())) .getInstalledAppSearchLoader(any(Context.class), anyString()))
.thenReturn(new MockAppLoader(RuntimeEnvironment.application)); .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController = ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class); Robolectric.buildActivity(SearchActivity.class);
@@ -282,12 +279,14 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider when(mFeatureFactory.searchFeatureProvider
.getInstalledAppSearchLoader(any(Context.class), anyString())) .getInstalledAppSearchLoader(any(Context.class), anyString()))
.thenReturn(new MockAppLoader(RuntimeEnvironment.application)); .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController = ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class); Robolectric.buildActivity(SearchActivity.class);
activityController.setup(); activityController.setup();
SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager() SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
.findFragmentById(R.id.main_content)); .findFragmentById(R.id.main_content);
fragment.onQueryTextChange("non-empty"); fragment.onQueryTextChange("non-empty");