SearchFragment.java revision 4c0f3062b5edd9750351068f46e5270bb220091d
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14package android.support.v17.leanback.app; 15 16import android.app.Fragment; 17import android.graphics.drawable.Drawable; 18import android.os.Bundle; 19import android.os.Handler; 20import android.speech.SpeechRecognizer; 21import android.support.v17.leanback.widget.ObjectAdapter; 22import android.support.v17.leanback.widget.OnItemClickedListener; 23import android.support.v17.leanback.widget.OnItemSelectedListener; 24import android.support.v17.leanback.widget.Row; 25import android.support.v17.leanback.widget.SearchBar; 26import android.support.v17.leanback.widget.VerticalGridView; 27import android.util.Log; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.view.ViewGroup; 31import android.widget.FrameLayout; 32import android.support.v17.leanback.R; 33 34import java.util.List; 35 36/** 37 * A fragment to handle searches. An application will supply an implementation 38 * of the {@link SearchResultProvider} interface to handle the search and return 39 * an {@link ObjectAdapter} containing the results. The results are rendered 40 * into a {@link RowsFragment}, in the same way that they are in a {@link 41 * BrowseFragment}. 42 * 43 * <p>Note: Your application will need to request android.permission.RECORD_AUDIO. 44 */ 45public class SearchFragment extends Fragment { 46 private static final String TAG = SearchFragment.class.getSimpleName(); 47 private static final boolean DEBUG = false; 48 49 private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName(); 50 private static final String ARG_QUERY = ARG_PREFIX + ".query"; 51 private static final String ARG_TITLE = ARG_PREFIX + ".title"; 52 53 /** 54 * Search API to be provided by the application. 55 */ 56 public static interface SearchResultProvider { 57 /** 58 * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve 59 * an ObjectAdapter that will contain the results to future updates of the search query.</p> 60 * 61 * <p>As results are retrieved, the application should use the data set notification methods 62 * on the ObjectAdapter to instruct the SearchFragment to update the results.</p> 63 * 64 * @return ObjectAdapter The result object adapter. 65 */ 66 public ObjectAdapter getResultsAdapter(); 67 68 /** 69 * <p>Method invoked when the search query is updated.</p> 70 * 71 * <p>This is called as soon as the query changes; it is up to the application to add a 72 * delay before actually executing the queries if needed. 73 * 74 * <p>This method might not always be called before onQueryTextSubmit gets called, in 75 * particular for voice input. 76 * 77 * @param newQuery The current search query. 78 * @return whether the results changed as a result of the new query. 79 */ 80 public boolean onQueryTextChange(String newQuery); 81 82 /** 83 * Method invoked when the search query is submitted, either by dismissing the keyboard, 84 * pressing search or next on the keyboard or when voice has detected the end of the query. 85 * 86 * @param query The query entered. 87 * @return whether the results changed as a result of the query. 88 */ 89 public boolean onQueryTextSubmit(String query); 90 } 91 92 private RowsFragment mRowsFragment; 93 private final Handler mHandler = new Handler(); 94 95 private SearchBar mSearchBar; 96 private SearchResultProvider mProvider; 97 private String mPendingQuery = null; 98 99 private OnItemSelectedListener mOnItemSelectedListener; 100 private OnItemClickedListener mOnItemClickedListener; 101 private ObjectAdapter mResultAdapter; 102 103 private String mTitle; 104 private Drawable mBadgeDrawable; 105 106 private SpeechRecognizer mSpeechRecognizer; 107 108 /** 109 * @param args Bundle to use for the arguments, if null a new Bundle will be created. 110 */ 111 public static Bundle createArgs(Bundle args, String query) { 112 return createArgs(args, query, null); 113 } 114 115 public static Bundle createArgs(Bundle args, String query, String title) { 116 if (args == null) { 117 args = new Bundle(); 118 } 119 args.putString(ARG_QUERY, query); 120 args.putString(ARG_TITLE, title); 121 return args; 122 } 123 124 /** 125 * Create a search fragment with a given search query. 126 * 127 * <p>You should only use this if you need to start the search fragment with a 128 * pre-filled query. 129 * 130 * @param query The search query to begin with. 131 * @return A new SearchFragment. 132 */ 133 public static SearchFragment newInstance(String query) { 134 SearchFragment fragment = new SearchFragment(); 135 Bundle args = createArgs(null, query); 136 fragment.setArguments(args); 137 return fragment; 138 } 139 140 @Override 141 public void onCreate(Bundle savedInstanceState) { 142 super.onCreate(savedInstanceState); 143 } 144 145 @Override 146 public View onCreateView(LayoutInflater inflater, ViewGroup container, 147 Bundle savedInstanceState) { 148 View root = inflater.inflate(R.layout.lb_search_fragment, container, false); 149 150 FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame); 151 mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar); 152 mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() { 153 @Override 154 public void onSearchQueryChange(String query) { 155 if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s", query)); 156 if (null != mProvider) { 157 retrieveResults(query); 158 } else { 159 mPendingQuery = query; 160 } 161 } 162 163 @Override 164 public void onSearchQuerySubmit(String query) { 165 if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query)); 166 mRowsFragment.setSelectedPosition(0); 167 mRowsFragment.getVerticalGridView().requestFocus(); 168 if (null != mProvider) { 169 mProvider.onQueryTextSubmit(query); 170 } 171 } 172 173 @Override 174 public void onKeyboardDismiss(String query) { 175 if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query)); 176 mRowsFragment.setSelectedPosition(0); 177 mRowsFragment.getVerticalGridView().requestFocus(); 178 } 179 }); 180 181 readArguments(getArguments()); 182 if (null != mBadgeDrawable) { 183 setBadgeDrawable(mBadgeDrawable); 184 } 185 if (null != mTitle) { 186 setTitle(mTitle); 187 } 188 189 // Inject the RowsFragment in the results container 190 if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) { 191 mRowsFragment = new RowsFragment(); 192 getChildFragmentManager().beginTransaction() 193 .replace(R.id.lb_results_frame, mRowsFragment).commit(); 194 } else { 195 mRowsFragment = (RowsFragment) getChildFragmentManager() 196 .findFragmentById(R.id.browse_container_dock); 197 } 198 mRowsFragment.setOnItemSelectedListener(new OnItemSelectedListener() { 199 @Override 200 public void onItemSelected(Object item, Row row) { 201 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 202 if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position)); 203 mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE); 204 if (null != mOnItemSelectedListener) { 205 mOnItemSelectedListener.onItemSelected(item, row); 206 } 207 } 208 }); 209 mRowsFragment.setOnItemClickedListener(new OnItemClickedListener() { 210 @Override 211 public void onItemClicked(Object item, Row row) { 212 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 213 if (DEBUG) Log.v(TAG, String.format("onItemClicked %d", position)); 214 if (null != mOnItemClickedListener) { 215 mOnItemClickedListener.onItemClicked(item, row); 216 } 217 } 218 }); 219 mRowsFragment.setExpand(true); 220 if (null != mProvider) { 221 onSetSearchResultProvider(); 222 } 223 return root; 224 } 225 226 @Override 227 public void onStart() { 228 super.onStart(); 229 230 VerticalGridView list = mRowsFragment.getVerticalGridView(); 231 int mContainerListAlignTop = 232 getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top); 233 list.setItemAlignmentOffset(0); 234 list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); 235 list.setWindowAlignmentOffset(mContainerListAlignTop); 236 list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); 237 list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 238 } 239 240 @Override 241 public void onResume() { 242 super.onResume(); 243 if (null == mSpeechRecognizer) { 244 mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity()); 245 mSearchBar.setSpeechRecognizer(mSpeechRecognizer); 246 } 247 } 248 249 @Override 250 public void onPause() { 251 if (null != mSpeechRecognizer) { 252 mSearchBar.setSpeechRecognizer(null); 253 mSpeechRecognizer.destroy(); 254 mSpeechRecognizer = null; 255 } 256 super.onPause(); 257 } 258 259 /** 260 * Set the search provider that is responsible for returning results for the 261 * search query. 262 */ 263 public void setSearchResultProvider(SearchResultProvider searchResultProvider) { 264 mProvider = searchResultProvider; 265 onSetSearchResultProvider(); 266 } 267 268 /** 269 * Sets an item selection listener for the results. 270 * 271 * @param listener The item selection listener to be invoked when an item in 272 * the search results is selected. 273 */ 274 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 275 mOnItemSelectedListener = listener; 276 } 277 278 /** 279 * Sets an item clicked listener for the results. 280 * 281 * @param listener The item clicked listener to be invoked when an item in 282 * the search results is clicked. 283 */ 284 public void setOnItemClickedListener(OnItemClickedListener listener) { 285 mOnItemClickedListener = listener; 286 } 287 288 /** 289 * Sets the title string to be be shown in an empty search bar. The title 290 * may be placed in a call-to-action, such as "Search <i>title</i>" or 291 * "Speak to search <i>title</i>". 292 */ 293 public void setTitle(String title) { 294 mTitle = title; 295 if (null != mSearchBar) { 296 mSearchBar.setTitle(title); 297 } 298 } 299 300 /** 301 * Returns the title set in the search bar. 302 */ 303 public String getTitle() { 304 if (null != mSearchBar) { 305 return mSearchBar.getTitle(); 306 } 307 return null; 308 } 309 310 /** 311 * Sets the badge drawable that will be shown inside the search bar next to 312 * the title. 313 */ 314 public void setBadgeDrawable(Drawable drawable) { 315 mBadgeDrawable = drawable; 316 if (null != mSearchBar) { 317 mSearchBar.setBadgeDrawable(drawable); 318 } 319 } 320 321 /** 322 * Returns the badge drawable in the search bar. 323 */ 324 public Drawable getBadgeDrawable() { 325 if (null != mSearchBar) { 326 return mSearchBar.getBadgeDrawable(); 327 } 328 return null; 329 } 330 331 /** 332 * Display the completions shown by the IME. An application may provide 333 * a list of query completions that the system will show in the IME. 334 * 335 * @param completions A list of completions to show in the IME. Setting to 336 * null or empty will clear the list. 337 */ 338 public void displayCompletions(List<String> completions) { 339 mSearchBar.displayCompletions(completions); 340 } 341 342 private void retrieveResults(String searchQuery) { 343 if (DEBUG) Log.v(TAG, String.format("retrieveResults %s", searchQuery)); 344 mProvider.onQueryTextChange(searchQuery); 345 } 346 347 private void onSetSearchResultProvider() { 348 mHandler.post(new Runnable() { 349 @Override 350 public void run() { 351 // Retrieve the result adapter 352 mResultAdapter = mProvider.getResultsAdapter(); 353 if (null != mRowsFragment) { 354 mRowsFragment.setAdapter(mResultAdapter); 355 executePendingQuery(); 356 } 357 } 358 }); 359 } 360 361 private void executePendingQuery() { 362 if (null != mPendingQuery && null != mResultAdapter) { 363 String query = mPendingQuery; 364 mPendingQuery = null; 365 retrieveResults(query); 366 } 367 } 368 369 private void readArguments(Bundle args) { 370 if (null == args) { 371 return; 372 } 373 if (args.containsKey(ARG_QUERY)) { 374 setSearchQuery(args.getString(ARG_QUERY)); 375 } 376 377 if (args.containsKey(ARG_TITLE)) { 378 setTitle(args.getString(ARG_TITLE)); 379 } 380 } 381 382 private void setSearchQuery(String query) { 383 mSearchBar.setSearchQuery(query); 384 } 385} 386