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