• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
getResultsAdapter()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          */
onQueryTextChange(String newQuery)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          */
onQueryTextSubmit(String query)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
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)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      */
createArgs(Bundle args, String query)254     public static Bundle createArgs(Bundle args, String query) {
255         return createArgs(args, query, null);
256     }
257 
createArgs(Bundle args, String query, String title)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      */
newInstance(String query)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
onCreate(Bundle savedInstanceState)284     public void onCreate(Bundle savedInstanceState) {
285         if (mAutoStartRecognition) {
286             mAutoStartRecognition = savedInstanceState == null;
287         }
288         super.onCreate(savedInstanceState);
289     }
290 
291     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 
resultsAvailable()366     private void resultsAvailable() {
367         if ((mStatus & QUERY_COMPLETE) != 0) {
368             focusOnResults();
369         }
370         updateSearchBarNextFocusId();
371     }
372 
373     @Override
onStart()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
onResume()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
onPause()409     public void onPause() {
410         releaseRecognizer();
411         mIsPaused = true;
412         super.onPause();
413     }
414 
415     @Override
onDestroy()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      */
getRowsSupportFragment()427     public RowsSupportFragment getRowsSupportFragment() {
428         return mRowsSupportFragment;
429     }
430 
releaseRecognizer()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      */
startRecognition()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      */
setSearchResultProvider(SearchResultProvider searchResultProvider)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      */
setOnItemViewSelectedListener(OnItemViewSelectedListener listener)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      */
setOnItemViewClickedListener(OnItemViewClickedListener listener)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      */
setTitle(String title)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      */
getTitle()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      */
setBadgeDrawable(Drawable drawable)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      */
getBadgeDrawable()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      */
setSearchAffordanceColors(SearchOrbView.Colors colors)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      */
setSearchAffordanceColorsInListening(SearchOrbView.Colors colors)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      */
displayCompletions(List<String> completions)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      */
displayCompletions(CompletionInfo[] completions)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
setSpeechRecognitionCallback(SpeechRecognitionCallback callback)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      */
setSearchQuery(String query, boolean submit)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      */
setSearchQuery(Intent intent, boolean submit)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      */
getRecognizerIntent()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 
retrieveResults(String searchQuery)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 
submitQuery(String query)665     void submitQuery(String query) {
666         queryComplete();
667         if (null != mProvider) {
668             mProvider.onQueryTextSubmit(query);
669         }
670     }
671 
queryComplete()672     void queryComplete() {
673         if (DEBUG) Log.v(TAG, "queryComplete");
674         mStatus |= QUERY_COMPLETE;
675         focusOnResults();
676     }
677 
updateSearchBarVisibility()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 
updateSearchBarNextFocusId()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 
updateFocus()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 
focusOnResults()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 
onSetSearchResultProvider()713     private void onSetSearchResultProvider() {
714         mHandler.removeCallbacks(mSetSearchResultProvider);
715         mHandler.post(mSetSearchResultProvider);
716     }
717 
releaseAdapter()718     void releaseAdapter() {
719         if (mResultAdapter != null) {
720             mResultAdapter.unregisterObserver(mAdapterObserver);
721             mResultAdapter = null;
722         }
723     }
724 
executePendingQuery()725     void executePendingQuery() {
726         if (null != mPendingQuery && null != mResultAdapter) {
727             String query = mPendingQuery;
728             mPendingQuery = null;
729             retrieveResults(query);
730         }
731     }
732 
applyExternalQuery()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 
readArguments(Bundle args)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 
setSearchQuery(String query)757     private void setSearchQuery(String query) {
758         mSearchBar.setSearchQuery(query);
759     }
760 
761     static class ExternalQuery {
762         String mQuery;
763         boolean mSubmit;
764 
ExternalQuery(String query, boolean submit)765         ExternalQuery(String query, boolean submit) {
766             mQuery = query;
767             mSubmit = submit;
768         }
769     }
770 }
771