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 */ 14 package android.support.v17.leanback.app; 15 16 import android.app.Fragment; 17 import android.content.Intent; 18 import android.graphics.drawable.Drawable; 19 import android.os.Bundle; 20 import android.os.Handler; 21 import android.speech.SpeechRecognizer; 22 import android.speech.RecognizerIntent; 23 import android.support.v17.leanback.widget.ObjectAdapter; 24 import android.support.v17.leanback.widget.ObjectAdapter.DataObserver; 25 import android.support.v17.leanback.widget.OnItemViewClickedListener; 26 import android.support.v17.leanback.widget.OnItemViewSelectedListener; 27 import android.support.v17.leanback.widget.Row; 28 import android.support.v17.leanback.widget.RowPresenter; 29 import android.support.v17.leanback.widget.SearchBar; 30 import android.support.v17.leanback.widget.VerticalGridView; 31 import android.support.v17.leanback.widget.Presenter.ViewHolder; 32 import android.support.v17.leanback.widget.SpeechRecognitionCallback; 33 import android.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.FrameLayout; 38 import android.support.v17.leanback.R; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 43 /** 44 * A fragment to handle searches. An application will supply an implementation 45 * of the {@link SearchResultProvider} interface to handle the search and return 46 * an {@link ObjectAdapter} containing the results. The results are rendered 47 * into a {@link RowsFragment}, in the same way that they are in a {@link 48 * BrowseFragment}. 49 * 50 * <p>If you do not supply a callback via 51 * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech 52 * recognizer will be used for which your application will need to request 53 * android.permission.RECORD_AUDIO. 54 * </p> 55 * <p> 56 * Speech recognition is automatically started when fragment is created, but 57 * not when fragment is restored from an instance state. Activity may manually 58 * call {@link #startRecognition()}, typically in onNewIntent(). 59 * </p> 60 */ 61 public class SearchFragment extends Fragment { 62 private static final String TAG = SearchFragment.class.getSimpleName(); 63 private static final boolean DEBUG = false; 64 65 private static final String EXTRA_LEANBACK_BADGE_PRESENT = "LEANBACK_BADGE_PRESENT"; 66 private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName(); 67 private static final String ARG_QUERY = ARG_PREFIX + ".query"; 68 private static final String ARG_TITLE = ARG_PREFIX + ".title"; 69 70 private static final long SPEECH_RECOGNITION_DELAY_MS = 300; 71 72 private static final int RESULTS_CHANGED = 0x1; 73 private static final int QUERY_COMPLETE = 0x2; 74 75 /** 76 * Search API to be provided by the application. 77 */ 78 public static interface SearchResultProvider { 79 /** 80 * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve 81 * an ObjectAdapter that will contain the results to future updates of the search query.</p> 82 * 83 * <p>As results are retrieved, the application should use the data set notification methods 84 * on the ObjectAdapter to instruct the SearchFragment to update the results.</p> 85 * 86 * @return ObjectAdapter The result object adapter. 87 */ getResultsAdapter()88 public ObjectAdapter getResultsAdapter(); 89 90 /** 91 * <p>Method invoked when the search query is updated.</p> 92 * 93 * <p>This is called as soon as the query changes; it is up to the application to add a 94 * delay before actually executing the queries if needed. 95 * 96 * <p>This method might not always be called before onQueryTextSubmit gets called, in 97 * particular for voice input. 98 * 99 * @param newQuery The current search query. 100 * @return whether the results changed as a result of the new query. 101 */ onQueryTextChange(String newQuery)102 public boolean onQueryTextChange(String newQuery); 103 104 /** 105 * Method invoked when the search query is submitted, either by dismissing the keyboard, 106 * pressing search or next on the keyboard or when voice has detected the end of the query. 107 * 108 * @param query The query entered. 109 * @return whether the results changed as a result of the query. 110 */ onQueryTextSubmit(String query)111 public boolean onQueryTextSubmit(String query); 112 } 113 114 private final DataObserver mAdapterObserver = new DataObserver() { 115 @Override 116 public void onChanged() { 117 // onChanged() may be called multiple times e.g. the provider add 118 // rows to ArrayObjectAdapter one by one. 119 mHandler.removeCallbacks(mResultsChangedCallback); 120 mHandler.post(mResultsChangedCallback); 121 } 122 }; 123 124 private final Handler mHandler = new Handler(); 125 126 private final Runnable mResultsChangedCallback = new Runnable() { 127 @Override 128 public void run() { 129 if (DEBUG) Log.v(TAG, "results changed, new size " + mResultAdapter.size()); 130 if (mRowsFragment != null 131 && mRowsFragment.getAdapter() != mResultAdapter) { 132 if (!(mRowsFragment.getAdapter() == null && mResultAdapter.size() == 0)) { 133 mRowsFragment.setAdapter(mResultAdapter); 134 mRowsFragment.setSelectedPosition(0); 135 } 136 } 137 mStatus |= RESULTS_CHANGED; 138 if ((mStatus & QUERY_COMPLETE) != 0) { 139 updateFocus(); 140 } 141 updateSearchBarNextFocusId(); 142 } 143 }; 144 145 /** 146 * Runs when a new provider is set AND when the fragment view is created. 147 */ 148 private final Runnable mSetSearchResultProvider = new Runnable() { 149 @Override 150 public void run() { 151 if (mRowsFragment == null) { 152 // We'll retry once we have a rows fragment 153 return; 154 } 155 // Retrieve the result adapter 156 ObjectAdapter adapter = mProvider.getResultsAdapter(); 157 if (DEBUG) Log.v(TAG, "Got results adapter " + adapter); 158 if (adapter != mResultAdapter) { 159 boolean firstTime = mResultAdapter == null; 160 releaseAdapter(); 161 mResultAdapter = adapter; 162 if (mResultAdapter != null) { 163 mResultAdapter.registerObserver(mAdapterObserver); 164 } 165 if (DEBUG) Log.v(TAG, "mResultAdapter " + mResultAdapter + " size " + 166 (mResultAdapter == null ? 0 : mResultAdapter.size())); 167 // delay the first time to avoid setting a empty result adapter 168 // until we got first onChange() from the provider 169 if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) { 170 mRowsFragment.setAdapter(mResultAdapter); 171 } 172 executePendingQuery(); 173 } 174 updateSearchBarNextFocusId(); 175 176 if (DEBUG) Log.v(TAG, "mAutoStartRecognition " + mAutoStartRecognition + 177 " mResultAdapter " + mResultAdapter + 178 " adapter " + mRowsFragment.getAdapter()); 179 if (mAutoStartRecognition) { 180 mHandler.removeCallbacks(mStartRecognitionRunnable); 181 mHandler.postDelayed(mStartRecognitionRunnable, SPEECH_RECOGNITION_DELAY_MS); 182 } else { 183 updateFocus(); 184 } 185 } 186 }; 187 188 private final Runnable mStartRecognitionRunnable = new Runnable() { 189 @Override 190 public void run() { 191 mAutoStartRecognition = false; 192 mSearchBar.startRecognition(); 193 } 194 }; 195 196 private RowsFragment mRowsFragment; 197 private SearchBar mSearchBar; 198 private SearchResultProvider mProvider; 199 private String mPendingQuery = null; 200 201 private OnItemViewSelectedListener mOnItemViewSelectedListener; 202 private OnItemViewClickedListener mOnItemViewClickedListener; 203 private ObjectAdapter mResultAdapter; 204 private SpeechRecognitionCallback mSpeechRecognitionCallback; 205 206 private String mTitle; 207 private Drawable mBadgeDrawable; 208 private ExternalQuery mExternalQuery; 209 210 private SpeechRecognizer mSpeechRecognizer; 211 212 private int mStatus; 213 private boolean mAutoStartRecognition = true; 214 215 /** 216 * @param args Bundle to use for the arguments, if null a new Bundle will be created. 217 */ createArgs(Bundle args, String query)218 public static Bundle createArgs(Bundle args, String query) { 219 return createArgs(args, query, null); 220 } 221 createArgs(Bundle args, String query, String title)222 public static Bundle createArgs(Bundle args, String query, String title) { 223 if (args == null) { 224 args = new Bundle(); 225 } 226 args.putString(ARG_QUERY, query); 227 args.putString(ARG_TITLE, title); 228 return args; 229 } 230 231 /** 232 * Creates a search fragment with a given search query. 233 * 234 * <p>You should only use this if you need to start the search fragment with a 235 * pre-filled query. 236 * 237 * @param query The search query to begin with. 238 * @return A new SearchFragment. 239 */ newInstance(String query)240 public static SearchFragment newInstance(String query) { 241 SearchFragment fragment = new SearchFragment(); 242 Bundle args = createArgs(null, query); 243 fragment.setArguments(args); 244 return fragment; 245 } 246 247 @Override onCreate(Bundle savedInstanceState)248 public void onCreate(Bundle savedInstanceState) { 249 if (mAutoStartRecognition) { 250 mAutoStartRecognition = savedInstanceState == null; 251 } 252 super.onCreate(savedInstanceState); 253 } 254 255 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)256 public View onCreateView(LayoutInflater inflater, ViewGroup container, 257 Bundle savedInstanceState) { 258 View root = inflater.inflate(R.layout.lb_search_fragment, container, false); 259 260 FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame); 261 mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar); 262 mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() { 263 @Override 264 public void onSearchQueryChange(String query) { 265 if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s %s", query, 266 null == mProvider ? "(null)" : mProvider)); 267 if (null != mProvider) { 268 retrieveResults(query); 269 } else { 270 mPendingQuery = query; 271 } 272 } 273 274 @Override 275 public void onSearchQuerySubmit(String query) { 276 if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query)); 277 submitQuery(query); 278 } 279 280 @Override 281 public void onKeyboardDismiss(String query) { 282 if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query)); 283 queryComplete(); 284 } 285 }); 286 mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback); 287 applyExternalQuery(); 288 289 readArguments(getArguments()); 290 if (null != mBadgeDrawable) { 291 setBadgeDrawable(mBadgeDrawable); 292 } 293 if (null != mTitle) { 294 setTitle(mTitle); 295 } 296 297 // Inject the RowsFragment in the results container 298 if (getChildFragmentManager().findFragmentById(R.id.lb_results_frame) == null) { 299 mRowsFragment = new RowsFragment(); 300 getChildFragmentManager().beginTransaction() 301 .replace(R.id.lb_results_frame, mRowsFragment).commit(); 302 } else { 303 mRowsFragment = (RowsFragment) getChildFragmentManager() 304 .findFragmentById(R.id.lb_results_frame); 305 } 306 mRowsFragment.setOnItemViewSelectedListener(new OnItemViewSelectedListener() { 307 @Override 308 public void onItemSelected(ViewHolder itemViewHolder, Object item, 309 RowPresenter.ViewHolder rowViewHolder, Row row) { 310 int position = mRowsFragment.getVerticalGridView().getSelectedPosition(); 311 if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position)); 312 mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE); 313 if (null != mOnItemViewSelectedListener) { 314 mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item, 315 rowViewHolder, row); 316 } 317 } 318 }); 319 mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener); 320 mRowsFragment.setExpand(true); 321 if (null != mProvider) { 322 onSetSearchResultProvider(); 323 } 324 return root; 325 } 326 resultsAvailable()327 private void resultsAvailable() { 328 if ((mStatus & QUERY_COMPLETE) != 0) { 329 focusOnResults(); 330 } 331 updateSearchBarNextFocusId(); 332 } 333 334 @Override onStart()335 public void onStart() { 336 super.onStart(); 337 338 VerticalGridView list = mRowsFragment.getVerticalGridView(); 339 int mContainerListAlignTop = 340 getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top); 341 list.setItemAlignmentOffset(0); 342 list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); 343 list.setWindowAlignmentOffset(mContainerListAlignTop); 344 list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); 345 list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); 346 } 347 348 @Override onResume()349 public void onResume() { 350 super.onResume(); 351 if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) { 352 mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity()); 353 mSearchBar.setSpeechRecognizer(mSpeechRecognizer); 354 } 355 // Ensure search bar state consistency when using external recognizer 356 mSearchBar.stopRecognition(); 357 } 358 359 @Override onPause()360 public void onPause() { 361 releaseRecognizer(); 362 super.onPause(); 363 } 364 365 @Override onDestroy()366 public void onDestroy() { 367 releaseAdapter(); 368 super.onDestroy(); 369 } 370 releaseRecognizer()371 private void releaseRecognizer() { 372 if (null != mSpeechRecognizer) { 373 mSearchBar.setSpeechRecognizer(null); 374 mSpeechRecognizer.destroy(); 375 mSpeechRecognizer = null; 376 } 377 } 378 379 /** 380 * Starts speech recognition. Typical use case is that 381 * activity receives onNewIntent() call when user clicks a MIC button. 382 * Note that SearchFragment automatically starts speech recognition 383 * at first time created, there is no need to call startRecognition() 384 * when fragment is created. 385 */ startRecognition()386 public void startRecognition() { 387 mSearchBar.startRecognition(); 388 } 389 390 /** 391 * Sets the search provider that is responsible for returning results for the 392 * search query. 393 */ setSearchResultProvider(SearchResultProvider searchResultProvider)394 public void setSearchResultProvider(SearchResultProvider searchResultProvider) { 395 if (mProvider != searchResultProvider) { 396 mProvider = searchResultProvider; 397 onSetSearchResultProvider(); 398 } 399 } 400 401 /** 402 * Sets an item selection listener for the results. 403 * 404 * @param listener The item selection listener to be invoked when an item in 405 * the search results is selected. 406 */ setOnItemViewSelectedListener(OnItemViewSelectedListener listener)407 public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) { 408 mOnItemViewSelectedListener = listener; 409 } 410 411 /** 412 * Sets an item clicked listener for the results. 413 * 414 * @param listener The item clicked listener to be invoked when an item in 415 * the search results is clicked. 416 */ setOnItemViewClickedListener(OnItemViewClickedListener listener)417 public void setOnItemViewClickedListener(OnItemViewClickedListener listener) { 418 if (listener != mOnItemViewClickedListener) { 419 mOnItemViewClickedListener = listener; 420 if (mRowsFragment != null) { 421 mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener); 422 } 423 } 424 } 425 426 /** 427 * Sets the title string to be be shown in an empty search bar. The title 428 * may be placed in a call-to-action, such as "Search <i>title</i>" or 429 * "Speak to search <i>title</i>". 430 */ setTitle(String title)431 public void setTitle(String title) { 432 mTitle = title; 433 if (null != mSearchBar) { 434 mSearchBar.setTitle(title); 435 } 436 } 437 438 /** 439 * Returns the title set in the search bar. 440 */ getTitle()441 public String getTitle() { 442 if (null != mSearchBar) { 443 return mSearchBar.getTitle(); 444 } 445 return null; 446 } 447 448 /** 449 * Sets the badge drawable that will be shown inside the search bar next to 450 * the title. 451 */ setBadgeDrawable(Drawable drawable)452 public void setBadgeDrawable(Drawable drawable) { 453 mBadgeDrawable = drawable; 454 if (null != mSearchBar) { 455 mSearchBar.setBadgeDrawable(drawable); 456 } 457 } 458 459 /** 460 * Returns the badge drawable in the search bar. 461 */ getBadgeDrawable()462 public Drawable getBadgeDrawable() { 463 if (null != mSearchBar) { 464 return mSearchBar.getBadgeDrawable(); 465 } 466 return null; 467 } 468 469 /** 470 * Displays the completions shown by the IME. An application may provide 471 * a list of query completions that the system will show in the IME. 472 * 473 * @param completions A list of completions to show in the IME. Setting to 474 * null or empty will clear the list. 475 */ displayCompletions(List<String> completions)476 public void displayCompletions(List<String> completions) { 477 mSearchBar.displayCompletions(completions); 478 } 479 480 /** 481 * Sets this callback to have the fragment pass speech recognition requests 482 * to the activity rather than using an internal recognizer. 483 */ setSpeechRecognitionCallback(SpeechRecognitionCallback callback)484 public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) { 485 mSpeechRecognitionCallback = callback; 486 if (mSearchBar != null) { 487 mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback); 488 } 489 if (callback != null) { 490 releaseRecognizer(); 491 } 492 } 493 494 /** 495 * Sets the text of the search query and optionally submits the query. Either 496 * {@link SearchResultProvider#onQueryTextChange onQueryTextChange} or 497 * {@link SearchResultProvider#onQueryTextSubmit onQueryTextSubmit} will be 498 * called on the provider if it is set. 499 * 500 * @param query The search query to set. 501 * @param submit Whether to submit the query. 502 */ setSearchQuery(String query, boolean submit)503 public void setSearchQuery(String query, boolean submit) { 504 if (DEBUG) Log.v(TAG, "setSearchQuery " + query + " submit " + submit); 505 if (query == null) { 506 return; 507 } 508 mExternalQuery = new ExternalQuery(query, submit); 509 applyExternalQuery(); 510 if (mAutoStartRecognition) { 511 mAutoStartRecognition = false; 512 mHandler.removeCallbacks(mStartRecognitionRunnable); 513 } 514 } 515 516 /** 517 * Sets the text of the search query based on the {@link RecognizerIntent#EXTRA_RESULTS} in 518 * the given intent, and optionally submit the query. If more than one result is present 519 * in the results list, the first will be used. 520 * 521 * @param intent Intent received from a speech recognition service. 522 * @param submit Whether to submit the query. 523 */ setSearchQuery(Intent intent, boolean submit)524 public void setSearchQuery(Intent intent, boolean submit) { 525 ArrayList<String> matches = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); 526 if (matches != null && matches.size() > 0) { 527 setSearchQuery(matches.get(0), submit); 528 } 529 } 530 531 /** 532 * Returns an intent that can be used to request speech recognition. 533 * Built from the base {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH} plus 534 * extras: 535 * 536 * <ul> 537 * <li>{@link RecognizerIntent#EXTRA_LANGUAGE_MODEL} set to 538 * {@link RecognizerIntent#LANGUAGE_MODEL_FREE_FORM}</li> 539 * <li>{@link RecognizerIntent#EXTRA_PARTIAL_RESULTS} set to true</li> 540 * <li>{@link RecognizerIntent#EXTRA_PROMPT} set to the search bar hint text</li> 541 * </ul> 542 * 543 * For handling the intent returned from the service, see 544 * {@link #setSearchQuery(Intent, boolean)}. 545 */ getRecognizerIntent()546 public Intent getRecognizerIntent() { 547 Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 548 recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, 549 RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); 550 recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); 551 if (mSearchBar != null && mSearchBar.getHint() != null) { 552 recognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, mSearchBar.getHint()); 553 } 554 recognizerIntent.putExtra(EXTRA_LEANBACK_BADGE_PRESENT, mBadgeDrawable != null); 555 return recognizerIntent; 556 } 557 retrieveResults(String searchQuery)558 private void retrieveResults(String searchQuery) { 559 if (DEBUG) Log.v(TAG, "retrieveResults " + searchQuery); 560 if (mProvider.onQueryTextChange(searchQuery)) { 561 mStatus &= ~QUERY_COMPLETE; 562 } 563 } 564 submitQuery(String query)565 private void submitQuery(String query) { 566 queryComplete(); 567 if (null != mProvider) { 568 mProvider.onQueryTextSubmit(query); 569 } 570 } 571 queryComplete()572 private void queryComplete() { 573 if (DEBUG) Log.v(TAG, "queryComplete"); 574 mStatus |= QUERY_COMPLETE; 575 focusOnResults(); 576 } 577 updateSearchBarNextFocusId()578 private void updateSearchBarNextFocusId() { 579 if (mSearchBar == null || mResultAdapter == null) { 580 return; 581 } 582 final int viewId = (mResultAdapter.size() == 0 || mRowsFragment == null || 583 mRowsFragment.getVerticalGridView() == null) ? 0 : 584 mRowsFragment.getVerticalGridView().getId(); 585 mSearchBar.setNextFocusDownId(viewId); 586 } 587 updateFocus()588 private void updateFocus() { 589 if (mResultAdapter != null && mResultAdapter.size() > 0 && 590 mRowsFragment != null && mRowsFragment.getAdapter() == mResultAdapter) { 591 focusOnResults(); 592 } else { 593 mSearchBar.requestFocus(); 594 } 595 } 596 focusOnResults()597 private void focusOnResults() { 598 if (mRowsFragment == null || 599 mRowsFragment.getVerticalGridView() == null || 600 mResultAdapter.size() == 0) { 601 return; 602 } 603 if (mRowsFragment.getVerticalGridView().requestFocus()) { 604 mStatus &= ~RESULTS_CHANGED; 605 } 606 } 607 onSetSearchResultProvider()608 private void onSetSearchResultProvider() { 609 mHandler.removeCallbacks(mSetSearchResultProvider); 610 mHandler.post(mSetSearchResultProvider); 611 } 612 releaseAdapter()613 private void releaseAdapter() { 614 if (mResultAdapter != null) { 615 mResultAdapter.unregisterObserver(mAdapterObserver); 616 mResultAdapter = null; 617 } 618 } 619 executePendingQuery()620 private void executePendingQuery() { 621 if (null != mPendingQuery && null != mResultAdapter) { 622 String query = mPendingQuery; 623 mPendingQuery = null; 624 retrieveResults(query); 625 } 626 } 627 applyExternalQuery()628 private void applyExternalQuery() { 629 if (mExternalQuery == null || mSearchBar == null) { 630 return; 631 } 632 mSearchBar.setSearchQuery(mExternalQuery.mQuery); 633 if (mExternalQuery.mSubmit) { 634 submitQuery(mExternalQuery.mQuery); 635 } 636 mExternalQuery = null; 637 } 638 readArguments(Bundle args)639 private void readArguments(Bundle args) { 640 if (null == args) { 641 return; 642 } 643 if (args.containsKey(ARG_QUERY)) { 644 setSearchQuery(args.getString(ARG_QUERY)); 645 } 646 647 if (args.containsKey(ARG_TITLE)) { 648 setTitle(args.getString(ARG_TITLE)); 649 } 650 } 651 setSearchQuery(String query)652 private void setSearchQuery(String query) { 653 mSearchBar.setSearchQuery(query); 654 } 655 656 static class ExternalQuery { 657 String mQuery; 658 boolean mSubmit; 659 ExternalQuery(String query, boolean submit)660 ExternalQuery(String query, boolean submit) { 661 mQuery = query; 662 mSubmit = submit; 663 } 664 } 665 } 666