1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14package android.support.v17.leanback.widget; 15 16import android.Manifest; 17import android.content.Context; 18import android.content.Intent; 19import android.content.pm.PackageManager; 20import android.content.res.Resources; 21import android.graphics.Color; 22import android.graphics.drawable.Drawable; 23import android.media.AudioManager; 24import android.media.SoundPool; 25import android.os.Build; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.SystemClock; 29import android.speech.RecognitionListener; 30import android.speech.RecognizerIntent; 31import android.speech.SpeechRecognizer; 32import android.text.Editable; 33import android.text.TextUtils; 34import android.text.TextWatcher; 35import android.util.AttributeSet; 36import android.util.Log; 37import android.util.SparseIntArray; 38import android.view.LayoutInflater; 39import android.view.ViewGroup; 40import android.view.inputmethod.CompletionInfo; 41import android.view.inputmethod.EditorInfo; 42import android.view.KeyEvent; 43import android.view.MotionEvent; 44import android.view.View; 45import android.widget.ImageView; 46import android.view.inputmethod.InputMethodManager; 47import android.widget.RelativeLayout; 48import android.widget.TextView; 49 50import android.support.v17.leanback.R; 51 52import java.util.ArrayList; 53import java.util.List; 54 55/** 56 * A search widget containing a search orb and a text entry view. 57 * 58 * <p> 59 * Note: When {@link SpeechRecognitionCallback} is not used, i.e. using {@link SpeechRecognizer}, 60 * your application will need to declare android.permission.RECORD_AUDIO in manifest file. 61 * If your application target >= 23 and the device is running >= 23, it needs implement 62 * {@link SearchBarPermissionListener} where requests runtime permission. 63 * </p> 64 */ 65public class SearchBar extends RelativeLayout { 66 private static final String TAG = SearchBar.class.getSimpleName(); 67 private static final boolean DEBUG = false; 68 69 private static final float FULL_LEFT_VOLUME = 1.0f; 70 private static final float FULL_RIGHT_VOLUME = 1.0f; 71 private static final int DEFAULT_PRIORITY = 1; 72 private static final int DO_NOT_LOOP = 0; 73 private static final float DEFAULT_RATE = 1.0f; 74 75 /** 76 * Interface for receiving notification of search query changes. 77 */ 78 public interface SearchBarListener { 79 80 /** 81 * Method invoked when the search bar detects a change in the query. 82 * 83 * @param query The current full query. 84 */ 85 public void onSearchQueryChange(String query); 86 87 /** 88 * <p>Method invoked when the search query is submitted.</p> 89 * 90 * <p>This method can be called without a preceeding onSearchQueryChange, 91 * in particular in the case of a voice input.</p> 92 * 93 * @param query The query being submitted. 94 */ 95 public void onSearchQuerySubmit(String query); 96 97 /** 98 * Method invoked when the IME is being dismissed. 99 * 100 * @param query The query set in the search bar at the time the IME is being dismissed. 101 */ 102 public void onKeyboardDismiss(String query); 103 104 } 105 106 /** 107 * Interface that handles runtime permissions requests. App sets listener on SearchBar via 108 * {@link #setPermissionListener(SearchBarPermissionListener)}. 109 */ 110 public interface SearchBarPermissionListener { 111 112 /** 113 * Method invoked when SearchBar asks for "android.permission.RECORD_AUDIO" runtime 114 * permission. 115 */ 116 void requestAudioPermission(); 117 118 } 119 120 private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener = 121 new AudioManager.OnAudioFocusChangeListener() { 122 @Override 123 public void onAudioFocusChange(int focusChange) { 124 stopRecognition(); 125 } 126 }; 127 128 private SearchBarListener mSearchBarListener; 129 private SearchEditText mSearchTextEditor; 130 private SpeechOrbView mSpeechOrbView; 131 private ImageView mBadgeView; 132 private String mSearchQuery; 133 private String mHint; 134 private String mTitle; 135 private Drawable mBadgeDrawable; 136 private final Handler mHandler = new Handler(); 137 private final InputMethodManager mInputMethodManager; 138 private boolean mAutoStartRecognition = false; 139 private Drawable mBarBackground; 140 141 private final int mTextColor; 142 private final int mTextColorSpeechMode; 143 private final int mTextHintColor; 144 private final int mTextHintColorSpeechMode; 145 private int mBackgroundAlpha; 146 private int mBackgroundSpeechAlpha; 147 private int mBarHeight; 148 private SpeechRecognizer mSpeechRecognizer; 149 private SpeechRecognitionCallback mSpeechRecognitionCallback; 150 private boolean mListening; 151 private SoundPool mSoundPool; 152 private SparseIntArray mSoundMap = new SparseIntArray(); 153 private boolean mRecognizing = false; 154 private final Context mContext; 155 private AudioManager mAudioManager; 156 private SearchBarPermissionListener mPermissionListener; 157 158 public SearchBar(Context context) { 159 this(context, null); 160 } 161 162 public SearchBar(Context context, AttributeSet attrs) { 163 this(context, attrs, 0); 164 } 165 166 public SearchBar(Context context, AttributeSet attrs, int defStyle) { 167 super(context, attrs, defStyle); 168 mContext = context; 169 170 Resources r = getResources(); 171 172 LayoutInflater inflater = LayoutInflater.from(getContext()); 173 inflater.inflate(R.layout.lb_search_bar, this, true); 174 175 mBarHeight = getResources().getDimensionPixelSize(R.dimen.lb_search_bar_height); 176 RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 177 mBarHeight); 178 params.addRule(ALIGN_PARENT_TOP, RelativeLayout.TRUE); 179 setLayoutParams(params); 180 setBackgroundColor(Color.TRANSPARENT); 181 setClipChildren(false); 182 183 mSearchQuery = ""; 184 mInputMethodManager = 185 (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); 186 187 mTextColorSpeechMode = r.getColor(R.color.lb_search_bar_text_speech_mode); 188 mTextColor = r.getColor(R.color.lb_search_bar_text); 189 190 mBackgroundSpeechAlpha = r.getInteger(R.integer.lb_search_bar_speech_mode_background_alpha); 191 mBackgroundAlpha = r.getInteger(R.integer.lb_search_bar_text_mode_background_alpha); 192 193 mTextHintColorSpeechMode = r.getColor(R.color.lb_search_bar_hint_speech_mode); 194 mTextHintColor = r.getColor(R.color.lb_search_bar_hint); 195 196 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 197 } 198 199 @Override 200 protected void onFinishInflate() { 201 super.onFinishInflate(); 202 203 RelativeLayout items = (RelativeLayout)findViewById(R.id.lb_search_bar_items); 204 mBarBackground = items.getBackground(); 205 206 mSearchTextEditor = (SearchEditText)findViewById(R.id.lb_search_text_editor); 207 mBadgeView = (ImageView)findViewById(R.id.lb_search_bar_badge); 208 if (null != mBadgeDrawable) { 209 mBadgeView.setImageDrawable(mBadgeDrawable); 210 } 211 212 mSearchTextEditor.setOnFocusChangeListener(new OnFocusChangeListener() { 213 @Override 214 public void onFocusChange(View view, boolean hasFocus) { 215 if (DEBUG) Log.v(TAG, "EditText.onFocusChange " + hasFocus); 216 if (hasFocus) { 217 showNativeKeyboard(); 218 } 219 updateUi(hasFocus); 220 } 221 }); 222 final Runnable mOnTextChangedRunnable = new Runnable() { 223 @Override 224 public void run() { 225 setSearchQueryInternal(mSearchTextEditor.getText().toString()); 226 } 227 }; 228 mSearchTextEditor.addTextChangedListener(new TextWatcher() { 229 @Override 230 public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { 231 } 232 233 @Override 234 public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { 235 // don't propagate event during speech recognition. 236 if (mRecognizing) { 237 return; 238 } 239 // while IME opens, text editor becomes "" then restores to current value 240 mHandler.removeCallbacks(mOnTextChangedRunnable); 241 mHandler.post(mOnTextChangedRunnable); 242 } 243 244 @Override 245 public void afterTextChanged(Editable editable) { 246 247 } 248 }); 249 mSearchTextEditor.setOnKeyboardDismissListener( 250 new SearchEditText.OnKeyboardDismissListener() { 251 @Override 252 public void onKeyboardDismiss() { 253 if (null != mSearchBarListener) { 254 mSearchBarListener.onKeyboardDismiss(mSearchQuery); 255 } 256 } 257 }); 258 259 mSearchTextEditor.setOnEditorActionListener(new TextView.OnEditorActionListener() { 260 @Override 261 public boolean onEditorAction(TextView textView, int action, KeyEvent keyEvent) { 262 if (DEBUG) Log.v(TAG, "onEditorAction: " + action + " event: " + keyEvent); 263 boolean handled = true; 264 if ((EditorInfo.IME_ACTION_SEARCH == action || 265 EditorInfo.IME_NULL == action) && null != mSearchBarListener) { 266 if (DEBUG) Log.v(TAG, "Action or enter pressed"); 267 hideNativeKeyboard(); 268 mHandler.postDelayed(new Runnable() { 269 @Override 270 public void run() { 271 if (DEBUG) Log.v(TAG, "Delayed action handling (search)"); 272 submitQuery(); 273 } 274 }, 500); 275 276 } else if (EditorInfo.IME_ACTION_NONE == action && null != mSearchBarListener) { 277 if (DEBUG) Log.v(TAG, "Escaped North"); 278 hideNativeKeyboard(); 279 mHandler.postDelayed(new Runnable() { 280 @Override 281 public void run() { 282 if (DEBUG) Log.v(TAG, "Delayed action handling (escape_north)"); 283 mSearchBarListener.onKeyboardDismiss(mSearchQuery); 284 } 285 }, 500); 286 } else if (EditorInfo.IME_ACTION_GO == action) { 287 if (DEBUG) Log.v(TAG, "Voice Clicked"); 288 hideNativeKeyboard(); 289 mHandler.postDelayed(new Runnable() { 290 @Override 291 public void run() { 292 if (DEBUG) Log.v(TAG, "Delayed action handling (voice_mode)"); 293 mAutoStartRecognition = true; 294 mSpeechOrbView.requestFocus(); 295 } 296 }, 500); 297 } else { 298 handled = false; 299 } 300 301 return handled; 302 } 303 }); 304 305 mSearchTextEditor.setPrivateImeOptions("EscapeNorth=1;VoiceDismiss=1;"); 306 307 mSpeechOrbView = (SpeechOrbView)findViewById(R.id.lb_search_bar_speech_orb); 308 mSpeechOrbView.setOnOrbClickedListener(new OnClickListener() { 309 @Override 310 public void onClick(View view) { 311 toggleRecognition(); 312 } 313 }); 314 mSpeechOrbView.setOnFocusChangeListener(new OnFocusChangeListener() { 315 @Override 316 public void onFocusChange(View view, boolean hasFocus) { 317 if (DEBUG) Log.v(TAG, "SpeechOrb.onFocusChange " + hasFocus); 318 if (hasFocus) { 319 hideNativeKeyboard(); 320 if (mAutoStartRecognition) { 321 startRecognition(); 322 mAutoStartRecognition = false; 323 } 324 } else { 325 stopRecognition(); 326 } 327 updateUi(hasFocus); 328 } 329 }); 330 331 updateUi(hasFocus()); 332 updateHint(); 333 } 334 335 @Override 336 protected void onAttachedToWindow() { 337 super.onAttachedToWindow(); 338 if (DEBUG) Log.v(TAG, "Loading soundPool"); 339 mSoundPool = new SoundPool(2, AudioManager.STREAM_SYSTEM, 0); 340 loadSounds(mContext); 341 } 342 343 @Override 344 protected void onDetachedFromWindow() { 345 stopRecognition(); 346 if (DEBUG) Log.v(TAG, "Releasing SoundPool"); 347 mSoundPool.release(); 348 super.onDetachedFromWindow(); 349 } 350 351 /** 352 * Sets a listener for when the term search changes 353 * @param listener 354 */ 355 public void setSearchBarListener(SearchBarListener listener) { 356 mSearchBarListener = listener; 357 } 358 359 /** 360 * Sets the search query 361 * @param query the search query to use 362 */ 363 public void setSearchQuery(String query) { 364 stopRecognition(); 365 mSearchTextEditor.setText(query); 366 setSearchQueryInternal(query); 367 } 368 369 private void setSearchQueryInternal(String query) { 370 if (DEBUG) Log.v(TAG, "setSearchQueryInternal " + query); 371 if (TextUtils.equals(mSearchQuery, query)) { 372 return; 373 } 374 mSearchQuery = query; 375 376 if (null != mSearchBarListener) { 377 mSearchBarListener.onSearchQueryChange(mSearchQuery); 378 } 379 } 380 381 /** 382 * Sets the title text used in the hint shown in the search bar. 383 * @param title The hint to use. 384 */ 385 public void setTitle(String title) { 386 mTitle = title; 387 updateHint(); 388 } 389 390 /** 391 * Returns the current title 392 */ 393 public String getTitle() { 394 return mTitle; 395 } 396 397 /** 398 * Returns the current search bar hint text. 399 */ 400 public CharSequence getHint() { 401 return mHint; 402 } 403 404 /** 405 * Sets the badge drawable showing inside the search bar. 406 * @param drawable The drawable to be used in the search bar. 407 */ 408 public void setBadgeDrawable(Drawable drawable) { 409 mBadgeDrawable = drawable; 410 if (null != mBadgeView) { 411 mBadgeView.setImageDrawable(drawable); 412 if (null != drawable) { 413 mBadgeView.setVisibility(View.VISIBLE); 414 } else { 415 mBadgeView.setVisibility(View.GONE); 416 } 417 } 418 } 419 420 /** 421 * Returns the badge drawable 422 */ 423 public Drawable getBadgeDrawable() { 424 return mBadgeDrawable; 425 } 426 427 /** 428 * Updates the completion list shown by the IME 429 * 430 * @param completions list of completions shown in the IME, can be null or empty to clear them 431 */ 432 public void displayCompletions(List<String> completions) { 433 List<CompletionInfo> infos = new ArrayList<>(); 434 if (null != completions) { 435 for (String completion : completions) { 436 infos.add(new CompletionInfo(infos.size(), infos.size(), completion)); 437 } 438 } 439 CompletionInfo[] array = new CompletionInfo[infos.size()]; 440 displayCompletions(infos.toArray(array)); 441 } 442 443 /** 444 * Updates the completion list shown by the IME 445 * 446 * @param completions list of completions shown in the IME, can be null or empty to clear them 447 */ 448 public void displayCompletions(CompletionInfo[] completions) { 449 mInputMethodManager.displayCompletions(mSearchTextEditor, completions); 450 } 451 452 /** 453 * Sets the speech recognizer to be used when doing voice search. The Activity/Fragment is in 454 * charge of creating and destroying the recognizer with its own lifecycle. 455 * 456 * @param recognizer a SpeechRecognizer 457 */ 458 public void setSpeechRecognizer(SpeechRecognizer recognizer) { 459 stopRecognition(); 460 if (null != mSpeechRecognizer) { 461 mSpeechRecognizer.setRecognitionListener(null); 462 if (mListening) { 463 mSpeechRecognizer.cancel(); 464 mListening = false; 465 } 466 } 467 mSpeechRecognizer = recognizer; 468 if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) { 469 throw new IllegalStateException("Can't have speech recognizer and request"); 470 } 471 } 472 473 /** 474 * Sets the speech recognition callback. 475 */ 476 public void setSpeechRecognitionCallback(SpeechRecognitionCallback request) { 477 mSpeechRecognitionCallback = request; 478 if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) { 479 throw new IllegalStateException("Can't have speech recognizer and request"); 480 } 481 } 482 483 private void hideNativeKeyboard() { 484 mInputMethodManager.hideSoftInputFromWindow(mSearchTextEditor.getWindowToken(), 485 InputMethodManager.RESULT_UNCHANGED_SHOWN); 486 } 487 488 private void showNativeKeyboard() { 489 mHandler.post(new Runnable() { 490 @Override 491 public void run() { 492 mSearchTextEditor.requestFocusFromTouch(); 493 mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), 494 SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 495 mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0)); 496 mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), 497 SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 498 mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0)); 499 } 500 }); 501 } 502 503 /** 504 * This will update the hint for the search bar properly depending on state and provided title 505 */ 506 private void updateHint() { 507 String title = getResources().getString(R.string.lb_search_bar_hint); 508 if (!TextUtils.isEmpty(mTitle)) { 509 if (isVoiceMode()) { 510 title = getResources().getString(R.string.lb_search_bar_hint_with_title_speech, mTitle); 511 } else { 512 title = getResources().getString(R.string.lb_search_bar_hint_with_title, mTitle); 513 } 514 } else if (isVoiceMode()) { 515 title = getResources().getString(R.string.lb_search_bar_hint_speech); 516 } 517 mHint = title; 518 if (mSearchTextEditor != null) { 519 mSearchTextEditor.setHint(mHint); 520 } 521 } 522 523 private void toggleRecognition() { 524 if (mRecognizing) { 525 stopRecognition(); 526 } else { 527 startRecognition(); 528 } 529 } 530 531 /** 532 * Returns true if is not running Recognizer, false otherwise. 533 * @return True if is not running Recognizer, false otherwise. 534 */ 535 public boolean isRecognizing() { 536 return mRecognizing; 537 } 538 539 /** 540 * Stops the speech recognition, if already started. 541 */ 542 public void stopRecognition() { 543 if (DEBUG) Log.v(TAG, String.format("stopRecognition (listening: %s, recognizing: %s)", 544 mListening, mRecognizing)); 545 546 if (!mRecognizing) return; 547 548 // Edit text content was cleared when starting recogition; ensure the content is restored 549 // in error cases 550 mSearchTextEditor.setText(mSearchQuery); 551 mSearchTextEditor.setHint(mHint); 552 553 mRecognizing = false; 554 555 if (mSpeechRecognitionCallback != null || null == mSpeechRecognizer) return; 556 557 mSpeechOrbView.showNotListening(); 558 559 if (mListening) { 560 mSpeechRecognizer.cancel(); 561 mListening = false; 562 mAudioManager.abandonAudioFocus(mAudioFocusChangeListener); 563 } 564 565 mSpeechRecognizer.setRecognitionListener(null); 566 } 567 568 /** 569 * Sets listener that handles runtime permission requests. 570 * @param listener Listener that handles runtime permission requests. 571 */ 572 public void setPermissionListener(SearchBarPermissionListener listener) { 573 mPermissionListener = listener; 574 } 575 576 public void startRecognition() { 577 if (DEBUG) Log.v(TAG, String.format("startRecognition (listening: %s, recognizing: %s)", 578 mListening, mRecognizing)); 579 580 if (mRecognizing) return; 581 if (!hasFocus()) { 582 requestFocus(); 583 } 584 if (mSpeechRecognitionCallback != null) { 585 mSearchTextEditor.setText(""); 586 mSearchTextEditor.setHint(""); 587 mSpeechRecognitionCallback.recognizeSpeech(); 588 mRecognizing = true; 589 return; 590 } 591 if (null == mSpeechRecognizer) return; 592 int res = getContext().checkCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO); 593 if (PackageManager.PERMISSION_GRANTED != res) { 594 if (Build.VERSION.SDK_INT >= 23 && mPermissionListener != null) { 595 mPermissionListener.requestAudioPermission(); 596 return; 597 } else { 598 throw new IllegalStateException(Manifest.permission.RECORD_AUDIO + 599 " required for search"); 600 } 601 } 602 603 mRecognizing = true; 604 // Request audio focus 605 int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener, 606 // Use the music stream. 607 AudioManager.STREAM_MUSIC, 608 // Request exclusive transient focus. 609 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); 610 611 612 if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 613 Log.w(TAG, "Could not get audio focus"); 614 } 615 616 mSearchTextEditor.setText(""); 617 618 Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 619 620 recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, 621 RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); 622 recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); 623 624 mSpeechRecognizer.setRecognitionListener(new RecognitionListener() { 625 @Override 626 public void onReadyForSpeech(Bundle bundle) { 627 if (DEBUG) Log.v(TAG, "onReadyForSpeech"); 628 mSpeechOrbView.showListening(); 629 playSearchOpen(); 630 } 631 632 @Override 633 public void onBeginningOfSpeech() { 634 if (DEBUG) Log.v(TAG, "onBeginningOfSpeech"); 635 } 636 637 @Override 638 public void onRmsChanged(float rmsdB) { 639 if (DEBUG) Log.v(TAG, "onRmsChanged " + rmsdB); 640 int level = rmsdB < 0 ? 0 : (int)(10 * rmsdB); 641 mSpeechOrbView.setSoundLevel(level); 642 } 643 644 @Override 645 public void onBufferReceived(byte[] bytes) { 646 if (DEBUG) Log.v(TAG, "onBufferReceived " + bytes.length); 647 } 648 649 @Override 650 public void onEndOfSpeech() { 651 if (DEBUG) Log.v(TAG, "onEndOfSpeech"); 652 } 653 654 @Override 655 public void onError(int error) { 656 if (DEBUG) Log.v(TAG, "onError " + error); 657 switch (error) { 658 case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: 659 Log.w(TAG, "recognizer network timeout"); 660 break; 661 case SpeechRecognizer.ERROR_NETWORK: 662 Log.w(TAG, "recognizer network error"); 663 break; 664 case SpeechRecognizer.ERROR_AUDIO: 665 Log.w(TAG, "recognizer audio error"); 666 break; 667 case SpeechRecognizer.ERROR_SERVER: 668 Log.w(TAG, "recognizer server error"); 669 break; 670 case SpeechRecognizer.ERROR_CLIENT: 671 Log.w(TAG, "recognizer client error"); 672 break; 673 case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: 674 Log.w(TAG, "recognizer speech timeout"); 675 break; 676 case SpeechRecognizer.ERROR_NO_MATCH: 677 Log.w(TAG, "recognizer no match"); 678 break; 679 case SpeechRecognizer.ERROR_RECOGNIZER_BUSY: 680 Log.w(TAG, "recognizer busy"); 681 break; 682 case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS: 683 Log.w(TAG, "recognizer insufficient permissions"); 684 break; 685 default: 686 Log.d(TAG, "recognizer other error"); 687 break; 688 } 689 690 stopRecognition(); 691 playSearchFailure(); 692 } 693 694 @Override 695 public void onResults(Bundle bundle) { 696 if (DEBUG) Log.v(TAG, "onResults"); 697 final ArrayList<String> matches = 698 bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); 699 if (matches != null) { 700 if (DEBUG) Log.v(TAG, "Got results" + matches); 701 702 mSearchQuery = matches.get(0); 703 mSearchTextEditor.setText(mSearchQuery); 704 submitQuery(); 705 } 706 707 stopRecognition(); 708 playSearchSuccess(); 709 } 710 711 @Override 712 public void onPartialResults(Bundle bundle) { 713 ArrayList<String> results = bundle.getStringArrayList( 714 SpeechRecognizer.RESULTS_RECOGNITION); 715 if (DEBUG) Log.v(TAG, "onPartialResults " + bundle + " results " + 716 (results == null ? results : results.size())); 717 if (results == null || results.size() == 0) { 718 return; 719 } 720 721 // stableText: high confidence text from PartialResults, if any. 722 // Otherwise, existing stable text. 723 final String stableText = results.get(0); 724 if (DEBUG) Log.v(TAG, "onPartialResults stableText " + stableText); 725 726 // pendingText: low confidence text from PartialResults, if any. 727 // Otherwise, empty string. 728 final String pendingText = results.size() > 1 ? results.get(1) : null; 729 if (DEBUG) Log.v(TAG, "onPartialResults pendingText " + pendingText); 730 731 mSearchTextEditor.updateRecognizedText(stableText, pendingText); 732 } 733 734 @Override 735 public void onEvent(int i, Bundle bundle) { 736 737 } 738 }); 739 740 mListening = true; 741 mSpeechRecognizer.startListening(recognizerIntent); 742 } 743 744 private void updateUi(boolean hasFocus) { 745 if (hasFocus) { 746 mBarBackground.setAlpha(mBackgroundSpeechAlpha); 747 if (isVoiceMode()) { 748 mSearchTextEditor.setTextColor(mTextHintColorSpeechMode); 749 mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode); 750 } else { 751 mSearchTextEditor.setTextColor(mTextColorSpeechMode); 752 mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode); 753 } 754 } else { 755 mBarBackground.setAlpha(mBackgroundAlpha); 756 mSearchTextEditor.setTextColor(mTextColor); 757 mSearchTextEditor.setHintTextColor(mTextHintColor); 758 } 759 760 updateHint(); 761 } 762 763 private boolean isVoiceMode() { 764 return mSpeechOrbView.isFocused(); 765 } 766 767 private void submitQuery() { 768 if (!TextUtils.isEmpty(mSearchQuery) && null != mSearchBarListener) { 769 mSearchBarListener.onSearchQuerySubmit(mSearchQuery); 770 } 771 } 772 773 private void loadSounds(Context context) { 774 int[] sounds = { 775 R.raw.lb_voice_failure, 776 R.raw.lb_voice_open, 777 R.raw.lb_voice_no_input, 778 R.raw.lb_voice_success, 779 }; 780 for (int sound : sounds) { 781 mSoundMap.put(sound, mSoundPool.load(context, sound, 1)); 782 } 783 } 784 785 private void play(final int resId) { 786 mHandler.post(new Runnable() { 787 @Override 788 public void run() { 789 int sound = mSoundMap.get(resId); 790 mSoundPool.play(sound, FULL_LEFT_VOLUME, FULL_RIGHT_VOLUME, DEFAULT_PRIORITY, 791 DO_NOT_LOOP, DEFAULT_RATE); 792 } 793 }); 794 } 795 796 private void playSearchOpen() { 797 play(R.raw.lb_voice_open); 798 } 799 800 private void playSearchFailure() { 801 play(R.raw.lb_voice_failure); 802 } 803 804 private void playSearchNoInput() { 805 play(R.raw.lb_voice_no_input); 806 } 807 808 private void playSearchSuccess() { 809 play(R.raw.lb_voice_success); 810 } 811 812 @Override 813 public void setNextFocusDownId(int viewId) { 814 mSpeechOrbView.setNextFocusDownId(viewId); 815 mSearchTextEditor.setNextFocusDownId(viewId); 816 } 817 818} 819