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