SearchActivity.java revision fd4a4cbc1143a734d357897531daa7105db6459b
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 private static final boolean TRACE = false; 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 // Keys for the saved instance state. 65 private static final String INSTANCE_KEY_CORPUS = "corpus"; 66 private static final String INSTANCE_KEY_QUERY = "query"; 67 68 // Measures time from for last onCreate()/onNewIntent() call. 69 private LatencyTracker mStartLatencyTracker; 70 // Measures time spent inside onCreate() 71 private LatencyTracker mOnCreateTracker; 72 private int mOnCreateLatency; 73 // Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume(). 74 private boolean mStarting; 75 // True if the user has taken some action, e.g. launching a search, voice search, 76 // or suggestions, since QSB was last started. 77 private boolean mTookAction; 78 79 private SearchActivityView mSearchActivityView; 80 81 private CorporaObserver mCorporaObserver; 82 83 private Bundle mAppSearchData; 84 85 private final Handler mHandler = new Handler(); 86 private final Runnable mUpdateSuggestionsTask = new Runnable() { 87 public void run() { 88 updateSuggestions(); 89 } 90 }; 91 92 private final Runnable mShowInputMethodTask = new Runnable() { 93 public void run() { 94 mSearchActivityView.showInputMethodForQuery(); 95 } 96 }; 97 98 private OnDestroyListener mDestroyListener; 99 100 /** Called when the activity is first created. */ 101 @Override 102 public void onCreate(Bundle savedInstanceState) { 103 if (TRACE) startMethodTracing(); 104 recordStartTime(); 105 if (DBG) Log.d(TAG, "onCreate()"); 106 super.onCreate(savedInstanceState); 107 108 mSearchActivityView = setupContentView(); 109 110 if (getConfig().showScrollingSuggestions()) { 111 mSearchActivityView.setMaxPromotedSuggestions(getConfig().getMaxPromotedSuggestions()); 112 } else { 113 mSearchActivityView.limitSuggestionsToViewHeight(); 114 } 115 if (getConfig().showScrollingResults()) { 116 mSearchActivityView.setMaxPromotedResults(getConfig().getMaxPromotedSuggestions()); 117 } else { 118 mSearchActivityView.limitResultsToViewHeight(); 119 } 120 121 mSearchActivityView.setSearchClickListener(new SearchActivityView.SearchClickListener() { 122 public boolean onSearchClicked(int method) { 123 return SearchActivity.this.onSearchClicked(method); 124 } 125 }); 126 127 mSearchActivityView.setQueryListener(new SearchActivityView.QueryListener() { 128 public void onQueryChanged() { 129 updateSuggestionsBuffered(); 130 } 131 }); 132 133 mSearchActivityView.setSuggestionClickListener(new ClickHandler()); 134 135 mSearchActivityView.setSettingsButtonClickListener(new View.OnClickListener() { 136 public void onClick(View v) { 137 onSettingsClicked(); 138 } 139 }); 140 141 mSearchActivityView.setVoiceSearchButtonClickListener(new View.OnClickListener() { 142 public void onClick(View view) { 143 onVoiceSearchClicked(); 144 } 145 }); 146 147 View.OnClickListener finishOnClick = new View.OnClickListener() { 148 public void onClick(View v) { 149 finish(); 150 } 151 }; 152 mSearchActivityView.setExitClickListener(finishOnClick); 153 mSearchActivityView.setEmptySpaceClickListener(finishOnClick); 154 155 // First get setup from intent 156 Intent intent = getIntent(); 157 setupFromIntent(intent); 158 // Then restore any saved instance state 159 restoreInstanceState(savedInstanceState); 160 161 // Do this at the end, to avoid updating the list view when setSource() 162 // is called. 163 mSearchActivityView.start(); 164 165 mCorporaObserver = new CorporaObserver(); 166 getCorpora().registerDataSetObserver(mCorporaObserver); 167 recordOnCreateDone(); 168 } 169 170 protected SearchActivityView setupContentView() { 171 setContentView(R.layout.search_activity); 172 return (SearchActivityView) findViewById(R.id.search_activity_view); 173 } 174 175 protected SearchActivityView getSearchActivityView() { 176 return mSearchActivityView; 177 } 178 179 private void startMethodTracing() { 180 File traceDir = getDir("traces", 0); 181 String traceFile = new File(traceDir, "qsb.trace").getAbsolutePath(); 182 Debug.startMethodTracing(traceFile); 183 } 184 185 @Override 186 protected void onNewIntent(Intent intent) { 187 if (DBG) Log.d(TAG, "onNewIntent()"); 188 recordStartTime(); 189 setIntent(intent); 190 setupFromIntent(intent); 191 } 192 193 private void recordStartTime() { 194 mStartLatencyTracker = new LatencyTracker(); 195 mOnCreateTracker = new LatencyTracker(); 196 mStarting = true; 197 mTookAction = false; 198 } 199 200 private void recordOnCreateDone() { 201 mOnCreateLatency = mOnCreateTracker.getLatency(); 202 } 203 204 protected void restoreInstanceState(Bundle savedInstanceState) { 205 if (savedInstanceState == null) return; 206 String corpusName = savedInstanceState.getString(INSTANCE_KEY_CORPUS); 207 String query = savedInstanceState.getString(INSTANCE_KEY_QUERY); 208 setCorpus(corpusName); 209 setQuery(query, false); 210 } 211 212 @Override 213 protected void onSaveInstanceState(Bundle outState) { 214 super.onSaveInstanceState(outState); 215 // We don't save appSearchData, since we always get the value 216 // from the intent and the user can't change it. 217 218 outState.putString(INSTANCE_KEY_CORPUS, getCorpusName()); 219 outState.putString(INSTANCE_KEY_QUERY, getQuery()); 220 } 221 222 private void setupFromIntent(Intent intent) { 223 if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")"); 224 String corpusName = getCorpusNameFromUri(intent.getData()); 225 String query = intent.getStringExtra(SearchManager.QUERY); 226 Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA); 227 boolean selectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false); 228 229 setCorpus(corpusName); 230 setQuery(query, selectAll); 231 mAppSearchData = appSearchData; 232 233 if (startedIntoCorpusSelectionDialog()) { 234 mSearchActivityView.showCorpusSelectionDialog(); 235 } 236 } 237 238 public boolean startedIntoCorpusSelectionDialog() { 239 return INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(getIntent().getAction()); 240 } 241 242 /** 243 * Removes corpus selector intent action, so that BACK works normally after 244 * dismissing and reopening the corpus selector. 245 */ 246 public void clearStartedIntoCorpusSelectionDialog() { 247 Intent oldIntent = getIntent(); 248 if (SearchActivity.INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(oldIntent.getAction())) { 249 Intent newIntent = new Intent(oldIntent); 250 newIntent.setAction(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); 251 setIntent(newIntent); 252 } 253 } 254 255 public static Uri getCorpusUri(Corpus corpus) { 256 if (corpus == null) return null; 257 return new Uri.Builder() 258 .scheme(SCHEME_CORPUS) 259 .authority(corpus.getName()) 260 .build(); 261 } 262 263 private String getCorpusNameFromUri(Uri uri) { 264 if (uri == null) return null; 265 if (!SCHEME_CORPUS.equals(uri.getScheme())) return null; 266 return uri.getAuthority(); 267 } 268 269 private Corpus getCorpus() { 270 return mSearchActivityView.getCorpus(); 271 } 272 273 private String getCorpusName() { 274 return mSearchActivityView.getCorpusName(); 275 } 276 277 private void setCorpus(String name) { 278 mSearchActivityView.setCorpus(name); 279 } 280 281 private QsbApplication getQsbApplication() { 282 return QsbApplication.get(this); 283 } 284 285 private Config getConfig() { 286 return getQsbApplication().getConfig(); 287 } 288 289 protected SearchSettings getSettings() { 290 return getQsbApplication().getSettings(); 291 } 292 293 private Corpora getCorpora() { 294 return getQsbApplication().getCorpora(); 295 } 296 297 private CorpusRanker getCorpusRanker() { 298 return getQsbApplication().getCorpusRanker(); 299 } 300 301 private ShortcutRepository getShortcutRepository() { 302 return getQsbApplication().getShortcutRepository(); 303 } 304 305 private SuggestionsProvider getSuggestionsProvider() { 306 return getQsbApplication().getSuggestionsProvider(); 307 } 308 309 private Logger getLogger() { 310 return getQsbApplication().getLogger(); 311 } 312 313 @VisibleForTesting 314 public void setOnDestroyListener(OnDestroyListener l) { 315 mDestroyListener = l; 316 } 317 318 @Override 319 protected void onDestroy() { 320 if (DBG) Log.d(TAG, "onDestroy()"); 321 getCorpora().unregisterDataSetObserver(mCorporaObserver); 322 mSearchActivityView.destroy(); 323 super.onDestroy(); 324 if (mDestroyListener != null) { 325 mDestroyListener.onDestroyed(); 326 } 327 } 328 329 @Override 330 protected void onStop() { 331 if (DBG) Log.d(TAG, "onStop()"); 332 if (!mTookAction) { 333 // TODO: This gets logged when starting other activities, e.g. by opening the search 334 // settings, or clicking a notification in the status bar. 335 // TODO we should log both sets of suggestions in 2-pane mode 336 getLogger().logExit(getCurrentSuggestions(), getQuery().length()); 337 } 338 // Close all open suggestion cursors. The query will be redone in onResume() 339 // if we come back to this activity. 340 mSearchActivityView.clearSuggestions(); 341 getQsbApplication().getShortcutRefresher().reset(); 342 mSearchActivityView.onStop(); 343 super.onStop(); 344 } 345 346 @Override 347 protected void onRestart() { 348 if (DBG) Log.d(TAG, "onRestart()"); 349 super.onRestart(); 350 } 351 352 @Override 353 protected void onResume() { 354 if (DBG) Log.d(TAG, "onResume()"); 355 super.onResume(); 356 updateSuggestionsBuffered(); 357 mSearchActivityView.onResume(); 358 if (TRACE) Debug.stopMethodTracing(); 359 } 360 361 @Override 362 public boolean onCreateOptionsMenu(Menu menu) { 363 super.onCreateOptionsMenu(menu); 364 getSettings().addMenuItems(menu); 365 return true; 366 } 367 368 @Override 369 public boolean onPrepareOptionsMenu(Menu menu) { 370 super.onPrepareOptionsMenu(menu); 371 getSettings().updateMenuItems(menu); 372 return true; 373 } 374 375 @Override 376 public void onWindowFocusChanged(boolean hasFocus) { 377 super.onWindowFocusChanged(hasFocus); 378 if (hasFocus) { 379 // Launch the IME after a bit 380 mHandler.postDelayed(mShowInputMethodTask, 0); 381 } 382 } 383 384 protected String getQuery() { 385 return mSearchActivityView.getQuery(); 386 } 387 388 protected void setQuery(String query, boolean selectAll) { 389 mSearchActivityView.setQuery(query, selectAll); 390 } 391 392 public CorpusSelectionDialog getCorpusSelectionDialog() { 393 CorpusSelectionDialog dialog = createCorpusSelectionDialog(); 394 dialog.setOwnerActivity(this); 395 dialog.setOnDismissListener(new CorpusSelectorDismissListener()); 396 return dialog; 397 } 398 399 protected CorpusSelectionDialog createCorpusSelectionDialog() { 400 return new CorpusSelectionDialog(this, getSettings()); 401 } 402 403 /** 404 * @return true if a search was performed as a result of this click, false otherwise. 405 */ 406 protected boolean onSearchClicked(int method) { 407 String query = CharMatcher.WHITESPACE.trimAndCollapseFrom(getQuery(), ' '); 408 if (DBG) Log.d(TAG, "Search clicked, query=" + query); 409 410 // Don't do empty queries 411 if (TextUtils.getTrimmedLength(query) == 0) return false; 412 413 Corpus searchCorpus = getSearchCorpus(); 414 if (searchCorpus == null) return false; 415 416 mTookAction = true; 417 418 // Log search start 419 getLogger().logSearch(getCorpus(), method, query.length()); 420 421 // Create shortcut 422 SuggestionData searchShortcut = searchCorpus.createSearchShortcut(query); 423 if (searchShortcut != null) { 424 ListSuggestionCursor cursor = new ListSuggestionCursor(query); 425 cursor.add(searchShortcut); 426 getShortcutRepository().reportClick(cursor, 0); 427 } 428 429 // Start search 430 startSearch(searchCorpus, query); 431 return true; 432 } 433 434 protected void startSearch(Corpus searchCorpus, String query) { 435 Intent intent = searchCorpus.createSearchIntent(query, mAppSearchData); 436 launchIntent(intent); 437 } 438 439 protected void onVoiceSearchClicked() { 440 if (DBG) Log.d(TAG, "Voice Search clicked"); 441 Corpus searchCorpus = getSearchCorpus(); 442 if (searchCorpus == null) return; 443 444 mTookAction = true; 445 446 // Log voice search start 447 getLogger().logVoiceSearch(searchCorpus); 448 449 // Start voice search 450 Intent intent = searchCorpus.createVoiceSearchIntent(mAppSearchData); 451 launchIntent(intent); 452 } 453 454 protected void onSettingsClicked() { 455 startActivity(getSettings().getSearchSettingsIntent()); 456 } 457 458 protected Corpus getSearchCorpus() { 459 return mSearchActivityView.getSearchCorpus(); 460 } 461 462 protected SuggestionCursor getCurrentSuggestions() { 463 return mSearchActivityView.getCurrentPromotedSuggestions(); 464 } 465 466 protected SuggestionCursor getCurrentSuggestions(SuggestionsAdapter<?> adapter, int position) { 467 SuggestionCursor suggestions = adapter.getCurrentPromotedSuggestions(); 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 suggestions; 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, int position) { 500 SuggestionCursor suggestions = getCurrentSuggestions(adapter, position); 501 if (suggestions == null) return false; 502 503 if (DBG) Log.d(TAG, "Launching suggestion " + position); 504 mTookAction = true; 505 506 // Log suggestion click 507 getLogger().logSuggestionClick(position, suggestions, getCurrentIncludedCorpora(), 508 Logger.SUGGESTION_CLICK_TYPE_LAUNCH); 509 510 // Create shortcut 511 getShortcutRepository().reportClick(suggestions, position); 512 513 // Launch intent 514 launchSuggestion(suggestions, position); 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 removeFromHistory(SuggestionsAdapter<?> adapter, int position) { 526 SuggestionCursor suggestions = getCurrentSuggestions(adapter, position); 527 if (suggestions == null) return; 528 removeFromHistory(suggestions, position); 529 // TODO: Log to event log? 530 } 531 532 protected void removeFromHistory(SuggestionCursor suggestions, int position) { 533 removeShortcut(suggestions, position); 534 removeFromHistoryDone(true); 535 } 536 537 protected void removeFromHistoryDone(boolean ok) { 538 Log.i(TAG, "Removed query from history, success=" + ok); 539 updateSuggestionsBuffered(); 540 if (!ok) { 541 Toast.makeText(this, R.string.remove_from_history_failed, Toast.LENGTH_SHORT).show(); 542 } 543 } 544 545 protected void removeShortcut(SuggestionCursor suggestions, int position) { 546 if (suggestions.isSuggestionShortcut()) { 547 if (DBG) Log.d(TAG, "Removing suggestion " + position + " from shortcuts"); 548 getShortcutRepository().removeFromHistory(suggestions, position); 549 } 550 } 551 552 protected void clickedQuickContact(SuggestionsAdapter<?> adapter, int position) { 553 SuggestionCursor suggestions = getCurrentSuggestions(adapter, position); 554 if (suggestions == null) return; 555 556 if (DBG) Log.d(TAG, "Used suggestion " + position); 557 mTookAction = true; 558 559 // Log suggestion click 560 getLogger().logSuggestionClick(position, suggestions, getCurrentIncludedCorpora(), 561 Logger.SUGGESTION_CLICK_TYPE_QUICK_CONTACT); 562 563 // Create shortcut 564 getShortcutRepository().reportClick(suggestions, position); 565 } 566 567 protected void refineSuggestion(SuggestionsAdapter<?> adapter, int position) { 568 if (DBG) Log.d(TAG, "query refine clicked, pos " + position); 569 SuggestionCursor suggestions = getCurrentSuggestions(adapter, position); 570 if (suggestions == null) { 571 return; 572 } 573 String query = suggestions.getSuggestionQuery(); 574 if (TextUtils.isEmpty(query)) { 575 return; 576 } 577 578 // Log refine click 579 getLogger().logSuggestionClick(position, suggestions, getCurrentIncludedCorpora(), 580 Logger.SUGGESTION_CLICK_TYPE_REFINE); 581 582 // Put query + space in query text view 583 String queryWithSpace = query + ' '; 584 setQuery(queryWithSpace, false); 585 updateSuggestions(); 586 mSearchActivityView.focusQueryTextView(); 587 } 588 589 private void updateSuggestionsBuffered() { 590 if (DBG) Log.d(TAG, "updateSuggestionsBuffered()"); 591 mHandler.removeCallbacks(mUpdateSuggestionsTask); 592 long delay = getConfig().getTypingUpdateSuggestionsDelayMillis(); 593 mHandler.postDelayed(mUpdateSuggestionsTask, delay); 594 } 595 596 private void gotSuggestions(Suggestions suggestions) { 597 if (mStarting) { 598 mStarting = false; 599 String source = getIntent().getStringExtra(Search.SOURCE); 600 int latency = mStartLatencyTracker.getLatency(); 601 getLogger().logStart(mOnCreateLatency, latency, source, getCorpus(), 602 suggestions == null ? null : suggestions.getExpectedCorpora()); 603 getQsbApplication().onStartupComplete(); 604 } 605 } 606 607 private void getCorporaToQuery(Consumer<List<Corpus>> consumer) { 608 Corpus corpus = getCorpus(); 609 if (corpus == null) { 610 getCorpusRanker().getCorporaInAll(Consumers.createAsyncConsumer(mHandler, consumer)); 611 } else { 612 List<Corpus> corpora = new ArrayList<Corpus>(); 613 Corpus searchCorpus = getSearchCorpus(); 614 if (searchCorpus != null) corpora.add(searchCorpus); 615 consumer.consume(corpora); 616 } 617 } 618 619 protected void getShortcutsForQuery(String query, Collection<Corpus> corporaToQuery, 620 final Suggestions suggestions) { 621 ShortcutRepository shortcutRepo = getShortcutRepository(); 622 if (shortcutRepo == null) return; 623 if (query.length() == 0 && !getConfig().showShortcutsForZeroQuery()) { 624 return; 625 } 626 Consumer<ShortcutCursor> consumer = Consumers.createAsyncCloseableConsumer(mHandler, 627 new Consumer<ShortcutCursor>() { 628 public boolean consume(ShortcutCursor shortcuts) { 629 suggestions.setShortcuts(shortcuts); 630 return true; 631 } 632 }); 633 shortcutRepo.getShortcutsForQuery(query, corporaToQuery, 634 getSettings().allowWebSearchShortcuts(), consumer); 635 } 636 637 public void updateSuggestions() { 638 if (DBG) Log.d(TAG, "updateSuggestions()"); 639 final String query = CharMatcher.WHITESPACE.trimLeadingFrom(getQuery()); 640 getQsbApplication().getSourceTaskExecutor().cancelPendingTasks(); 641 getCorporaToQuery(new Consumer<List<Corpus>>(){ 642 @Override 643 public boolean consume(List<Corpus> corporaToQuery) { 644 updateSuggestions(query, corporaToQuery); 645 return true; 646 } 647 }); 648 } 649 650 protected void updateSuggestions(String query, List<Corpus> corporaToQuery) { 651 if (DBG) Log.d(TAG, "updateSuggestions(\"" + query+"\"," + corporaToQuery + ")"); 652 Suggestions suggestions = getSuggestionsProvider().getSuggestions( 653 query, corporaToQuery); 654 getShortcutsForQuery(query, corporaToQuery, suggestions); 655 656 // Log start latency if this is the first suggestions update 657 gotSuggestions(suggestions); 658 659 showSuggestions(suggestions); 660 } 661 662 protected void showSuggestions(Suggestions suggestions) { 663 mSearchActivityView.setSuggestions(suggestions); 664 } 665 666 private class ClickHandler implements SuggestionClickListener { 667 668 public void onSuggestionQuickContactClicked(SuggestionsAdapter<?> adapter, int position) { 669 clickedQuickContact(adapter, position); 670 } 671 672 public void onSuggestionClicked(SuggestionsAdapter<?> adapter, int position) { 673 launchSuggestion(adapter, position); 674 } 675 676 public void onSuggestionRemoveFromHistoryClicked(SuggestionsAdapter<?> adapter, int position) { 677 removeFromHistory(adapter, position); 678 } 679 680 public void onSuggestionQueryRefineClicked(SuggestionsAdapter<?> adapter, int position) { 681 refineSuggestion(adapter, position); 682 } 683 } 684 685 private class CorpusSelectorDismissListener implements DialogInterface.OnDismissListener { 686 public void onDismiss(DialogInterface dialog) { 687 if (DBG) Log.d(TAG, "Corpus selector dismissed"); 688 clearStartedIntoCorpusSelectionDialog(); 689 } 690 } 691 692 private class CorporaObserver extends DataSetObserver { 693 @Override 694 public void onChanged() { 695 setCorpus(getCorpusName()); 696 updateSuggestions(); 697 } 698 } 699 700 public interface OnDestroyListener { 701 void onDestroyed(); 702 } 703 704} 705