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