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