SearchBar.java revision e2bc82a1c02f667901bd96e244afa7dc0232b354
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(); 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 && null != mSearchBarListener) { 240 if (DEBUG) Log.v(TAG, "Action Pressed"); 241 hideNativeKeyboard(); 242 mHandler.postDelayed(new Runnable() { 243 @Override 244 public void run() { 245 if (DEBUG) Log.v(TAG, "Delayed action handling (search)"); 246 submitQuery(); 247 } 248 }, 500); 249 250 } else if (EditorInfo.IME_ACTION_NONE == action && null != mSearchBarListener) { 251 if (DEBUG) Log.v(TAG, "Escaped North"); 252 hideNativeKeyboard(); 253 mHandler.postDelayed(new Runnable() { 254 @Override 255 public void run() { 256 if (DEBUG) Log.v(TAG, "Delayed action handling (escape_north)"); 257 mSearchBarListener.onKeyboardDismiss(mSearchQuery); 258 } 259 }, 500); 260 } else if (EditorInfo.IME_ACTION_GO == action) { 261 if (DEBUG) Log.v(TAG, "Voice Clicked"); 262 hideNativeKeyboard(); 263 mHandler.postDelayed(new Runnable() { 264 @Override 265 public void run() { 266 if (DEBUG) Log.v(TAG, "Delayed action handling (voice_mode)"); 267 mAutoStartRecognition = true; 268 mSpeechOrbView.requestFocus(); 269 } 270 }, 500); 271 } else { 272 handled = false; 273 } 274 275 return handled; 276 } 277 }); 278 279 mSearchTextEditor.setPrivateImeOptions("EscapeNorth=1;VoiceDismiss=1;"); 280 281 mSpeechOrbView = (SpeechOrbView)findViewById(R.id.lb_search_bar_speech_orb); 282 mSpeechOrbView.setOnOrbClickedListener(new OnClickListener() { 283 @Override 284 public void onClick(View view) { 285 toggleRecognition(); 286 } 287 }); 288 mSpeechOrbView.setOnFocusChangeListener(new OnFocusChangeListener() { 289 @Override 290 public void onFocusChange(View view, boolean hasFocus) { 291 if (DEBUG) Log.v(TAG, "SpeechOrb.onFocusChange " + hasFocus); 292 if (hasFocus) { 293 hideNativeKeyboard(); 294 if (mAutoStartRecognition) { 295 startRecognition(); 296 mAutoStartRecognition = false; 297 } 298 } else { 299 stopRecognition(); 300 } 301 updateUi(); 302 } 303 }); 304 305 updateUi(); 306 updateHint(); 307 } 308 309 @Override 310 protected void onAttachedToWindow() { 311 super.onAttachedToWindow(); 312 if (DEBUG) Log.v(TAG, "Loading soundPool"); 313 mSoundPool = new SoundPool(2, AudioManager.STREAM_SYSTEM, 0); 314 loadSounds(mContext); 315 } 316 317 @Override 318 protected void onDetachedFromWindow() { 319 if (DEBUG) Log.v(TAG, "Releasing SoundPool"); 320 mSoundPool.release(); 321 super.onDetachedFromWindow(); 322 } 323 324 /** 325 * Set a listener for when the term search changes 326 * @param listener 327 */ 328 public void setSearchBarListener(SearchBarListener listener) { 329 mSearchBarListener = listener; 330 } 331 332 /** 333 * Set the search query 334 * @param query the search query to use 335 */ 336 public void setSearchQuery(String query) { 337 stopRecognition(); 338 mSearchTextEditor.setText(query); 339 setSearchQueryInternal(query); 340 } 341 342 private void setSearchQueryInternal(String query) { 343 if (DEBUG) Log.v(TAG, "setSearchQueryInternal " + query); 344 if (TextUtils.equals(mSearchQuery, query)) { 345 return; 346 } 347 mSearchQuery = query; 348 349 if (null != mSearchBarListener) { 350 mSearchBarListener.onSearchQueryChange(mSearchQuery); 351 } 352 } 353 354 /** 355 * Set the title text used in the hint shown in the search bar. 356 * @param title The hint to use. 357 */ 358 public void setTitle(String title) { 359 mTitle = title; 360 updateHint(); 361 } 362 363 /** 364 * Returns the current title 365 */ 366 public String getTitle() { 367 return mTitle; 368 } 369 370 /** 371 * Returns the current search bar hint text. 372 */ 373 public CharSequence getHint() { 374 return (mSearchTextEditor == null) ? null : mSearchTextEditor.getHint(); 375 } 376 377 /** 378 * Set the badge drawable showing inside the search bar. 379 * @param drawable The drawable to be used in the search bar. 380 */ 381 public void setBadgeDrawable(Drawable drawable) { 382 mBadgeDrawable = drawable; 383 if (null != mBadgeView) { 384 mBadgeView.setImageDrawable(drawable); 385 if (null != drawable) { 386 mBadgeView.setVisibility(View.VISIBLE); 387 } else { 388 mBadgeView.setVisibility(View.GONE); 389 } 390 } 391 } 392 393 /** 394 * Returns the badge drawable 395 */ 396 public Drawable getBadgeDrawable() { 397 return mBadgeDrawable; 398 } 399 400 /** 401 * Update the completion list shown by the IME 402 * 403 * @param completions list of completions shown in the IME, can be null or empty to clear them 404 */ 405 public void displayCompletions(List<String> completions) { 406 List<CompletionInfo> infos = new ArrayList<CompletionInfo>(); 407 if (null != completions) { 408 for (String completion : completions) { 409 infos.add(new CompletionInfo(infos.size(), infos.size(), completion)); 410 } 411 } 412 413 mInputMethodManager.displayCompletions(mSearchTextEditor, 414 infos.toArray(new CompletionInfo[] {})); 415 } 416 417 /** 418 * Set the speech recognizer to be used when doing voice search. The Activity/Fragment is in 419 * charge of creating and destroying the recognizer with its own lifecycle. 420 * 421 * @param recognizer a SpeechRecognizer 422 */ 423 public void setSpeechRecognizer(SpeechRecognizer recognizer) { 424 if (null != mSpeechRecognizer) { 425 mSpeechRecognizer.setRecognitionListener(null); 426 if (mListening) { 427 mSpeechRecognizer.cancel(); 428 mListening = false; 429 } 430 } 431 mSpeechRecognizer = recognizer; 432 if (mSpeechRecognizer != null) { 433 enforceAudioRecordPermission(); 434 } 435 if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) { 436 throw new IllegalStateException("Can't have speech recognizer and request"); 437 } 438 } 439 440 public void setSpeechRecognitionCallback(SpeechRecognitionCallback request) { 441 mSpeechRecognitionCallback = request; 442 if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) { 443 throw new IllegalStateException("Can't have speech recognizer and request"); 444 } 445 } 446 447 private void hideNativeKeyboard() { 448 mInputMethodManager.hideSoftInputFromWindow(mSearchTextEditor.getWindowToken(), 449 InputMethodManager.RESULT_UNCHANGED_SHOWN); 450 } 451 452 private void showNativeKeyboard() { 453 mHandler.post(new Runnable() { 454 @Override 455 public void run() { 456 mSearchTextEditor.requestFocusFromTouch(); 457 mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), 458 SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 459 mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0)); 460 mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), 461 SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 462 mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0)); 463 } 464 }); 465 } 466 467 /** 468 * This will update the hint for the search bar properly depending on state and provided title 469 */ 470 private void updateHint() { 471 if (null == mSearchTextEditor) return; 472 473 String title = getResources().getString(R.string.lb_search_bar_hint); 474 if (!TextUtils.isEmpty(mTitle)) { 475 if (isVoiceMode()) { 476 title = getResources().getString(R.string.lb_search_bar_hint_with_title_speech, mTitle); 477 } else { 478 title = getResources().getString(R.string.lb_search_bar_hint_with_title, mTitle); 479 } 480 } else if (isVoiceMode()) { 481 title = getResources().getString(R.string.lb_search_bar_hint_speech); 482 } 483 mSearchTextEditor.setHint(title); 484 } 485 486 private void toggleRecognition() { 487 if (mRecognizing) { 488 stopRecognition(); 489 } else { 490 startRecognition(); 491 } 492 } 493 494 /** 495 * Stop the recognition if already started 496 */ 497 public void stopRecognition() { 498 if (DEBUG) Log.v(TAG, String.format("stopRecognition (listening: %s, recognizing: %s)", 499 mListening, mRecognizing)); 500 501 if (!mRecognizing) return; 502 mRecognizing = false; 503 504 if (mSpeechRecognitionCallback != null || null == mSpeechRecognizer) return; 505 506 mSpeechOrbView.showNotListening(); 507 508 if (mListening) { 509 mSpeechRecognizer.cancel(); 510 mListening = false; 511 mAudioManager.abandonAudioFocus(mAudioFocusChangeListener); 512 } 513 514 mSpeechRecognizer.setRecognitionListener(null); 515 } 516 517 /** 518 * Start the voice recognition 519 */ 520 public void startRecognition() { 521 if (DEBUG) Log.v(TAG, String.format("startRecognition (listening: %s, recognizing: %s)", 522 mListening, mRecognizing)); 523 524 if (mRecognizing) return; 525 mRecognizing = true; 526 if (!hasFocus()) { 527 requestFocus(); 528 } 529 if (mSpeechRecognitionCallback != null) { 530 mSearchTextEditor.setText(""); 531 mSpeechRecognitionCallback.recognizeSpeech(); 532 return; 533 } 534 if (null == mSpeechRecognizer) return; 535 536 // Request audio focus 537 int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener, 538 // Use the music stream. 539 AudioManager.STREAM_MUSIC, 540 // Request exclusive transient focus. 541 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); 542 543 544 if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 545 Log.w(TAG, "Could not get audio focus"); 546 } 547 548 mSearchTextEditor.setText(""); 549 550 Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 551 552 recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, 553 RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); 554 recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); 555 556 mSpeechRecognizer.setRecognitionListener(new RecognitionListener() { 557 @Override 558 public void onReadyForSpeech(Bundle bundle) { 559 if (DEBUG) Log.v(TAG, "onReadyForSpeech"); 560 } 561 562 @Override 563 public void onBeginningOfSpeech() { 564 if (DEBUG) Log.v(TAG, "onBeginningOfSpeech"); 565 } 566 567 @Override 568 public void onRmsChanged(float rmsdB) { 569 if (DEBUG) Log.v(TAG, "onRmsChanged " + rmsdB); 570 int level = rmsdB < 0 ? 0 : (int)(10 * rmsdB); 571 mSpeechOrbView.setSoundLevel(level); 572 } 573 574 @Override 575 public void onBufferReceived(byte[] bytes) { 576 if (DEBUG) Log.v(TAG, "onBufferReceived " + bytes.length); 577 } 578 579 @Override 580 public void onEndOfSpeech() { 581 if (DEBUG) Log.v(TAG, "onEndOfSpeech"); 582 } 583 584 @Override 585 public void onError(int error) { 586 if (DEBUG) Log.v(TAG, "onError " + error); 587 switch (error) { 588 case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: 589 Log.w(TAG, "recognizer network timeout"); 590 break; 591 case SpeechRecognizer.ERROR_NETWORK: 592 Log.w(TAG, "recognizer network error"); 593 break; 594 case SpeechRecognizer.ERROR_AUDIO: 595 Log.w(TAG, "recognizer audio error"); 596 break; 597 case SpeechRecognizer.ERROR_SERVER: 598 Log.w(TAG, "recognizer server error"); 599 break; 600 case SpeechRecognizer.ERROR_CLIENT: 601 Log.w(TAG, "recognizer client error"); 602 break; 603 case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: 604 Log.w(TAG, "recognizer speech timeout"); 605 break; 606 case SpeechRecognizer.ERROR_NO_MATCH: 607 Log.w(TAG, "recognizer no match"); 608 break; 609 case SpeechRecognizer.ERROR_RECOGNIZER_BUSY: 610 Log.w(TAG, "recognizer busy"); 611 break; 612 case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS: 613 Log.w(TAG, "recognizer insufficient permissions"); 614 break; 615 default: 616 Log.d(TAG, "recognizer other error"); 617 break; 618 } 619 620 stopRecognition(); 621 playSearchFailure(); 622 } 623 624 @Override 625 public void onResults(Bundle bundle) { 626 if (DEBUG) Log.v(TAG, "onResults"); 627 final ArrayList<String> matches = 628 bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); 629 if (matches != null) { 630 if (DEBUG) Log.v(TAG, "Got results" + matches); 631 632 mSearchQuery = matches.get(0); 633 mSearchTextEditor.setText(mSearchQuery); 634 submitQuery(); 635 } 636 637 stopRecognition(); 638 playSearchSuccess(); 639 } 640 641 @Override 642 public void onPartialResults(Bundle bundle) { 643 ArrayList<String> results = bundle.getStringArrayList( 644 SpeechRecognizer.RESULTS_RECOGNITION); 645 if (DEBUG) Log.v(TAG, "onPartialResults " + bundle + " results " + 646 (results == null ? results : results.size())); 647 if (results == null || results.size() == 0) { 648 return; 649 } 650 651 // stableText: high confidence text from PartialResults, if any. 652 // Otherwise, existing stable text. 653 final String stableText = results.get(0); 654 if (DEBUG) Log.v(TAG, "onPartialResults stableText " + stableText); 655 656 // pendingText: low confidence text from PartialResults, if any. 657 // Otherwise, empty string. 658 final String pendingText = results.size() > 1 ? results.get(1) : null; 659 if (DEBUG) Log.v(TAG, "onPartialResults pendingText " + pendingText); 660 661 mSearchTextEditor.updateRecognizedText(stableText, pendingText); 662 } 663 664 @Override 665 public void onEvent(int i, Bundle bundle) { 666 667 } 668 }); 669 670 mListening = true; 671 mSpeechOrbView.showListening(); 672 playSearchOpen(); 673 mSpeechRecognizer.startListening(recognizerIntent); 674 } 675 676 private void updateUi() { 677 if (DEBUG) Log.v(TAG, String.format("Update UI %s %s", 678 isVoiceMode() ? "Voice" : "Text", 679 hasFocus() ? "Focused" : "Unfocused")); 680 if (isVoiceMode()) { 681 mBarBackground.setAlpha(mBackgroundSpeechAlpha); 682 mSearchTextEditor.setTextColor(mTextColorSpeechMode); 683 mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode); 684 } else { 685 mBarBackground.setAlpha(mBackgroundAlpha); 686 mSearchTextEditor.setTextColor(mTextColor); 687 mSearchTextEditor.setHintTextColor(mTextHintColor); 688 } 689 690 updateHint(); 691 } 692 693 private boolean isVoiceMode() { 694 return mSpeechOrbView.isFocused(); 695 } 696 697 private void submitQuery() { 698 if (!TextUtils.isEmpty(mSearchQuery) && null != mSearchBarListener) { 699 mSearchBarListener.onSearchQuerySubmit(mSearchQuery); 700 } 701 } 702 703 private void enforceAudioRecordPermission() { 704 String permission = "android.permission.RECORD_AUDIO"; 705 int res = getContext().checkCallingOrSelfPermission(permission); 706 if (PackageManager.PERMISSION_GRANTED != res) { 707 throw new IllegalStateException("android.permission.RECORD_AUDIO required for search"); 708 } 709 } 710 711 private void loadSounds(Context context) { 712 int[] sounds = { 713 R.raw.lb_voice_failure, 714 R.raw.lb_voice_open, 715 R.raw.lb_voice_no_input, 716 R.raw.lb_voice_success, 717 }; 718 for (int sound : sounds) { 719 mSoundMap.put(sound, mSoundPool.load(context, sound, 1)); 720 } 721 } 722 723 private void play(final int resId) { 724 mHandler.post(new Runnable() { 725 @Override 726 public void run() { 727 int sound = mSoundMap.get(resId); 728 mSoundPool.play(sound, FULL_LEFT_VOLUME, FULL_RIGHT_VOLUME, DEFAULT_PRIORITY, 729 DO_NOT_LOOP, DEFAULT_RATE); 730 } 731 }); 732 } 733 734 private void playSearchOpen() { 735 play(R.raw.lb_voice_open); 736 } 737 738 private void playSearchFailure() { 739 play(R.raw.lb_voice_failure); 740 } 741 742 private void playSearchNoInput() { 743 play(R.raw.lb_voice_no_input); 744 } 745 746 private void playSearchSuccess() { 747 play(R.raw.lb_voice_success); 748 } 749 750 @Override 751 public void setNextFocusDownId(int viewId) { 752 mSpeechOrbView.setNextFocusDownId(viewId); 753 mSearchTextEditor.setNextFocusDownId(viewId); 754 } 755 756} 757