OSDN Git Service

Refactor all saved query related logic into a controller
[android-x86/packages-apps-Settings.git] / src / com / android / settings / search2 / SearchFragment.java
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.settings.search2;
18
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.LoaderManager;
22 import android.content.Context;
23 import android.content.Loader;
24 import android.os.Bundle;
25 import android.support.annotation.VisibleForTesting;
26 import android.support.v7.widget.LinearLayoutManager;
27 import android.support.v7.widget.RecyclerView;
28 import android.text.TextUtils;
29 import android.util.Log;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.inputmethod.InputMethodManager;
34 import android.widget.LinearLayout;
35 import android.widget.LinearLayout.LayoutParams;
36 import android.widget.SearchView;
37
38 import com.android.internal.logging.nano.MetricsProto;
39 import com.android.settings.R;
40 import com.android.settings.Utils;
41 import com.android.settings.core.InstrumentedFragment;
42 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
43 import com.android.settings.overlay.FeatureFactory;
44
45 import java.util.List;
46 import java.util.concurrent.atomic.AtomicInteger;
47
48 public class SearchFragment extends InstrumentedFragment implements SearchView.OnQueryTextListener,
49         LoaderManager.LoaderCallbacks<List<? extends SearchResult>> {
50     private static final String TAG = "SearchFragment";
51
52     @VisibleForTesting
53     static final int SEARCH_TAG = "SearchViewTag".hashCode();
54
55     // State values
56     private static final String STATE_QUERY = "state_query";
57     private static final String STATE_SHOWING_SAVED_QUERY = "state_showing_saved_query";
58     private static final String STATE_NEVER_ENTERED_QUERY = "state_never_entered_query";
59     private static final String STATE_RESULT_CLICK_COUNT = "state_result_click_count";
60
61     // Loader IDs
62     private static final int LOADER_ID_DATABASE = 1;
63     private static final int LOADER_ID_INSTALLED_APPS = 2;
64
65     private static final int NUM_QUERY_LOADERS = 2;
66
67     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
68     AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);
69
70     // Logging
71     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
72     static final String RESULT_CLICK_COUNT = "settings_search_result_click_count";
73
74     @VisibleForTesting
75     String mQuery;
76
77     private boolean mNeverEnteredQuery = true;
78     private boolean mShowingSavedQuery;
79     private int mResultClickCount;
80     private MetricsFeatureProvider mMetricsFeatureProvider;
81     private SavedQueryController mSavedQueryController;
82
83     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
84     SearchFeatureProvider mSearchFeatureProvider;
85
86     private SearchResultsAdapter mSearchAdapter;
87
88     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
89     RecyclerView mResultsRecyclerView;
90     private SearchView mSearchView;
91     private LinearLayout mNoResultsView;
92
93     @VisibleForTesting
94     final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
95         @Override
96         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
97             if (dy != 0) {
98                 hideKeyboard();
99             }
100         }
101     };
102
103     @Override
104     public int getMetricsCategory() {
105         return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
106     }
107
108     @Override
109     public void onAttach(Context context) {
110         super.onAttach(context);
111         mSearchFeatureProvider = FeatureFactory.getFactory(context).getSearchFeatureProvider();
112         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
113     }
114
115     @Override
116     public void onCreate(Bundle savedInstanceState) {
117         super.onCreate(savedInstanceState);
118         setHasOptionsMenu(true);
119         final LoaderManager loaderManager = getLoaderManager();
120         mSearchAdapter = new SearchResultsAdapter(this);
121         mSavedQueryController = new SavedQueryController(
122                 getContext(), loaderManager, mSearchAdapter);
123         mSearchFeatureProvider.initFeedbackButton();
124
125         if (savedInstanceState != null) {
126             mQuery = savedInstanceState.getString(STATE_QUERY);
127             mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY);
128             mResultClickCount = savedInstanceState.getInt(STATE_RESULT_CLICK_COUNT);
129             mShowingSavedQuery = savedInstanceState.getBoolean(STATE_SHOWING_SAVED_QUERY);
130             if (mShowingSavedQuery) {
131                 mSavedQueryController.loadSavedQueries();
132             } else {
133                 loaderManager.initLoader(LOADER_ID_DATABASE, null, this);
134                 loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this);
135             }
136         } else {
137             mShowingSavedQuery = true;
138             mSavedQueryController.loadSavedQueries();
139         }
140
141         final Activity activity = getActivity();
142         final ActionBar actionBar = activity.getActionBar();
143         mSearchView = makeSearchView(actionBar, mQuery);
144         actionBar.setCustomView(mSearchView);
145         actionBar.setDisplayShowCustomEnabled(true);
146         actionBar.setDisplayShowTitleEnabled(false);
147         mSearchView.requestFocus();
148
149         // Run the Index update only if we have some space
150         if (!Utils.isLowStorage(activity)) {
151             mSearchFeatureProvider.updateIndex(activity);
152         } else {
153             Log.w(TAG, "Cannot update the Indexer as we are running low on storage space!");
154         }
155     }
156
157     @Override
158     public View onCreateView(LayoutInflater inflater, ViewGroup container,
159             Bundle savedInstanceState) {
160         final View view = inflater.inflate(R.layout.search_panel_2, container, false);
161         mResultsRecyclerView = view.findViewById(R.id.list_results);
162         mResultsRecyclerView.setAdapter(mSearchAdapter);
163         mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
164         mResultsRecyclerView.addOnScrollListener(mScrollListener);
165
166         mNoResultsView = view.findViewById(R.id.no_results_layout);
167         return view;
168     }
169
170     @Override
171     public void onStop() {
172         super.onStop();
173         final Activity activity = getActivity();
174         if (activity != null && activity.isFinishing()) {
175             mMetricsFeatureProvider.histogram(activity, RESULT_CLICK_COUNT, mResultClickCount);
176             if (mNeverEnteredQuery) {
177                 mMetricsFeatureProvider.action(activity,
178                         MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY);
179             }
180         }
181     }
182
183     @Override
184     public void onSaveInstanceState(Bundle outState) {
185         super.onSaveInstanceState(outState);
186         outState.putString(STATE_QUERY, mQuery);
187         outState.putBoolean(STATE_NEVER_ENTERED_QUERY, mNeverEnteredQuery);
188         outState.putBoolean(STATE_SHOWING_SAVED_QUERY, mShowingSavedQuery);
189         outState.putInt(STATE_RESULT_CLICK_COUNT, mResultClickCount);
190     }
191
192     @Override
193     public boolean onQueryTextChange(String query) {
194         if (TextUtils.equals(query, mQuery)) {
195             return true;
196         }
197
198         final boolean isEmptyQuery = TextUtils.isEmpty(query);
199
200         // Hide no-results-view when the new query is not a super-string of the previous
201         if ((mQuery != null) && (mNoResultsView.getVisibility() == View.VISIBLE)
202                 && (query.length() < mQuery.length())) {
203             mNoResultsView.setVisibility(View.GONE);
204         }
205
206         mResultClickCount = 0;
207         mNeverEnteredQuery = false;
208         mQuery = query;
209         mSearchAdapter.clearResults();
210
211         if (isEmptyQuery) {
212             final LoaderManager loaderManager = getLoaderManager();
213             loaderManager.destroyLoader(LOADER_ID_DATABASE);
214             loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS);
215             mShowingSavedQuery = true;
216             mSavedQueryController.loadSavedQueries();
217             mSearchFeatureProvider.hideFeedbackButton();
218         } else {
219             restartLoaders();
220         }
221
222         return true;
223     }
224
225     @Override
226     public boolean onQueryTextSubmit(String query) {
227         // Save submitted query.
228         mSavedQueryController.saveQuery(mQuery);
229         hideKeyboard();
230         return true;
231     }
232
233     @Override
234     public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
235         final Activity activity = getActivity();
236
237         switch (id) {
238             case LOADER_ID_DATABASE:
239                 return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
240             case LOADER_ID_INSTALLED_APPS:
241                 return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
242             default:
243                 return null;
244         }
245     }
246
247     @Override
248     public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
249             List<? extends SearchResult> data) {
250         mSearchAdapter.addSearchResults(data, loader.getClass().getName());
251         if (mUnfinishedLoadersCount.decrementAndGet() != 0) {
252             return;
253         }
254         final int resultCount = mSearchAdapter.displaySearchResults();
255         mNoResultsView.setVisibility(resultCount == 0 ? View.VISIBLE : View.GONE);
256         mSearchFeatureProvider.showFeedbackButton(this, getView());
257     }
258
259     @Override
260     public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
261     }
262
263     public void onSearchResultClicked() {
264         mSavedQueryController.saveQuery(mQuery);
265         mResultClickCount++;
266     }
267
268     public void onSavedQueryClicked(CharSequence query) {
269         final String queryString = query.toString();
270         mMetricsFeatureProvider.action(getContext(),
271                 MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_SAVED_QUERY);
272         mSearchView.setQuery(queryString, false /* submit */);
273         onQueryTextChange(queryString);
274     }
275
276     public void onRemoveSavedQueryClicked(CharSequence title) {
277         mSavedQueryController.removeQuery(title.toString());
278     }
279
280     private void restartLoaders() {
281         mShowingSavedQuery = false;
282         final LoaderManager loaderManager = getLoaderManager();
283         mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS);
284         loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
285         loaderManager.restartLoader(LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
286     }
287
288     public String getQuery() {
289         return mQuery;
290     }
291
292     public List<SearchResult> getSearchResults() {
293         return mSearchAdapter.getSearchResults();
294     }
295
296     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
297     SearchView makeSearchView(ActionBar actionBar, String query) {
298         final SearchView searchView = new SearchView(actionBar.getThemedContext());
299         searchView.setIconifiedByDefault(false);
300         searchView.setQuery(query, false /* submitQuery */);
301         searchView.setOnQueryTextListener(this);
302         searchView.setTag(SEARCH_TAG, searchView);
303         final LayoutParams lp =
304                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
305         searchView.setLayoutParams(lp);
306         return searchView;
307     }
308
309     private void hideKeyboard() {
310         final Activity activity = getActivity();
311         if (activity != null) {
312             View view = activity.getCurrentFocus();
313             InputMethodManager imm = (InputMethodManager)
314                     activity.getSystemService(Context.INPUT_METHOD_SERVICE);
315             imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
316         }
317
318         if (mResultsRecyclerView != null) {
319             mResultsRecyclerView.requestFocus();
320         }
321     }
322 }