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