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