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