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