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