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