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