SearchActivity.java revision fde948e69f59589cf0d217ea414af7947de600bb
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.CorpusIndicator; 21import com.android.quicksearchbox.ui.SuggestionClickListener; 22import com.android.quicksearchbox.ui.SuggestionSelectionListener; 23import com.android.quicksearchbox.ui.SuggestionViewFactory; 24import com.android.quicksearchbox.ui.SuggestionsAdapter; 25import com.android.quicksearchbox.ui.SuggestionsView; 26 27import android.app.Activity; 28import android.app.Dialog; 29import android.app.SearchManager; 30import android.content.Intent; 31import android.database.DataSetObserver; 32import android.graphics.drawable.Animatable; 33import android.graphics.drawable.Drawable; 34import android.net.Uri; 35import android.os.Bundle; 36import android.os.SystemClock; 37import android.text.Editable; 38import android.text.TextUtils; 39import android.text.TextWatcher; 40import android.util.Log; 41import android.view.KeyEvent; 42import android.view.Menu; 43import android.view.View; 44import android.view.View.OnFocusChangeListener; 45import android.view.inputmethod.InputMethodManager; 46import android.widget.EditText; 47import android.widget.ImageButton; 48 49import java.util.ArrayList; 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 = true; 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 // Keys for the saved instance state. 66 private static final String INSTANCE_KEY_CORPUS = "corpus"; 67 private static final String INSTANCE_KEY_USER_QUERY = "query"; 68 69 // Dialog IDs 70 private static final int CORPUS_SELECTION_DIALOG = 0; 71 72 // Timestamp for last onCreate()/onNewIntent() call, as returned by SystemClock.uptimeMillis(). 73 private long mStartTime; 74 // Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume(). 75 private boolean mStarting; 76 // True if the user has taken some action, e.g. launching a search, voice search, 77 // or suggestions, since QSB was last started. 78 private boolean mTookAction; 79 80 protected SuggestionsAdapter mSuggestionsAdapter; 81 82 protected EditText mQueryTextView; 83 84 protected SuggestionsView mSuggestionsView; 85 86 protected ImageButton mSearchGoButton; 87 protected ImageButton mVoiceSearchButton; 88 protected CorpusIndicator mCorpusIndicator; 89 90 private Launcher mLauncher; 91 92 private Corpus mCorpus; 93 private Bundle mAppSearchData; 94 private boolean mUpdateSuggestions; 95 private String mUserQuery; 96 private boolean mSelectAll; 97 98 /** Called when the activity is first created. */ 99 @Override 100 public void onCreate(Bundle savedInstanceState) { 101 recordStartTime(); 102 if (DBG) Log.d(TAG, "onCreate()"); 103 super.onCreate(savedInstanceState); 104 105 setContentView(R.layout.search_bar); 106 107 mSuggestionsAdapter = getQsbApplication().createSuggestionsAdapter(); 108 109 mQueryTextView = (EditText) findViewById(R.id.search_src_text); 110 mSuggestionsView = (SuggestionsView) findViewById(R.id.suggestions); 111 mSuggestionsView.setSuggestionClickListener(new ClickHandler()); 112 mSuggestionsView.setSuggestionSelectionListener(new SelectionHandler()); 113 mSuggestionsView.setInteractionListener(new InputMethodCloser()); 114 mSuggestionsView.setOnKeyListener(new SuggestionsViewKeyListener()); 115 mSuggestionsView.setOnFocusChangeListener(new SuggestListFocusListener()); 116 117 mSearchGoButton = (ImageButton) findViewById(R.id.search_go_btn); 118 mVoiceSearchButton = (ImageButton) findViewById(R.id.search_voice_btn); 119 mCorpusIndicator = new CorpusIndicator(findViewById(R.id.corpus_indicator)); 120 121 mLauncher = new Launcher(this); 122 123 mQueryTextView.addTextChangedListener(new SearchTextWatcher()); 124 mQueryTextView.setOnKeyListener(new QueryTextViewKeyListener()); 125 mQueryTextView.setOnFocusChangeListener(new QueryTextViewFocusListener()); 126 127 mCorpusIndicator.setOnClickListener(new CorpusIndicatorClickListener()); 128 129 mSearchGoButton.setOnClickListener(new SearchGoButtonClickListener()); 130 131 mVoiceSearchButton.setOnClickListener(new VoiceSearchButtonClickListener()); 132 133 ButtonsKeyListener buttonsKeyListener = new ButtonsKeyListener(); 134 mSearchGoButton.setOnKeyListener(buttonsKeyListener); 135 mVoiceSearchButton.setOnKeyListener(buttonsKeyListener); 136 mCorpusIndicator.setOnKeyListener(buttonsKeyListener); 137 138 mUpdateSuggestions = true; 139 140 // First get setup from intent 141 Intent intent = getIntent(); 142 setupFromIntent(intent); 143 // Then restore any saved instance state 144 restoreInstanceState(savedInstanceState); 145 146 // Do this at the end, to avoid updating the list view when setSource() 147 // is called. 148 mSuggestionsView.setAdapter(mSuggestionsAdapter); 149 } 150 151 @Override 152 protected void onNewIntent(Intent intent) { 153 recordStartTime(); 154 setIntent(intent); 155 setupFromIntent(intent); 156 } 157 158 private void recordStartTime() { 159 mStartTime = SystemClock.uptimeMillis(); 160 mStarting = true; 161 mTookAction = false; 162 } 163 164 protected void restoreInstanceState(Bundle savedInstanceState) { 165 if (savedInstanceState == null) return; 166 String corpusName = savedInstanceState.getString(INSTANCE_KEY_CORPUS); 167 String query = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY); 168 setCorpus(getCorpus(corpusName)); 169 setUserQuery(query); 170 } 171 172 @Override 173 protected void onSaveInstanceState(Bundle outState) { 174 super.onSaveInstanceState(outState); 175 // We don't save appSearchData, since we always get the value 176 // from the intent and the user can't change it. 177 178 String corpusName = mCorpus == null ? null : mCorpus.getName(); 179 outState.putString(INSTANCE_KEY_CORPUS, corpusName); 180 outState.putString(INSTANCE_KEY_USER_QUERY, mUserQuery); 181 } 182 183 private void setupFromIntent(Intent intent) { 184 if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")"); 185 Corpus corpus = getCorpusFromUri(intent.getData()); 186 String query = intent.getStringExtra(SearchManager.QUERY); 187 Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA); 188 189 setCorpus(corpus); 190 setUserQuery(query); 191 mSelectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false); 192 setAppSearchData(appSearchData); 193 194 if (INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(intent.getAction())) { 195 showSourceSelectorDialog(); 196 } 197 } 198 199 public static Uri getCorpusUri(Corpus corpus) { 200 if (corpus == null) return null; 201 return new Uri.Builder() 202 .scheme(SCHEME_CORPUS) 203 .authority(corpus.getName()) 204 .build(); 205 } 206 207 private Corpus getCorpusFromUri(Uri uri) { 208 if (uri == null) return null; 209 if (!SCHEME_CORPUS.equals(uri.getScheme())) return null; 210 String name = uri.getAuthority(); 211 return getCorpus(name); 212 } 213 214 private Corpus getCorpus(String sourceName) { 215 if (sourceName == null) return null; 216 Corpus corpus = getCorpora().getCorpus(sourceName); 217 if (corpus == null) { 218 Log.w(TAG, "Unknown corpus " + sourceName); 219 return null; 220 } 221 return corpus; 222 } 223 224 private void setCorpus(Corpus corpus) { 225 if (DBG) Log.d(TAG, "setCorpus(" + corpus + ")"); 226 mCorpus = corpus; 227 Drawable sourceIcon; 228 if (corpus == null) { 229 sourceIcon = getSuggestionViewFactory().getGlobalSearchIcon(); 230 } else { 231 sourceIcon = corpus.getCorpusIcon(); 232 } 233 mSuggestionsAdapter.setCorpus(corpus); 234 mCorpusIndicator.setSourceIcon(sourceIcon); 235 236 boolean enableVoiceSearch = Launcher.shouldShowVoiceSearch(this, mCorpus); 237 mVoiceSearchButton.setVisibility(enableVoiceSearch ? View.VISIBLE : View.GONE); 238 } 239 240 private QsbApplication getQsbApplication() { 241 return (QsbApplication) getApplication(); 242 } 243 244 private Corpora getCorpora() { 245 return getQsbApplication().getCorpora(); 246 } 247 248 private CorpusRanker getCorpusRanker() { 249 return getQsbApplication().getCorpusRanker(); 250 } 251 252 private ShortcutRepository getShortcutRepository() { 253 return getQsbApplication().getShortcutRepository(); 254 } 255 256 private SuggestionsProvider getSuggestionsProvider() { 257 return getQsbApplication().getSuggestionsProvider(mCorpus); 258 } 259 260 private SuggestionViewFactory getSuggestionViewFactory() { 261 return getQsbApplication().getSuggestionViewFactory(); 262 } 263 264 private Logger getLogger() { 265 return getQsbApplication().getLogger(); 266 } 267 268 @Override 269 protected void onDestroy() { 270 if (DBG) Log.d(TAG, "onDestroy()"); 271 super.onDestroy(); 272 mSuggestionsView.setAdapter(null); // closes mSuggestionsAdapter 273 } 274 275 @Override 276 protected void onStop() { 277 if (DBG) Log.d(TAG, "onStop()"); 278 // Close all open suggestion cursors. The query will be redone in onResume() 279 // if we come back to this activity. 280 mSuggestionsAdapter.setSuggestions(null); 281 getQsbApplication().getShortcutRefresher().reset(); 282 super.onStop(); 283 } 284 285 @Override 286 protected void onResume() { 287 if (DBG) Log.d(TAG, "onResume()"); 288 super.onResume(); 289 setQuery(mUserQuery, mSelectAll); 290 // Only select everything the first time after creating the activity. 291 mSelectAll = false; 292 updateSuggestions(mUserQuery); 293 mQueryTextView.requestFocus(); 294 if (mStarting) { 295 mStarting = false; 296 // Start up latency should not exceed 2^31 ms (~ 25 days). Note that 297 // SystemClock.uptimeMillis() does not advance during deep sleep. 298 int latency = (int) (SystemClock.uptimeMillis() - mStartTime); 299 String source = getIntent().getStringExtra(Search.SOURCE); 300 getLogger().logStart(latency, source, mCorpus, 301 getSuggestionsProvider().getOrderedCorpora()); 302 } 303 } 304 305 @Override 306 public boolean onCreateOptionsMenu(Menu menu) { 307 super.onCreateOptionsMenu(menu); 308 309 Intent settings = new Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS); 310 settings.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 311 // Don't show activity chooser if there are multiple search settings activities, 312 // e.g. from different QSB implementations. 313 settings.setPackage(this.getPackageName()); 314 menu.add(Menu.NONE, Menu.NONE, 0, R.string.menu_settings) 315 .setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P') 316 .setIntent(settings); 317 318 return true; 319 } 320 321 /** 322 * Sets the query as typed by the user. Does not update the suggestions 323 * or the text in the query box. 324 */ 325 protected void setUserQuery(String userQuery) { 326 if (userQuery == null) userQuery = ""; 327 mUserQuery = userQuery; 328 } 329 330 protected void setAppSearchData(Bundle appSearchData) { 331 mAppSearchData = appSearchData; 332 mLauncher.setAppSearchData(appSearchData); 333 } 334 335 protected String getQuery() { 336 CharSequence q = mQueryTextView.getText(); 337 return q == null ? "" : q.toString(); 338 } 339 340 /** 341 * Restores the query entered by the user. 342 */ 343 private void restoreUserQuery() { 344 if (DBG) Log.d(TAG, "Restoring query to '" + mUserQuery + "'"); 345 setQuery(mUserQuery, false); 346 } 347 348 /** 349 * Sets the text in the query box. Does not update the suggestions, 350 * and does not change the saved user-entered query. 351 * {@link #restoreUserQuery()} will restore the query to the last 352 * user-entered query. 353 */ 354 private void setQuery(String query, boolean selectAll) { 355 mUpdateSuggestions = false; 356 mQueryTextView.setText(query); 357 setTextSelection(selectAll); 358 mUpdateSuggestions = true; 359 } 360 361 /** 362 * Sets the text selection in the query text view. 363 * 364 * @param selectAll If {@code true}, selects the entire query. 365 * If {@false}, no characters are selected, and the cursor is placed 366 * at the end of the query. 367 */ 368 private void setTextSelection(boolean selectAll) { 369 if (selectAll) { 370 mQueryTextView.setSelection(0, mQueryTextView.length()); 371 } else { 372 mQueryTextView.setSelection(mQueryTextView.length()); 373 } 374 } 375 376 protected void showSourceSelectorDialog() { 377 showDialog(CORPUS_SELECTION_DIALOG); 378 } 379 380 381 @Override 382 protected Dialog onCreateDialog(int id, Bundle args) { 383 switch (id) { 384 case CORPUS_SELECTION_DIALOG: 385 return createCorpusSelectionDialog(); 386 default: 387 throw new IllegalArgumentException("Unknown dialog: " + id); 388 } 389 } 390 391 @Override 392 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { 393 switch (id) { 394 case CORPUS_SELECTION_DIALOG: 395 prepareCorpusSelectionDialog((CorpusSelectionDialog) dialog); 396 break; 397 default: 398 throw new IllegalArgumentException("Unknown dialog: " + id); 399 } 400 } 401 402 protected CorpusSelectionDialog createCorpusSelectionDialog() { 403 CorpusSelectionDialog dialog = new CorpusSelectionDialog(this); 404 dialog.setOwnerActivity(this); 405 return dialog; 406 } 407 408 protected void prepareCorpusSelectionDialog(CorpusSelectionDialog dialog) { 409 dialog.setCorpus(mCorpus); 410 dialog.setQuery(getQuery()); 411 dialog.setAppData(mAppSearchData); 412 } 413 414 protected void onSearchClicked(int method) { 415 String query = getQuery(); 416 if (DBG) Log.d(TAG, "Search clicked, query=" + query); 417 mTookAction = true; 418 getLogger().logSearch(mCorpus, method, query.length()); 419 mLauncher.startSearch(mCorpus, query); 420 } 421 422 protected void onVoiceSearchClicked() { 423 if (DBG) Log.d(TAG, "Voice Search clicked"); 424 mTookAction = true; 425 getLogger().logVoiceSearch(mCorpus); 426 427 // TODO: should this start voice search in the current source? 428 mLauncher.startVoiceSearch(mCorpus); 429 } 430 431 protected SuggestionCursor getSuggestions() { 432 return mSuggestionsAdapter.getCurrentSuggestions(); 433 } 434 435 protected boolean launchSuggestion(int position) { 436 if (DBG) Log.d(TAG, "Launching suggestion " + position); 437 mTookAction = true; 438 SuggestionCursor suggestions = getSuggestions(); 439 // TODO: This should be just the queried sources, but currently 440 // all sources are queried 441 ArrayList<Corpus> corpora = getCorpusRanker().rankCorpora(getCorpora().getEnabledCorpora()); 442 getLogger().logSuggestionClick(position, suggestions, corpora); 443 444 mLauncher.launchSuggestion(suggestions, position); 445 getShortcutRepository().reportClick(suggestions, position); 446 return true; 447 } 448 449 protected boolean onSuggestionLongClicked(int position) { 450 if (DBG) Log.d(TAG, "Long clicked on suggestion " + position); 451 return false; 452 } 453 454 protected boolean onSuggestionKeyDown(int position, int keyCode, KeyEvent event) { 455 // Treat enter or search as a click 456 if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) { 457 return launchSuggestion(position); 458 } 459 460 if (keyCode == KeyEvent.KEYCODE_DPAD_UP 461 && mSuggestionsView.getSelectedItemPosition() == 0) { 462 // Moved up from the top suggestion, restore the user query and focus query box 463 if (DBG) Log.d(TAG, "Up and out"); 464 restoreUserQuery(); 465 return false; // let the framework handle the move 466 } 467 468 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT 469 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { 470 // Moved left / right from a suggestion, keep current query, move 471 // focus to query box, and move cursor to far left / right 472 if (DBG) Log.d(TAG, "Left/right on a suggestion"); 473 int cursorPos = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView.length(); 474 mQueryTextView.setSelection(cursorPos); 475 mQueryTextView.requestFocus(); 476 // TODO: should we modify the list selection? 477 return true; 478 } 479 480 return false; 481 } 482 483 protected void onSourceSelected() { 484 if (DBG) Log.d(TAG, "No suggestion selected"); 485 restoreUserQuery(); 486 } 487 488 protected int getSelectedPosition() { 489 return mSuggestionsView.getSelectedPosition(); 490 } 491 492 /** 493 * Hides the input method. 494 */ 495 protected void hideInputMethod() { 496 InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); 497 if (imm != null) { 498 imm.hideSoftInputFromWindow(mQueryTextView.getWindowToken(), 0); 499 } 500 } 501 502 protected void showInputMethodForQuery() { 503 InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); 504 if (imm != null) { 505 imm.showSoftInput(mQueryTextView, 0); 506 } 507 } 508 509 /** 510 * Hides the input method when the suggestions get focus. 511 */ 512 private class SuggestListFocusListener implements OnFocusChangeListener { 513 public void onFocusChange(View v, boolean focused) { 514 if (DBG) Log.d(TAG, "Suggestions focus change, now: " + focused); 515 if (focused) { 516 // The suggestions list got focus, hide the input method 517 hideInputMethod(); 518 } 519 } 520 } 521 522 private class QueryTextViewFocusListener implements OnFocusChangeListener { 523 public void onFocusChange(View v, boolean focused) { 524 if (DBG) Log.d(TAG, "Query focus change, now: " + focused); 525 if (focused) { 526 // The query box got focus, show the input method if the 527 // query box got focus? 528 showInputMethodForQuery(); 529 } 530 } 531 } 532 533 private void startSearchProgress() { 534 // TODO: Cache animation between calls? 535 mSearchGoButton.setImageResource(R.drawable.searching); 536 Animatable animation = (Animatable) mSearchGoButton.getDrawable(); 537 animation.start(); 538 } 539 540 private void stopSearchProgress() { 541 Drawable animation = mSearchGoButton.getDrawable(); 542 if (animation instanceof Animatable) { 543 // TODO: Is this needed, or is it done automatically when the 544 // animation is removed? 545 ((Animatable) animation).stop(); 546 } 547 mSearchGoButton.setImageResource(R.drawable.ic_btn_search); 548 } 549 550 private void updateSuggestions(String query) { 551 query = ltrim(query); 552 LatencyTracker latency = new LatencyTracker(TAG); 553 Suggestions suggestions = getSuggestionsProvider().getSuggestions(query); 554 latency.addEvent("getSuggestions_done"); 555 if (!suggestions.isDone()) { 556 suggestions.registerDataSetObserver(new ProgressUpdater(suggestions)); 557 startSearchProgress(); 558 } else { 559 stopSearchProgress(); 560 } 561 mSuggestionsAdapter.setSuggestions(suggestions); 562 latency.addEvent("shortcuts_shown"); 563 long userVisibleLatency = latency.getUserVisibleLatency(); 564 if (DBG) { 565 Log.d(TAG, "User visible latency (shortcuts): " + userVisibleLatency + " ms."); 566 } 567 } 568 569 private boolean forwardKeyToQueryTextView(int keyCode, KeyEvent event) { 570 if (!event.isSystem() && !isDpadKey(keyCode)) { 571 if (DBG) Log.d(TAG, "Forwarding key to query box: " + event); 572 if (mQueryTextView.requestFocus()) { 573 return mQueryTextView.dispatchKeyEvent(event); 574 } 575 } 576 return false; 577 } 578 579 private boolean isDpadKey(int keyCode) { 580 switch (keyCode) { 581 case KeyEvent.KEYCODE_DPAD_UP: 582 case KeyEvent.KEYCODE_DPAD_DOWN: 583 case KeyEvent.KEYCODE_DPAD_LEFT: 584 case KeyEvent.KEYCODE_DPAD_RIGHT: 585 case KeyEvent.KEYCODE_DPAD_CENTER: 586 return true; 587 default: 588 return false; 589 } 590 } 591 592 /** 593 * Filters the suggestions list when the search text changes. 594 */ 595 private class SearchTextWatcher implements TextWatcher { 596 public void afterTextChanged(Editable s) { 597 if (mUpdateSuggestions) { 598 String query = s == null ? "" : s.toString(); 599 setUserQuery(query); 600 updateSuggestions(query); 601 } 602 } 603 604 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 605 } 606 607 public void onTextChanged(CharSequence s, int start, int before, int count) { 608 } 609 } 610 611 /** 612 * Handles non-text keys in the query text view. 613 */ 614 private class QueryTextViewKeyListener implements View.OnKeyListener { 615 public boolean onKey(View view, int keyCode, KeyEvent event) { 616 // Handle IME search action key 617 if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) { 618 onSearchClicked(Logger.SEARCH_METHOD_KEYBOARD); 619 } 620 return false; 621 } 622 } 623 624 /** 625 * Handles key events on the search and voice search buttons, 626 * by refocusing to EditText. 627 */ 628 private class ButtonsKeyListener implements View.OnKeyListener { 629 public boolean onKey(View v, int keyCode, KeyEvent event) { 630 return forwardKeyToQueryTextView(keyCode, event); 631 } 632 } 633 634 /** 635 * Handles key events on the suggestions list view. 636 */ 637 private class SuggestionsViewKeyListener implements View.OnKeyListener { 638 public boolean onKey(View v, int keyCode, KeyEvent event) { 639 if (event.getAction() == KeyEvent.ACTION_DOWN) { 640 int position = getSelectedPosition(); 641 if (onSuggestionKeyDown(position, keyCode, event)) { 642 return true; 643 } 644 } 645 return forwardKeyToQueryTextView(keyCode, event); 646 } 647 } 648 649 private class InputMethodCloser implements SuggestionsView.InteractionListener { 650 public void onInteraction() { 651 hideInputMethod(); 652 } 653 } 654 655 private class ClickHandler implements SuggestionClickListener { 656 public void onSuggestionClicked(int position) { 657 launchSuggestion(position); 658 } 659 660 public boolean onSuggestionLongClicked(int position) { 661 return SearchActivity.this.onSuggestionLongClicked(position); 662 } 663 } 664 665 private class SelectionHandler implements SuggestionSelectionListener { 666 public void onSuggestionSelected(int position) { 667 SuggestionCursor suggestions = getSuggestions(); 668 suggestions.moveTo(position); 669 String displayQuery = suggestions.getSuggestionDisplayQuery(); 670 if (TextUtils.isEmpty(displayQuery)) { 671 restoreUserQuery(); 672 } else { 673 setQuery(displayQuery, false); 674 } 675 } 676 677 public void onNothingSelected() { 678 // This happens when a suggestion has been selected with the 679 // dpad / trackball and then a different UI element is touched. 680 // Do nothing, since we want to keep the query of the selection 681 // in the search box. 682 } 683 } 684 685 /** 686 * Listens for clicks on the source selector. 687 */ 688 private class SearchGoButtonClickListener implements View.OnClickListener { 689 public void onClick(View view) { 690 onSearchClicked(Logger.SEARCH_METHOD_BUTTON); 691 } 692 } 693 694 /** 695 * Listens for clicks on the search button. 696 */ 697 private class CorpusIndicatorClickListener implements View.OnClickListener { 698 public void onClick(View view) { 699 showSourceSelectorDialog(); 700 } 701 } 702 703 /** 704 * Listens for clicks on the voice search button. 705 */ 706 private class VoiceSearchButtonClickListener implements View.OnClickListener { 707 public void onClick(View view) { 708 onVoiceSearchClicked(); 709 } 710 } 711 712 /** 713 * Updates the progress bar when the suggestions adapter changes its progress. 714 */ 715 private class ProgressUpdater extends DataSetObserver { 716 private final Suggestions mSuggestions; 717 718 public ProgressUpdater(Suggestions suggestions) { 719 mSuggestions = suggestions; 720 } 721 722 @Override 723 public void onChanged() { 724 if (mSuggestions.isDone()) { 725 stopSearchProgress(); 726 } 727 } 728 } 729 730 private static String ltrim(String text) { 731 int start = 0; 732 int length = text.length(); 733 while (start < length && Character.isWhitespace(text.charAt(start))) { 734 start++; 735 } 736 return start > 0 ? text.substring(start, length) : text; 737 } 738 739} 740