SearchActivity.java revision 3109221113ff01d16dfa7d0844f93dccb7dd643b
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 17package com.android.quicksearchbox; 18 19import com.android.common.Search; 20import com.android.quicksearchbox.ui.SearchActivityView; 21import com.android.quicksearchbox.ui.SuggestionClickListener; 22import com.android.quicksearchbox.ui.SuggestionsAdapter; 23import com.android.quicksearchbox.util.Consumer; 24import com.android.quicksearchbox.util.Consumers; 25import com.google.common.annotations.VisibleForTesting; 26import com.google.common.base.CharMatcher; 27 28import android.app.Activity; 29import android.app.SearchManager; 30import android.content.DialogInterface; 31import android.content.Intent; 32import android.database.DataSetObserver; 33import android.net.Uri; 34import android.os.Bundle; 35import android.os.Debug; 36import android.os.Handler; 37import android.text.TextUtils; 38import android.util.Log; 39import android.view.Menu; 40import android.view.MenuInflater; 41import android.view.MenuItem; 42import android.view.View; 43import android.widget.Toast; 44 45import java.io.File; 46import java.util.ArrayList; 47import java.util.Collection; 48import java.util.List; 49import java.util.Set; 50 51/** 52 * The main activity for Quick Search Box. Shows the search UI. 53 * 54 */ 55public class SearchActivity extends Activity { 56 57 private static final boolean DBG = false; 58 private static final String TAG = "QSB.SearchActivity"; 59 60 private static final String SCHEME_CORPUS = "qsb.corpus"; 61 62 public static final String INTENT_ACTION_QSB_AND_SELECT_CORPUS 63 = "com.android.quicksearchbox.action.QSB_AND_SELECT_CORPUS"; 64 65 private static final String INTENT_EXTRA_TRACE_START_UP = "trace_start_up"; 66 67 // Keys for the saved instance state. 68 private static final String INSTANCE_KEY_CORPUS = "corpus"; 69 private static final String INSTANCE_KEY_QUERY = "query"; 70 71 private boolean mTraceStartUp; 72 // Measures time from for last onCreate()/onNewIntent() call. 73 private LatencyTracker mStartLatencyTracker; 74 // Measures time spent inside onCreate() 75 private LatencyTracker mOnCreateTracker; 76 private int mOnCreateLatency; 77 // Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume(). 78 private boolean mStarting; 79 // True if the user has taken some action, e.g. launching a search, voice search, 80 // or suggestions, since QSB was last started. 81 private boolean mTookAction; 82 83 private SearchActivityView mSearchActivityView; 84 85 private CorporaObserver mCorporaObserver; 86 87 private Bundle mAppSearchData; 88 89 private final Handler mHandler = new Handler(); 90 private final Runnable mUpdateSuggestionsTask = new Runnable() { 91 public void run() { 92 updateSuggestions(); 93 } 94 }; 95 96 private final Runnable mShowInputMethodTask = new Runnable() { 97 public void run() { 98 mSearchActivityView.showInputMethodForQuery(); 99 } 100 }; 101 102 private OnDestroyListener mDestroyListener; 103 104 /** Called when the activity is first created. */ 105 @Override 106 public void onCreate(Bundle savedInstanceState) { 107 mTraceStartUp = getIntent().hasExtra(INTENT_EXTRA_TRACE_START_UP); 108 if (mTraceStartUp) { 109 String traceFile = new File(getDir("traces", 0), "qsb-start.trace").getAbsolutePath(); 110 Log.i(TAG, "Writing start-up trace to " + traceFile); 111 Debug.startMethodTracing(traceFile); 112 } 113 recordStartTime(); 114 if (DBG) Log.d(TAG, "onCreate()"); 115 super.onCreate(savedInstanceState); 116 117 // This forces the HTTP request to check the users domain to be 118 // sent as early as possible. 119 QsbApplication.get(this).getSearchBaseUrlHelper(); 120 121 mSearchActivityView = setupContentView(); 122 123 if (getConfig().showScrollingSuggestions()) { 124 mSearchActivityView.setMaxPromotedSuggestions(getConfig().getMaxPromotedSuggestions()); 125 } else { 126 mSearchActivityView.limitSuggestionsToViewHeight(); 127 } 128 if (getConfig().showScrollingResults()) { 129 mSearchActivityView.setMaxPromotedResults(getConfig().getMaxPromotedResults()); 130 } else { 131 mSearchActivityView.limitResultsToViewHeight(); 132 } 133 134 mSearchActivityView.setSearchClickListener(new SearchActivityView.SearchClickListener() { 135 public boolean onSearchClicked(int method) { 136 return SearchActivity.this.onSearchClicked(method); 137 } 138 }); 139 140 mSearchActivityView.setQueryListener(new SearchActivityView.QueryListener() { 141 public void onQueryChanged() { 142 updateSuggestionsBuffered(); 143 } 144 }); 145 146 mSearchActivityView.setSuggestionClickListener(new ClickHandler()); 147 148 mSearchActivityView.setVoiceSearchButtonClickListener(new View.OnClickListener() { 149 public void onClick(View view) { 150 onVoiceSearchClicked(); 151 } 152 }); 153 154 View.OnClickListener finishOnClick = new View.OnClickListener() { 155 public void onClick(View v) { 156 finish(); 157 } 158 }; 159 mSearchActivityView.setExitClickListener(finishOnClick); 160 mSearchActivityView.setEmptySpaceClickListener(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 addHelpMenuItem(menu); 380 } 381 382 private void addHelpMenuItem(Menu menu) { 383 Intent helpIntent = getHelpIntent(); 384 if (helpIntent != null) { 385 MenuInflater inflater = new MenuInflater(this); 386 inflater.inflate(R.menu.help, menu); 387 MenuItem item = menu.findItem(R.id.menu_help); 388 item.setIntent(helpIntent); 389 } 390 } 391 392 private Intent getHelpIntent() { 393 String helpUrl = getConfig().getHelpUrl(); 394 if (TextUtils.isEmpty(helpUrl)) return null; 395 return new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl)); 396 } 397 398 @Override 399 public void onWindowFocusChanged(boolean hasFocus) { 400 super.onWindowFocusChanged(hasFocus); 401 if (hasFocus) { 402 // Launch the IME after a bit 403 mHandler.postDelayed(mShowInputMethodTask, 0); 404 } 405 } 406 407 protected String getQuery() { 408 return mSearchActivityView.getQuery(); 409 } 410 411 protected void setQuery(String query, boolean selectAll) { 412 mSearchActivityView.setQuery(query, selectAll); 413 } 414 415 public CorpusSelectionDialog getCorpusSelectionDialog() { 416 CorpusSelectionDialog dialog = createCorpusSelectionDialog(); 417 dialog.setOwnerActivity(this); 418 dialog.setOnDismissListener(new CorpusSelectorDismissListener()); 419 return dialog; 420 } 421 422 protected CorpusSelectionDialog createCorpusSelectionDialog() { 423 return new CorpusSelectionDialog(this, getSettings()); 424 } 425 426 /** 427 * @return true if a search was performed as a result of this click, false otherwise. 428 */ 429 protected boolean onSearchClicked(int method) { 430 String query = CharMatcher.WHITESPACE.trimAndCollapseFrom(getQuery(), ' '); 431 if (DBG) Log.d(TAG, "Search clicked, query=" + query); 432 433 // Don't do empty queries 434 if (TextUtils.getTrimmedLength(query) == 0) return false; 435 436 Corpus searchCorpus = getSearchCorpus(); 437 if (searchCorpus == null) return false; 438 439 mTookAction = true; 440 441 // Log search start 442 getLogger().logSearch(getCorpus(), method, query.length()); 443 444 // Start search 445 startSearch(searchCorpus, query); 446 return true; 447 } 448 449 protected void startSearch(Corpus searchCorpus, String query) { 450 Intent intent = searchCorpus.createSearchIntent(query, mAppSearchData); 451 launchIntent(intent); 452 } 453 454 protected void onVoiceSearchClicked() { 455 if (DBG) Log.d(TAG, "Voice Search clicked"); 456 Corpus searchCorpus = getSearchCorpus(); 457 if (searchCorpus == null) return; 458 459 mTookAction = true; 460 461 // Log voice search start 462 getLogger().logVoiceSearch(searchCorpus); 463 464 // Start voice search 465 Intent intent = searchCorpus.createVoiceSearchIntent(mAppSearchData); 466 launchIntent(intent); 467 } 468 469 protected Corpus getSearchCorpus() { 470 return mSearchActivityView.getSearchCorpus(); 471 } 472 473 protected SuggestionCursor getCurrentSuggestions() { 474 return mSearchActivityView.getCurrentPromotedSuggestions(); 475 } 476 477 protected SuggestionPosition getCurrentSuggestions(SuggestionsAdapter<?> adapter, long id) { 478 SuggestionPosition pos = adapter.getSuggestion(id); 479 if (pos == null) { 480 return null; 481 } 482 SuggestionCursor suggestions = pos.getCursor(); 483 int position = pos.getPosition(); 484 if (suggestions == null) { 485 return null; 486 } 487 int count = suggestions.getCount(); 488 if (position < 0 || position >= count) { 489 Log.w(TAG, "Invalid suggestion position " + position + ", count = " + count); 490 return null; 491 } 492 suggestions.moveTo(position); 493 return pos; 494 } 495 496 protected Set<Corpus> getCurrentIncludedCorpora() { 497 Suggestions suggestions = mSearchActivityView.getSuggestions(); 498 return suggestions == null ? null : suggestions.getIncludedCorpora(); 499 } 500 501 protected void launchIntent(Intent intent) { 502 if (DBG) Log.d(TAG, "launchIntent " + intent); 503 if (intent == null) { 504 return; 505 } 506 try { 507 startActivity(intent); 508 } catch (RuntimeException ex) { 509 // Since the intents for suggestions specified by suggestion providers, 510 // guard against them not being handled, not allowed, etc. 511 Log.e(TAG, "Failed to start " + intent.toUri(0), ex); 512 } 513 } 514 515 private boolean launchSuggestion(SuggestionsAdapter<?> adapter, long id) { 516 SuggestionPosition suggestion = getCurrentSuggestions(adapter, id); 517 if (suggestion == null) return false; 518 519 if (DBG) Log.d(TAG, "Launching suggestion " + id); 520 mTookAction = true; 521 522 // Log suggestion click 523 getLogger().logSuggestionClick(id, suggestion.getCursor(), getCurrentIncludedCorpora(), 524 Logger.SUGGESTION_CLICK_TYPE_LAUNCH); 525 526 // Create shortcut 527 getShortcutRepository().reportClick(suggestion.getCursor(), suggestion.getPosition()); 528 529 // Launch intent 530 launchSuggestion(suggestion.getCursor(), suggestion.getPosition()); 531 532 return true; 533 } 534 535 protected void launchSuggestion(SuggestionCursor suggestions, int position) { 536 suggestions.moveTo(position); 537 Intent intent = SuggestionUtils.getSuggestionIntent(suggestions, mAppSearchData); 538 launchIntent(intent); 539 } 540 541 protected void removeFromHistory(SuggestionsAdapter<?> adapter, long id) { 542 SuggestionPosition suggestion = getCurrentSuggestions(adapter, id); 543 if (suggestion == null) return; 544 removeFromHistory(suggestion.getCursor(), suggestion.getPosition()); 545 // TODO: Log to event log? 546 } 547 548 protected void removeFromHistory(SuggestionCursor suggestions, int position) { 549 removeShortcut(suggestions, position); 550 removeFromHistoryDone(true); 551 } 552 553 protected void removeFromHistoryDone(boolean ok) { 554 Log.i(TAG, "Removed query from history, success=" + ok); 555 updateSuggestionsBuffered(); 556 if (!ok) { 557 Toast.makeText(this, R.string.remove_from_history_failed, Toast.LENGTH_SHORT).show(); 558 } 559 } 560 561 protected void removeShortcut(SuggestionCursor suggestions, int position) { 562 if (suggestions.isSuggestionShortcut()) { 563 if (DBG) Log.d(TAG, "Removing suggestion " + position + " from shortcuts"); 564 getShortcutRepository().removeFromHistory(suggestions, position); 565 } 566 } 567 568 protected void clickedQuickContact(SuggestionsAdapter<?> adapter, long id) { 569 SuggestionPosition suggestion = getCurrentSuggestions(adapter, id); 570 if (suggestion == null) return; 571 572 if (DBG) Log.d(TAG, "Used suggestion " + suggestion.getPosition()); 573 mTookAction = true; 574 575 // Log suggestion click 576 getLogger().logSuggestionClick(id, suggestion.getCursor(), getCurrentIncludedCorpora(), 577 Logger.SUGGESTION_CLICK_TYPE_QUICK_CONTACT); 578 579 // Create shortcut 580 getShortcutRepository().reportClick(suggestion.getCursor(), suggestion.getPosition()); 581 } 582 583 protected void refineSuggestion(SuggestionsAdapter<?> adapter, long id) { 584 if (DBG) Log.d(TAG, "query refine clicked, pos " + id); 585 SuggestionPosition suggestion = getCurrentSuggestions(adapter, id); 586 if (suggestion == null) { 587 return; 588 } 589 String query = suggestion.getSuggestionQuery(); 590 if (TextUtils.isEmpty(query)) { 591 return; 592 } 593 594 // Log refine click 595 getLogger().logSuggestionClick(id, suggestion.getCursor(), getCurrentIncludedCorpora(), 596 Logger.SUGGESTION_CLICK_TYPE_REFINE); 597 598 // Put query + space in query text view 599 String queryWithSpace = query + ' '; 600 setQuery(queryWithSpace, false); 601 updateSuggestions(); 602 mSearchActivityView.focusQueryTextView(); 603 } 604 605 private void updateSuggestionsBuffered() { 606 if (DBG) Log.d(TAG, "updateSuggestionsBuffered()"); 607 mHandler.removeCallbacks(mUpdateSuggestionsTask); 608 long delay = getConfig().getTypingUpdateSuggestionsDelayMillis(); 609 mHandler.postDelayed(mUpdateSuggestionsTask, delay); 610 } 611 612 private void gotSuggestions(Suggestions suggestions) { 613 if (mStarting) { 614 mStarting = false; 615 String source = getIntent().getStringExtra(Search.SOURCE); 616 int latency = mStartLatencyTracker.getLatency(); 617 getLogger().logStart(mOnCreateLatency, latency, source, getCorpus(), 618 suggestions == null ? null : suggestions.getExpectedCorpora()); 619 getQsbApplication().onStartupComplete(); 620 } 621 } 622 623 private void getCorporaToQuery(Consumer<List<Corpus>> consumer) { 624 Corpus corpus = getCorpus(); 625 if (corpus == null) { 626 getCorpusRanker().getCorporaInAll(Consumers.createAsyncConsumer(mHandler, consumer)); 627 } else { 628 List<Corpus> corpora = new ArrayList<Corpus>(); 629 Corpus searchCorpus = getSearchCorpus(); 630 if (searchCorpus != null) corpora.add(searchCorpus); 631 consumer.consume(corpora); 632 } 633 } 634 635 protected void getShortcutsForQuery(String query, Collection<Corpus> corporaToQuery, 636 final Suggestions suggestions) { 637 ShortcutRepository shortcutRepo = getShortcutRepository(); 638 if (shortcutRepo == null) return; 639 if (query.length() == 0 && !getConfig().showShortcutsForZeroQuery()) { 640 return; 641 } 642 Consumer<ShortcutCursor> consumer = Consumers.createAsyncCloseableConsumer(mHandler, 643 new Consumer<ShortcutCursor>() { 644 public boolean consume(ShortcutCursor shortcuts) { 645 suggestions.setShortcuts(shortcuts); 646 return true; 647 } 648 }); 649 shortcutRepo.getShortcutsForQuery(query, corporaToQuery, 650 getSettings().allowWebSearchShortcuts(), consumer); 651 } 652 653 public void updateSuggestions() { 654 if (DBG) Log.d(TAG, "updateSuggestions()"); 655 final String query = CharMatcher.WHITESPACE.trimLeadingFrom(getQuery()); 656 getQsbApplication().getSourceTaskExecutor().cancelPendingTasks(); 657 getCorporaToQuery(new Consumer<List<Corpus>>(){ 658 @Override 659 public boolean consume(List<Corpus> corporaToQuery) { 660 updateSuggestions(query, corporaToQuery); 661 return true; 662 } 663 }); 664 } 665 666 protected void updateSuggestions(String query, List<Corpus> corporaToQuery) { 667 if (DBG) Log.d(TAG, "updateSuggestions(\"" + query+"\"," + corporaToQuery + ")"); 668 Suggestions suggestions = getSuggestionsProvider().getSuggestions( 669 query, corporaToQuery); 670 getShortcutsForQuery(query, corporaToQuery, suggestions); 671 672 // Log start latency if this is the first suggestions update 673 gotSuggestions(suggestions); 674 675 showSuggestions(suggestions); 676 } 677 678 protected void showSuggestions(Suggestions suggestions) { 679 mSearchActivityView.setSuggestions(suggestions); 680 } 681 682 private class ClickHandler implements SuggestionClickListener { 683 684 public void onSuggestionQuickContactClicked(SuggestionsAdapter<?> adapter, long id) { 685 clickedQuickContact(adapter, id); 686 } 687 688 public void onSuggestionClicked(SuggestionsAdapter<?> adapter, long id) { 689 launchSuggestion(adapter, id); 690 } 691 692 public void onSuggestionRemoveFromHistoryClicked(SuggestionsAdapter<?> adapter, long id) { 693 removeFromHistory(adapter, id); 694 } 695 696 public void onSuggestionQueryRefineClicked(SuggestionsAdapter<?> adapter, long id) { 697 refineSuggestion(adapter, id); 698 } 699 } 700 701 private class CorpusSelectorDismissListener implements DialogInterface.OnDismissListener { 702 public void onDismiss(DialogInterface dialog) { 703 if (DBG) Log.d(TAG, "Corpus selector dismissed"); 704 clearStartedIntoCorpusSelectionDialog(); 705 } 706 } 707 708 private class CorporaObserver extends DataSetObserver { 709 @Override 710 public void onChanged() { 711 setCorpus(getCorpusName()); 712 updateSuggestions(); 713 } 714 } 715 716 public interface OnDestroyListener { 717 void onDestroyed(); 718 } 719 720} 721