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