1 /*
2  * Copyright (C) 2020 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.intelligence.search.car;
18 
19 import static com.android.car.ui.core.CarUi.requireInsets;
20 import static com.android.car.ui.core.CarUi.requireToolbar;
21 
22 import android.app.Activity;
23 import android.content.Context;
24 import android.os.Bundle;
25 import android.text.TextUtils;
26 import android.view.View;
27 import android.view.inputmethod.InputMethodManager;
28 
29 import androidx.annotation.NonNull;
30 import androidx.loader.app.LoaderManager;
31 import androidx.loader.content.Loader;
32 import androidx.recyclerview.widget.RecyclerView;
33 
34 import com.android.car.ui.preference.PreferenceFragment;
35 import com.android.car.ui.toolbar.MenuItem;
36 import com.android.car.ui.toolbar.Toolbar;
37 import com.android.car.ui.toolbar.ToolbarController;
38 import com.android.settings.intelligence.R;
39 import com.android.settings.intelligence.overlay.FeatureFactory;
40 import com.android.settings.intelligence.search.SearchCommon;
41 import com.android.settings.intelligence.search.SearchFeatureProvider;
42 import com.android.settings.intelligence.search.SearchResult;
43 import com.android.settings.intelligence.search.indexing.IndexingCallback;
44 import com.android.settings.intelligence.search.savedqueries.car.CarSavedQueryController;
45 import com.android.settings.intelligence.search.savedqueries.car.CarSavedQueryViewHolder;
46 
47 import java.util.List;
48 
49 /**
50  * Search fragment for car settings.
51  */
52 public class CarSearchFragment extends PreferenceFragment implements
53         LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
54 
55     private SearchFeatureProvider mSearchFeatureProvider;
56 
57     private ToolbarController mToolbar;
58     private RecyclerView mRecyclerView;
59 
60     private String mQuery;
61     private boolean mShowingSavedQuery;
62 
63     private CarSearchResultsAdapter mSearchAdapter;
64     private CarSavedQueryController mSavedQueryController;
65 
66     private final RecyclerView.OnScrollListener mScrollListener =
67             new RecyclerView.OnScrollListener() {
68                 @Override
69                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
70                     if (dy != 0) {
71                         hideKeyboard();
72                     }
73                 }
74             };
75 
76     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)77     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
78         setPreferencesFromResource(R.xml.car_search_fragment, rootKey);
79     }
80 
getToolbar()81     protected ToolbarController getToolbar() {
82         return requireToolbar(requireActivity());
83     }
84 
getToolbarMenuItems()85     protected List<MenuItem> getToolbarMenuItems() {
86         return null;
87     }
88 
89     @Override
onAttach(Context context)90     public void onAttach(Context context) {
91         super.onAttach(context);
92         mSearchFeatureProvider = FeatureFactory.get(context).searchFeatureProvider();
93     }
94 
95     @Override
onCreate(Bundle savedInstanceState)96     public void onCreate(Bundle savedInstanceState) {
97         super.onCreate(savedInstanceState);
98 
99         if (savedInstanceState != null) {
100             mQuery = savedInstanceState.getString(SearchCommon.STATE_QUERY);
101             mShowingSavedQuery = savedInstanceState.getBoolean(
102                     SearchCommon.STATE_SHOWING_SAVED_QUERY);
103         } else {
104             mShowingSavedQuery = true;
105         }
106 
107         LoaderManager loaderManager = getLoaderManager();
108         mSearchAdapter = new CarSearchResultsAdapter(/* fragment= */ this);
109         mSavedQueryController = new CarSavedQueryController(
110                 getContext(), loaderManager, mSearchAdapter);
111         mSearchFeatureProvider.updateIndexAsync(getContext(), /* indexingCallback= */ this);
112     }
113 
114     @Override
onActivityCreated(Bundle savedInstanceState)115     public void onActivityCreated(Bundle savedInstanceState) {
116         super.onActivityCreated(savedInstanceState);
117         mToolbar = getToolbar();
118         if (mToolbar != null) {
119             List<MenuItem> items = getToolbarMenuItems();
120             mToolbar.setTitle(getPreferenceScreen().getTitle());
121             mToolbar.setMenuItems(items);
122             mToolbar.setNavButtonMode(Toolbar.NavButtonMode.BACK);
123             mToolbar.setState(Toolbar.State.SUBPAGE);
124             mToolbar.setState(Toolbar.State.SEARCH);
125             mToolbar.setSearchHint(R.string.abc_search_hint);
126             mToolbar.registerOnSearchListener(this::onQueryTextChange);
127             mToolbar.registerOnSearchCompletedListener(this::onSearchComplete);
128             mToolbar.setShowMenuItemsWhileSearching(true);
129             mToolbar.setSearchQuery(mQuery);
130         }
131         mRecyclerView = getListView();
132         if (mRecyclerView != null) {
133             mRecyclerView.setAdapter(mSearchAdapter);
134             mRecyclerView.addOnScrollListener(mScrollListener);
135         }
136     }
137 
138     @Override
onStart()139     public void onStart() {
140         super.onStart();
141         onCarUiInsetsChanged(requireInsets(requireActivity()));
142     }
143 
144     @Override
onSaveInstanceState(@onNull Bundle outState)145     public void onSaveInstanceState(@NonNull Bundle outState) {
146         super.onSaveInstanceState(outState);
147         outState.putString(SearchCommon.STATE_QUERY, mQuery);
148         outState.putBoolean(SearchCommon.STATE_SHOWING_SAVED_QUERY, mShowingSavedQuery);
149     }
150 
onQueryTextChange(String query)151     private void onQueryTextChange(String query) {
152         if (TextUtils.equals(query, mQuery)) {
153             return;
154         }
155         boolean isEmptyQuery = TextUtils.isEmpty(query);
156 
157         mQuery = query;
158 
159         // If indexing is not finished, register the query text, but don't search.
160         if (!mSearchFeatureProvider.isIndexingComplete(getActivity())) {
161             mToolbar.getProgressBar().setVisible(!isEmptyQuery);
162             return;
163         }
164 
165         if (isEmptyQuery) {
166             LoaderManager loaderManager = getLoaderManager();
167             loaderManager.destroyLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT);
168             mShowingSavedQuery = true;
169             mSavedQueryController.loadSavedQueries();
170         } else {
171             restartLoaders();
172         }
173     }
174 
onSearchComplete()175     private void onSearchComplete() {
176         if (!TextUtils.isEmpty(mQuery)) {
177             mSavedQueryController.saveQuery(mQuery);
178         }
179     }
180 
181     /**
182      * Gets called when a saved query is clicked.
183      */
onSavedQueryClicked(CarSavedQueryViewHolder vh, CharSequence query)184     public void onSavedQueryClicked(CarSavedQueryViewHolder vh, CharSequence query) {
185         String queryString = query.toString();
186         mToolbar.setSearchQuery(queryString);
187         onQueryTextChange(queryString);
188     }
189 
190     /**
191      * Gets called when a search result is clicked.
192      */
onSearchResultClicked(CarSearchViewHolder resultViewHolder, SearchResult result)193     public void onSearchResultClicked(CarSearchViewHolder resultViewHolder, SearchResult result) {
194         mSearchFeatureProvider.searchResultClicked(getContext(), mQuery, result);
195         mSavedQueryController.saveQuery(mQuery);
196     }
197 
198     @Override
onCreateLoader(int id, Bundle args)199     public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
200         Activity activity = getActivity();
201 
202         if (id == SearchCommon.SearchLoaderId.SEARCH_RESULT) {
203             return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
204         }
205         return null;
206     }
207 
208     @Override
onLoadFinished(Loader<List<? extends SearchResult>> loader, List<? extends SearchResult> data)209     public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
210             List<? extends SearchResult> data) {
211         mSearchAdapter.postSearchResults(data);
212         mRecyclerView.scrollToPosition(0);
213     }
214 
215     @Override
onLoaderReset(Loader<List<? extends SearchResult>> loader)216     public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
217     }
218 
219     /**
220      * Gets called when Indexing is completed.
221      */
222     @Override
onIndexingFinished()223     public void onIndexingFinished() {
224         if (getActivity() == null) {
225             return;
226         }
227         mToolbar.getProgressBar().setVisible(false);
228         if (mShowingSavedQuery) {
229             mSavedQueryController.loadSavedQueries();
230         } else {
231             LoaderManager loaderManager = getLoaderManager();
232             loaderManager.initLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT,
233                     /* args= */ null, /* callback= */ this);
234         }
235         requery();
236     }
237 
requery()238     private void requery() {
239         if (TextUtils.isEmpty(mQuery)) {
240             return;
241         }
242         String query = mQuery;
243         mQuery = "";
244         onQueryTextChange(query);
245     }
246 
restartLoaders()247     private void restartLoaders() {
248         mShowingSavedQuery = false;
249         LoaderManager loaderManager = getLoaderManager();
250         loaderManager.restartLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT,
251                 /* args= */ null, /* callback= */ this);
252     }
253 
hideKeyboard()254     private void hideKeyboard() {
255         Activity activity = getActivity();
256         if (activity != null) {
257             View view = activity.getCurrentFocus();
258             InputMethodManager imm = (InputMethodManager)
259                     activity.getSystemService(Context.INPUT_METHOD_SERVICE);
260             if (imm.isActive(view)) {
261                 imm.hideSoftInputFromWindow(view.getWindowToken(), /* flags= */ 0);
262             }
263         }
264 
265         if (mRecyclerView != null) {
266             mRecyclerView.requestFocus();
267         }
268     }
269 }
270