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