• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quicksearchbox;
18 
19 import com.android.common.Search;
20 import com.android.quicksearchbox.ui.SearchActivityView;
21 import com.android.quicksearchbox.ui.SuggestionClickListener;
22 import com.android.quicksearchbox.ui.SuggestionsAdapter;
23 import com.android.quicksearchbox.util.Consumer;
24 import com.android.quicksearchbox.util.Consumers;
25 import com.google.common.annotations.VisibleForTesting;
26 import com.google.common.base.CharMatcher;
27 
28 import android.app.Activity;
29 import android.app.AlertDialog;
30 import android.app.SearchManager;
31 import android.content.DialogInterface;
32 import android.content.Intent;
33 import android.database.DataSetObserver;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.Debug;
37 import android.os.Handler;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.Menu;
41 import android.view.View;
42 import android.widget.Toast;
43 
44 import java.io.File;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.List;
48 import java.util.Set;
49 
50 /**
51  * The main activity for Quick Search Box. Shows the search UI.
52  *
53  */
54 public class SearchActivity extends Activity {
55 
56     private static final boolean DBG = false;
57     private static final String TAG = "QSB.SearchActivity";
58 
59     private static final String SCHEME_CORPUS = "qsb.corpus";
60 
61     public static final String INTENT_ACTION_QSB_AND_SELECT_CORPUS
62             = "com.android.quicksearchbox.action.QSB_AND_SELECT_CORPUS";
63 
64     private static final String INTENT_EXTRA_TRACE_START_UP = "trace_start_up";
65 
66     // Keys for the saved instance state.
67     private static final String INSTANCE_KEY_CORPUS = "corpus";
68     private static final String INSTANCE_KEY_QUERY = "query";
69 
70     private static final String ACTIVITY_HELP_CONTEXT = "search";
71 
72     private boolean mTraceStartUp;
73     // Measures time from for last onCreate()/onNewIntent() call.
74     private LatencyTracker mStartLatencyTracker;
75     // Measures time spent inside onCreate()
76     private LatencyTracker mOnCreateTracker;
77     private int mOnCreateLatency;
78     // Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume().
79     private boolean mStarting;
80     // True if the user has taken some action, e.g. launching a search, voice search,
81     // or suggestions, since QSB was last started.
82     private boolean mTookAction;
83 
84     private SearchActivityView mSearchActivityView;
85 
86     private CorporaObserver mCorporaObserver;
87 
88     private Bundle mAppSearchData;
89 
90     private final Handler mHandler = new Handler();
91     private final Runnable mUpdateSuggestionsTask = new Runnable() {
92         public void run() {
93             updateSuggestions();
94         }
95     };
96 
97     private final Runnable mShowInputMethodTask = new Runnable() {
98         public void run() {
99             mSearchActivityView.showInputMethodForQuery();
100         }
101     };
102 
103     private OnDestroyListener mDestroyListener;
104 
105     /** Called when the activity is first created. */
106     @Override
onCreate(Bundle savedInstanceState)107     public void onCreate(Bundle savedInstanceState) {
108         mTraceStartUp = getIntent().hasExtra(INTENT_EXTRA_TRACE_START_UP);
109         if (mTraceStartUp) {
110             String traceFile = new File(getDir("traces", 0), "qsb-start.trace").getAbsolutePath();
111             Log.i(TAG, "Writing start-up trace to " + traceFile);
112             Debug.startMethodTracing(traceFile);
113         }
114         recordStartTime();
115         if (DBG) Log.d(TAG, "onCreate()");
116         super.onCreate(savedInstanceState);
117 
118         // This forces the HTTP request to check the users domain to be
119         // sent as early as possible.
120         QsbApplication.get(this).getSearchBaseUrlHelper();
121 
122         mSearchActivityView = setupContentView();
123 
124         if (getConfig().showScrollingSuggestions()) {
125             mSearchActivityView.setMaxPromotedSuggestions(getConfig().getMaxPromotedSuggestions());
126         } else {
127             mSearchActivityView.limitSuggestionsToViewHeight();
128         }
129         if (getConfig().showScrollingResults()) {
130             mSearchActivityView.setMaxPromotedResults(getConfig().getMaxPromotedResults());
131         } else {
132             mSearchActivityView.limitResultsToViewHeight();
133         }
134 
135         mSearchActivityView.setSearchClickListener(new SearchActivityView.SearchClickListener() {
136             public boolean onSearchClicked(int method) {
137                 return SearchActivity.this.onSearchClicked(method);
138             }
139         });
140 
141         mSearchActivityView.setQueryListener(new SearchActivityView.QueryListener() {
142             public void onQueryChanged() {
143                 updateSuggestionsBuffered();
144             }
145         });
146 
147         mSearchActivityView.setSuggestionClickListener(new ClickHandler());
148 
149         mSearchActivityView.setVoiceSearchButtonClickListener(new View.OnClickListener() {
150             public void onClick(View view) {
151                 onVoiceSearchClicked();
152             }
153         });
154 
155         View.OnClickListener finishOnClick = new View.OnClickListener() {
156             public void onClick(View v) {
157                 finish();
158             }
159         };
160         mSearchActivityView.setExitClickListener(finishOnClick);
161 
162         // First get setup from intent
163         Intent intent = getIntent();
164         setupFromIntent(intent);
165         // Then restore any saved instance state
166         restoreInstanceState(savedInstanceState);
167 
168         // Do this at the end, to avoid updating the list view when setSource()
169         // is called.
170         mSearchActivityView.start();
171 
172         mCorporaObserver = new CorporaObserver();
173         getCorpora().registerDataSetObserver(mCorporaObserver);
174         recordOnCreateDone();
175     }
176 
setupContentView()177     protected SearchActivityView setupContentView() {
178         setContentView(R.layout.search_activity);
179         return (SearchActivityView) findViewById(R.id.search_activity_view);
180     }
181 
getSearchActivityView()182     protected SearchActivityView getSearchActivityView() {
183         return mSearchActivityView;
184     }
185 
186     @Override
onNewIntent(Intent intent)187     protected void onNewIntent(Intent intent) {
188         if (DBG) Log.d(TAG, "onNewIntent()");
189         recordStartTime();
190         setIntent(intent);
191         setupFromIntent(intent);
192     }
193 
recordStartTime()194     private void recordStartTime() {
195         mStartLatencyTracker = new LatencyTracker();
196         mOnCreateTracker = new LatencyTracker();
197         mStarting = true;
198         mTookAction = false;
199     }
200 
recordOnCreateDone()201     private void recordOnCreateDone() {
202         mOnCreateLatency = mOnCreateTracker.getLatency();
203     }
204 
restoreInstanceState(Bundle savedInstanceState)205     protected void restoreInstanceState(Bundle savedInstanceState) {
206         if (savedInstanceState == null) return;
207         String corpusName = savedInstanceState.getString(INSTANCE_KEY_CORPUS);
208         String query = savedInstanceState.getString(INSTANCE_KEY_QUERY);
209         setCorpus(corpusName);
210         setQuery(query, false);
211     }
212 
213     @Override
onSaveInstanceState(Bundle outState)214     protected void onSaveInstanceState(Bundle outState) {
215         super.onSaveInstanceState(outState);
216         // We don't save appSearchData, since we always get the value
217         // from the intent and the user can't change it.
218 
219         outState.putString(INSTANCE_KEY_CORPUS, getCorpusName());
220         outState.putString(INSTANCE_KEY_QUERY, getQuery());
221     }
222 
setupFromIntent(Intent intent)223     private void setupFromIntent(Intent intent) {
224         if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
225         String corpusName = getCorpusNameFromUri(intent.getData());
226         String query = intent.getStringExtra(SearchManager.QUERY);
227         Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
228         boolean selectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false);
229 
230         setCorpus(corpusName);
231         setQuery(query, selectAll);
232         mAppSearchData = appSearchData;
233 
234         if (startedIntoCorpusSelectionDialog()) {
235             mSearchActivityView.showCorpusSelectionDialog();
236         }
237     }
238 
startedIntoCorpusSelectionDialog()239     public boolean startedIntoCorpusSelectionDialog() {
240         return INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(getIntent().getAction());
241     }
242 
243     /**
244      * Removes corpus selector intent action, so that BACK works normally after
245      * dismissing and reopening the corpus selector.
246      */
clearStartedIntoCorpusSelectionDialog()247     public void clearStartedIntoCorpusSelectionDialog() {
248         Intent oldIntent = getIntent();
249         if (SearchActivity.INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(oldIntent.getAction())) {
250             Intent newIntent = new Intent(oldIntent);
251             newIntent.setAction(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
252             setIntent(newIntent);
253         }
254     }
255 
getCorpusUri(Corpus corpus)256     public static Uri getCorpusUri(Corpus corpus) {
257         if (corpus == null) return null;
258         return new Uri.Builder()
259                 .scheme(SCHEME_CORPUS)
260                 .authority(corpus.getName())
261                 .build();
262     }
263 
getCorpusNameFromUri(Uri uri)264     private String getCorpusNameFromUri(Uri uri) {
265         if (uri == null) return null;
266         if (!SCHEME_CORPUS.equals(uri.getScheme())) return null;
267         return uri.getAuthority();
268     }
269 
getCorpus()270     private Corpus getCorpus() {
271         return mSearchActivityView.getCorpus();
272     }
273 
getCorpusName()274     private String getCorpusName() {
275         return mSearchActivityView.getCorpusName();
276     }
277 
setCorpus(String name)278     private void setCorpus(String name) {
279         mSearchActivityView.setCorpus(name);
280     }
281 
getQsbApplication()282     private QsbApplication getQsbApplication() {
283         return QsbApplication.get(this);
284     }
285 
getConfig()286     private Config getConfig() {
287         return getQsbApplication().getConfig();
288     }
289 
getSettings()290     protected SearchSettings getSettings() {
291         return getQsbApplication().getSettings();
292     }
293 
getCorpora()294     private Corpora getCorpora() {
295         return getQsbApplication().getCorpora();
296     }
297 
getCorpusRanker()298     private CorpusRanker getCorpusRanker() {
299         return getQsbApplication().getCorpusRanker();
300     }
301 
getShortcutRepository()302     private ShortcutRepository getShortcutRepository() {
303         return getQsbApplication().getShortcutRepository();
304     }
305 
getSuggestionsProvider()306     private SuggestionsProvider getSuggestionsProvider() {
307         return getQsbApplication().getSuggestionsProvider();
308     }
309 
getLogger()310     private Logger getLogger() {
311         return getQsbApplication().getLogger();
312     }
313 
314     @VisibleForTesting
setOnDestroyListener(OnDestroyListener l)315     public void setOnDestroyListener(OnDestroyListener l) {
316         mDestroyListener = l;
317     }
318 
319     @Override
onDestroy()320     protected void onDestroy() {
321         if (DBG) Log.d(TAG, "onDestroy()");
322         getCorpora().unregisterDataSetObserver(mCorporaObserver);
323         mSearchActivityView.destroy();
324         super.onDestroy();
325         if (mDestroyListener != null) {
326             mDestroyListener.onDestroyed();
327         }
328     }
329 
330     @Override
onStop()331     protected void onStop() {
332         if (DBG) Log.d(TAG, "onStop()");
333         if (!mTookAction) {
334             // TODO: This gets logged when starting other activities, e.g. by opening the search
335             // settings, or clicking a notification in the status bar.
336             // TODO we should log both sets of suggestions in 2-pane mode
337             getLogger().logExit(getCurrentSuggestions(), getQuery().length());
338         }
339         // Close all open suggestion cursors. The query will be redone in onResume()
340         // if we come back to this activity.
341         mSearchActivityView.clearSuggestions();
342         getQsbApplication().getShortcutRefresher().reset();
343         mSearchActivityView.onStop();
344         super.onStop();
345     }
346 
347     @Override
onPause()348     protected void onPause() {
349         if (DBG) Log.d(TAG, "onPause()");
350         mSearchActivityView.onPause();
351         super.onPause();
352     }
353 
354     @Override
onRestart()355     protected void onRestart() {
356         if (DBG) Log.d(TAG, "onRestart()");
357         super.onRestart();
358     }
359 
360     @Override
onResume()361     protected void onResume() {
362         if (DBG) Log.d(TAG, "onResume()");
363         super.onResume();
364         updateSuggestionsBuffered();
365         mSearchActivityView.onResume();
366         if (mTraceStartUp) Debug.stopMethodTracing();
367     }
368 
369     @Override
onPrepareOptionsMenu(Menu menu)370     public boolean onPrepareOptionsMenu(Menu menu) {
371         // Since the menu items are dynamic, we recreate the menu every time.
372         menu.clear();
373         createMenuItems(menu, true);
374         return true;
375     }
376 
createMenuItems(Menu menu, boolean showDisabled)377     public void createMenuItems(Menu menu, boolean showDisabled) {
378         getSettings().addMenuItems(menu, showDisabled);
379         getQsbApplication().getHelp().addHelpMenuItem(menu, ACTIVITY_HELP_CONTEXT);
380     }
381 
382     @Override
onWindowFocusChanged(boolean hasFocus)383     public void onWindowFocusChanged(boolean hasFocus) {
384         super.onWindowFocusChanged(hasFocus);
385         if (hasFocus) {
386             // Launch the IME after a bit
387             mHandler.postDelayed(mShowInputMethodTask, 0);
388         }
389     }
390 
getQuery()391     protected String getQuery() {
392         return mSearchActivityView.getQuery();
393     }
394 
setQuery(String query, boolean selectAll)395     protected void setQuery(String query, boolean selectAll) {
396         mSearchActivityView.setQuery(query, selectAll);
397     }
398 
getCorpusSelectionDialog()399     public CorpusSelectionDialog getCorpusSelectionDialog() {
400         CorpusSelectionDialog dialog = createCorpusSelectionDialog();
401         dialog.setOwnerActivity(this);
402         dialog.setOnDismissListener(new CorpusSelectorDismissListener());
403         return dialog;
404     }
405 
createCorpusSelectionDialog()406     protected CorpusSelectionDialog createCorpusSelectionDialog() {
407         return new CorpusSelectionDialog(this, getSettings());
408     }
409 
410     /**
411      * @return true if a search was performed as a result of this click, false otherwise.
412      */
onSearchClicked(int method)413     protected boolean onSearchClicked(int method) {
414         String query = CharMatcher.WHITESPACE.trimAndCollapseFrom(getQuery(), ' ');
415         if (DBG) Log.d(TAG, "Search clicked, query=" + query);
416 
417         // Don't do empty queries
418         if (TextUtils.getTrimmedLength(query) == 0) return false;
419 
420         Corpus searchCorpus = getSearchCorpus();
421         if (searchCorpus == null) return false;
422 
423         mTookAction = true;
424 
425         // Log search start
426         getLogger().logSearch(getCorpus(), method, query.length());
427 
428         // Start search
429         startSearch(searchCorpus, query);
430         return true;
431     }
432 
startSearch(Corpus searchCorpus, String query)433     protected void startSearch(Corpus searchCorpus, String query) {
434         Intent intent = searchCorpus.createSearchIntent(query, mAppSearchData);
435         launchIntent(intent);
436     }
437 
onVoiceSearchClicked()438     protected void onVoiceSearchClicked() {
439         if (DBG) Log.d(TAG, "Voice Search clicked");
440         Corpus searchCorpus = getSearchCorpus();
441         if (searchCorpus == null) return;
442 
443         mTookAction = true;
444 
445         // Log voice search start
446         getLogger().logVoiceSearch(searchCorpus);
447 
448         // Start voice search
449         Intent intent = searchCorpus.createVoiceSearchIntent(mAppSearchData);
450         launchIntent(intent);
451     }
452 
getSearchCorpus()453     protected Corpus getSearchCorpus() {
454         return mSearchActivityView.getSearchCorpus();
455     }
456 
getCurrentSuggestions()457     protected SuggestionCursor getCurrentSuggestions() {
458         return mSearchActivityView.getCurrentPromotedSuggestions();
459     }
460 
getCurrentSuggestions(SuggestionsAdapter<?> adapter, long id)461     protected SuggestionPosition getCurrentSuggestions(SuggestionsAdapter<?> adapter, long id) {
462         SuggestionPosition pos = adapter.getSuggestion(id);
463         if (pos == null) {
464             return null;
465         }
466         SuggestionCursor suggestions = pos.getCursor();
467         int position = pos.getPosition();
468         if (suggestions == null) {
469             return null;
470         }
471         int count = suggestions.getCount();
472         if (position < 0 || position >= count) {
473             Log.w(TAG, "Invalid suggestion position " + position + ", count = " + count);
474             return null;
475         }
476         suggestions.moveTo(position);
477         return pos;
478     }
479 
getCurrentIncludedCorpora()480     protected Set<Corpus> getCurrentIncludedCorpora() {
481         Suggestions suggestions = mSearchActivityView.getSuggestions();
482         return suggestions == null  ? null : suggestions.getIncludedCorpora();
483     }
484 
launchIntent(Intent intent)485     protected void launchIntent(Intent intent) {
486         if (DBG) Log.d(TAG, "launchIntent " + intent);
487         if (intent == null) {
488             return;
489         }
490         try {
491             startActivity(intent);
492         } catch (RuntimeException ex) {
493             // Since the intents for suggestions specified by suggestion providers,
494             // guard against them not being handled, not allowed, etc.
495             Log.e(TAG, "Failed to start " + intent.toUri(0), ex);
496         }
497     }
498 
launchSuggestion(SuggestionsAdapter<?> adapter, long id)499     private boolean launchSuggestion(SuggestionsAdapter<?> adapter, long id) {
500         SuggestionPosition suggestion = getCurrentSuggestions(adapter, id);
501         if (suggestion == null) return false;
502 
503         if (DBG) Log.d(TAG, "Launching suggestion " + id);
504         mTookAction = true;
505 
506         // Log suggestion click
507         getLogger().logSuggestionClick(id, suggestion.getCursor(), getCurrentIncludedCorpora(),
508                 Logger.SUGGESTION_CLICK_TYPE_LAUNCH);
509 
510         // Create shortcut
511         getShortcutRepository().reportClick(suggestion.getCursor(), suggestion.getPosition());
512 
513         // Launch intent
514         launchSuggestion(suggestion.getCursor(), suggestion.getPosition());
515 
516         return true;
517     }
518 
launchSuggestion(SuggestionCursor suggestions, int position)519     protected void launchSuggestion(SuggestionCursor suggestions, int position) {
520         suggestions.moveTo(position);
521         Intent intent = SuggestionUtils.getSuggestionIntent(suggestions, mAppSearchData);
522         launchIntent(intent);
523     }
524 
removeFromHistoryClicked(final SuggestionsAdapter<?> adapter, final long id)525     protected void removeFromHistoryClicked(final SuggestionsAdapter<?> adapter,
526             final long id) {
527         SuggestionPosition suggestion = getCurrentSuggestions(adapter, id);
528         if (suggestion == null) return;
529         CharSequence title = suggestion.getSuggestionText1();
530         AlertDialog dialog = new AlertDialog.Builder(this)
531                 .setTitle(title)
532                 .setMessage(R.string.remove_from_history)
533                 .setPositiveButton(android.R.string.ok,
534                         new DialogInterface.OnClickListener() {
535                             public void onClick(DialogInterface dialog, int which) {
536                                 // TODO: what if the suggestions have changed?
537                                 removeFromHistory(adapter, id);
538                             }
539                         })
540                 .setNegativeButton(android.R.string.cancel, null)
541                 .create();
542         dialog.show();
543     }
544 
removeFromHistory(SuggestionsAdapter<?> adapter, long id)545     protected void removeFromHistory(SuggestionsAdapter<?> adapter, long id) {
546         SuggestionPosition suggestion = getCurrentSuggestions(adapter, id);
547         if (suggestion == null) return;
548         removeFromHistory(suggestion.getCursor(), suggestion.getPosition());
549         // TODO: Log to event log?
550     }
551 
removeFromHistory(SuggestionCursor suggestions, int position)552     protected void removeFromHistory(SuggestionCursor suggestions, int position) {
553         removeShortcut(suggestions, position);
554         removeFromHistoryDone(true);
555     }
556 
removeFromHistoryDone(boolean ok)557     protected void removeFromHistoryDone(boolean ok) {
558         Log.i(TAG, "Removed query from history, success=" + ok);
559         updateSuggestionsBuffered();
560         if (!ok) {
561             Toast.makeText(this, R.string.remove_from_history_failed, Toast.LENGTH_SHORT).show();
562         }
563     }
564 
removeShortcut(SuggestionCursor suggestions, int position)565     protected void removeShortcut(SuggestionCursor suggestions, int position) {
566         if (suggestions.isSuggestionShortcut()) {
567             if (DBG) Log.d(TAG, "Removing suggestion " + position + " from shortcuts");
568             getShortcutRepository().removeFromHistory(suggestions, position);
569         }
570     }
571 
clickedQuickContact(SuggestionsAdapter<?> adapter, long id)572     protected void clickedQuickContact(SuggestionsAdapter<?> adapter, long id) {
573         SuggestionPosition suggestion = getCurrentSuggestions(adapter, id);
574         if (suggestion == null) return;
575 
576         if (DBG) Log.d(TAG, "Used suggestion " + suggestion.getPosition());
577         mTookAction = true;
578 
579         // Log suggestion click
580         getLogger().logSuggestionClick(id, suggestion.getCursor(), getCurrentIncludedCorpora(),
581                 Logger.SUGGESTION_CLICK_TYPE_QUICK_CONTACT);
582 
583         // Create shortcut
584         getShortcutRepository().reportClick(suggestion.getCursor(), suggestion.getPosition());
585     }
586 
refineSuggestion(SuggestionsAdapter<?> adapter, long id)587     protected void refineSuggestion(SuggestionsAdapter<?> adapter, long id) {
588         if (DBG) Log.d(TAG, "query refine clicked, pos " + id);
589         SuggestionPosition suggestion = getCurrentSuggestions(adapter, id);
590         if (suggestion == null) {
591             return;
592         }
593         String query = suggestion.getSuggestionQuery();
594         if (TextUtils.isEmpty(query)) {
595             return;
596         }
597 
598         // Log refine click
599         getLogger().logSuggestionClick(id, suggestion.getCursor(), getCurrentIncludedCorpora(),
600                 Logger.SUGGESTION_CLICK_TYPE_REFINE);
601 
602         // Put query + space in query text view
603         String queryWithSpace = query + ' ';
604         setQuery(queryWithSpace, false);
605         updateSuggestions();
606         mSearchActivityView.focusQueryTextView();
607     }
608 
updateSuggestionsBuffered()609     private void updateSuggestionsBuffered() {
610         if (DBG) Log.d(TAG, "updateSuggestionsBuffered()");
611         mHandler.removeCallbacks(mUpdateSuggestionsTask);
612         long delay = getConfig().getTypingUpdateSuggestionsDelayMillis();
613         mHandler.postDelayed(mUpdateSuggestionsTask, delay);
614     }
615 
gotSuggestions(Suggestions suggestions)616     private void gotSuggestions(Suggestions suggestions) {
617         if (mStarting) {
618             mStarting = false;
619             String source = getIntent().getStringExtra(Search.SOURCE);
620             int latency = mStartLatencyTracker.getLatency();
621             getLogger().logStart(mOnCreateLatency, latency, source, getCorpus(),
622                     suggestions == null ? null : suggestions.getExpectedCorpora());
623             getQsbApplication().onStartupComplete();
624         }
625     }
626 
getCorporaToQuery(Consumer<List<Corpus>> consumer)627     private void getCorporaToQuery(Consumer<List<Corpus>> consumer) {
628         Corpus corpus = getCorpus();
629         if (corpus == null) {
630             getCorpusRanker().getCorporaInAll(Consumers.createAsyncConsumer(mHandler, consumer));
631         } else {
632             List<Corpus> corpora = new ArrayList<Corpus>();
633             Corpus searchCorpus = getSearchCorpus();
634             if (searchCorpus != null) corpora.add(searchCorpus);
635             consumer.consume(corpora);
636         }
637     }
638 
getShortcutsForQuery(String query, Collection<Corpus> corporaToQuery, final Suggestions suggestions)639     protected void getShortcutsForQuery(String query, Collection<Corpus> corporaToQuery,
640             final Suggestions suggestions) {
641         ShortcutRepository shortcutRepo = getShortcutRepository();
642         if (shortcutRepo == null) return;
643         if (query.length() == 0 && !getConfig().showShortcutsForZeroQuery()) {
644             return;
645         }
646         Consumer<ShortcutCursor> consumer = Consumers.createAsyncCloseableConsumer(mHandler,
647                 new Consumer<ShortcutCursor>() {
648             public boolean consume(ShortcutCursor shortcuts) {
649                 suggestions.setShortcuts(shortcuts);
650                 return true;
651             }
652         });
653         shortcutRepo.getShortcutsForQuery(query, corporaToQuery,
654                 getSettings().allowWebSearchShortcuts(), consumer);
655     }
656 
updateSuggestions()657     public void updateSuggestions() {
658         if (DBG) Log.d(TAG, "updateSuggestions()");
659         final String query = CharMatcher.WHITESPACE.trimLeadingFrom(getQuery());
660         getQsbApplication().getSourceTaskExecutor().cancelPendingTasks();
661         getCorporaToQuery(new Consumer<List<Corpus>>(){
662             @Override
663             public boolean consume(List<Corpus> corporaToQuery) {
664                 updateSuggestions(query, corporaToQuery);
665                 return true;
666             }
667         });
668     }
669 
updateSuggestions(String query, List<Corpus> corporaToQuery)670     protected void updateSuggestions(String query, List<Corpus> corporaToQuery) {
671         if (DBG) Log.d(TAG, "updateSuggestions(\"" + query+"\"," + corporaToQuery + ")");
672         Suggestions suggestions = getSuggestionsProvider().getSuggestions(
673                 query, corporaToQuery);
674         getShortcutsForQuery(query, corporaToQuery, suggestions);
675 
676         // Log start latency if this is the first suggestions update
677         gotSuggestions(suggestions);
678 
679         showSuggestions(suggestions);
680     }
681 
showSuggestions(Suggestions suggestions)682     protected void showSuggestions(Suggestions suggestions) {
683         mSearchActivityView.setSuggestions(suggestions);
684     }
685 
686     private class ClickHandler implements SuggestionClickListener {
687 
onSuggestionQuickContactClicked(SuggestionsAdapter<?> adapter, long id)688         public void onSuggestionQuickContactClicked(SuggestionsAdapter<?> adapter, long id) {
689             clickedQuickContact(adapter, id);
690         }
691 
onSuggestionClicked(SuggestionsAdapter<?> adapter, long id)692         public void onSuggestionClicked(SuggestionsAdapter<?> adapter, long id) {
693             launchSuggestion(adapter, id);
694         }
695 
onSuggestionRemoveFromHistoryClicked(SuggestionsAdapter<?> adapter, long id)696         public void onSuggestionRemoveFromHistoryClicked(SuggestionsAdapter<?> adapter, long id) {
697             removeFromHistoryClicked(adapter, id);
698         }
699 
onSuggestionQueryRefineClicked(SuggestionsAdapter<?> adapter, long id)700         public void onSuggestionQueryRefineClicked(SuggestionsAdapter<?> adapter, long id) {
701             refineSuggestion(adapter, id);
702         }
703     }
704 
705     private class CorpusSelectorDismissListener implements DialogInterface.OnDismissListener {
onDismiss(DialogInterface dialog)706         public void onDismiss(DialogInterface dialog) {
707             if (DBG) Log.d(TAG, "Corpus selector dismissed");
708             clearStartedIntoCorpusSelectionDialog();
709         }
710     }
711 
712     private class CorporaObserver extends DataSetObserver {
713         @Override
onChanged()714         public void onChanged() {
715             setCorpus(getCorpusName());
716             updateSuggestions();
717         }
718     }
719 
720     public interface OnDestroyListener {
onDestroyed()721         void onDestroyed();
722     }
723 
724 }
725