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