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