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 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 177 protected SearchActivityView setupContentView() { 178 setContentView(R.layout.search_activity); 179 return (SearchActivityView) findViewById(R.id.search_activity_view); 180 } 181 182 protected SearchActivityView getSearchActivityView() { 183 return mSearchActivityView; 184 } 185 186 @Override 187 protected void onNewIntent(Intent intent) { 188 if (DBG) Log.d(TAG, "onNewIntent()"); 189 recordStartTime(); 190 setIntent(intent); 191 setupFromIntent(intent); 192 } 193 194 private void recordStartTime() { 195 mStartLatencyTracker = new LatencyTracker(); 196 mOnCreateTracker = new LatencyTracker(); 197 mStarting = true; 198 mTookAction = false; 199 } 200 201 private void recordOnCreateDone() { 202 mOnCreateLatency = mOnCreateTracker.getLatency(); 203 } 204 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 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 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 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 */ 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 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 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 270 private Corpus getCorpus() { 271 return mSearchActivityView.getCorpus(); 272 } 273 274 private String getCorpusName() { 275 return mSearchActivityView.getCorpusName(); 276 } 277 278 private void setCorpus(String name) { 279 mSearchActivityView.setCorpus(name); 280 } 281 282 private QsbApplication getQsbApplication() { 283 return QsbApplication.get(this); 284 } 285 286 private Config getConfig() { 287 return getQsbApplication().getConfig(); 288 } 289 290 protected SearchSettings getSettings() { 291 return getQsbApplication().getSettings(); 292 } 293 294 private Corpora getCorpora() { 295 return getQsbApplication().getCorpora(); 296 } 297 298 private CorpusRanker getCorpusRanker() { 299 return getQsbApplication().getCorpusRanker(); 300 } 301 302 private ShortcutRepository getShortcutRepository() { 303 return getQsbApplication().getShortcutRepository(); 304 } 305 306 private SuggestionsProvider getSuggestionsProvider() { 307 return getQsbApplication().getSuggestionsProvider(); 308 } 309 310 private Logger getLogger() { 311 return getQsbApplication().getLogger(); 312 } 313 314 @VisibleForTesting 315 public void setOnDestroyListener(OnDestroyListener l) { 316 mDestroyListener = l; 317 } 318 319 @Override 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 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 348 protected void onPause() { 349 if (DBG) Log.d(TAG, "onPause()"); 350 mSearchActivityView.onPause(); 351 super.onPause(); 352 } 353 354 @Override 355 protected void onRestart() { 356 if (DBG) Log.d(TAG, "onRestart()"); 357 super.onRestart(); 358 } 359 360 @Override 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 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 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 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 391 protected String getQuery() { 392 return mSearchActivityView.getQuery(); 393 } 394 395 protected void setQuery(String query, boolean selectAll) { 396 mSearchActivityView.setQuery(query, selectAll); 397 } 398 399 public CorpusSelectionDialog getCorpusSelectionDialog() { 400 CorpusSelectionDialog dialog = createCorpusSelectionDialog(); 401 dialog.setOwnerActivity(this); 402 dialog.setOnDismissListener(new CorpusSelectorDismissListener()); 403 return dialog; 404 } 405 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 */ 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 433 protected void startSearch(Corpus searchCorpus, String query) { 434 Intent intent = searchCorpus.createSearchIntent(query, mAppSearchData); 435 launchIntent(intent); 436 } 437 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 453 protected Corpus getSearchCorpus() { 454 return mSearchActivityView.getSearchCorpus(); 455 } 456 457 protected SuggestionCursor getCurrentSuggestions() { 458 return mSearchActivityView.getCurrentPromotedSuggestions(); 459 } 460 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 480 protected Set<Corpus> getCurrentIncludedCorpora() { 481 Suggestions suggestions = mSearchActivityView.getSuggestions(); 482 return suggestions == null ? null : suggestions.getIncludedCorpora(); 483 } 484 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 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 519 protected void launchSuggestion(SuggestionCursor suggestions, int position) { 520 suggestions.moveTo(position); 521 Intent intent = SuggestionUtils.getSuggestionIntent(suggestions, mAppSearchData); 522 launchIntent(intent); 523 } 524 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 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 552 protected void removeFromHistory(SuggestionCursor suggestions, int position) { 553 removeShortcut(suggestions, position); 554 removeFromHistoryDone(true); 555 } 556 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 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 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 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 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 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 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 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 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 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 682 protected void showSuggestions(Suggestions suggestions) { 683 mSearchActivityView.setSuggestions(suggestions); 684 } 685 686 private class ClickHandler implements SuggestionClickListener { 687 688 public void onSuggestionQuickContactClicked(SuggestionsAdapter<?> adapter, long id) { 689 clickedQuickContact(adapter, id); 690 } 691 692 public void onSuggestionClicked(SuggestionsAdapter<?> adapter, long id) { 693 launchSuggestion(adapter, id); 694 } 695 696 public void onSuggestionRemoveFromHistoryClicked(SuggestionsAdapter<?> adapter, long id) { 697 removeFromHistoryClicked(adapter, id); 698 } 699 700 public void onSuggestionQueryRefineClicked(SuggestionsAdapter<?> adapter, long id) { 701 refineSuggestion(adapter, id); 702 } 703 } 704 705 private class CorpusSelectorDismissListener implements DialogInterface.OnDismissListener { 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 714 public void onChanged() { 715 setCorpus(getCorpusName()); 716 updateSuggestions(); 717 } 718 } 719 720 public interface OnDestroyListener { 721 void onDestroyed(); 722 } 723 724 } 725