1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.inputmethod.leanback.voice; 18 19 import com.android.inputmethod.leanback.LeanbackUtils; 20 import com.android.inputmethod.leanback.R; 21 22 import android.content.Context; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.util.AttributeSet; 26 import android.util.Log; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.accessibility.AccessibilityNodeInfo; 30 import android.widget.ImageView; 31 import android.widget.RelativeLayout; 32 33 /** 34 * Displays the recognizer status. 35 * This mView includes a {@link BitmapSoundLevelsView} to display the recording value. 36 */ 37 public class RecognizerView extends RelativeLayout { 38 private static final boolean DEBUG = false; 39 private static final String TAG = "RecognizerView"; 40 41 private BitmapSoundLevelView mSoundLevels; 42 protected ImageView mMicButton; 43 44 private Callback mCallback; 45 46 private State mState; 47 48 private boolean mEnabled; 49 50 private enum State { 51 NOT_LISTENING, 52 MIC_INITIALIZING, 53 LISTENING, 54 RECORDING, 55 RECOGNIZING, 56 } 57 RecognizerView(Context context, AttributeSet attrs, int defStyle)58 public RecognizerView(Context context, AttributeSet attrs, int defStyle) { 59 super(context, attrs, defStyle); 60 } 61 RecognizerView(Context context, AttributeSet attrs)62 public RecognizerView(Context context, AttributeSet attrs) { 63 super(context, attrs); 64 } 65 RecognizerView(Context context)66 public RecognizerView(Context context) { 67 super(context); 68 } 69 70 @Override onFinishInflate()71 public void onFinishInflate() { 72 LayoutInflater inflater = LayoutInflater.from(getContext()); 73 inflater.inflate(R.layout.recognizer_view, this, true); 74 75 mSoundLevels = (BitmapSoundLevelView) findViewById(R.id.microphone); 76 mMicButton = (ImageView) findViewById(R.id.recognizer_mic_button); 77 78 mState = State.NOT_LISTENING; 79 } 80 getMicButton()81 public View getMicButton() { 82 return mMicButton; 83 } 84 onClick()85 public void onClick() { 86 if (DEBUG) Log.v(TAG, "onClick " + mState); 87 switch (mState) { 88 case MIC_INITIALIZING: 89 if (DEBUG) 90 Log.d(TAG, "Ignore #onClick as mic is initializing"); 91 return; 92 case LISTENING: 93 mCallback.onCancelRecordingClicked(); 94 break; 95 case RECORDING: 96 mCallback.onStopRecordingClicked(); 97 break; 98 case RECOGNIZING: 99 mCallback.onCancelRecordingClicked(); 100 break; 101 case NOT_LISTENING: 102 mCallback.onStartRecordingClicked(); 103 break; 104 default: 105 return; 106 } 107 } 108 109 @Override onAttachedToWindow()110 public void onAttachedToWindow() { 111 super.onAttachedToWindow(); 112 113 // When the mView is attached to a window, a callback has to be already set. 114 // 115 // This isn't true when this mView is used in the intent API layout. When the user hits 116 // retry, we reattach the layout to the window, which is being shown. onAttachedToWindow( ) 117 // will be called from inside setContentView( ) and before we can attach a callback to it. 118 // We ensure that the callback is not used when not set. 119 // 120 // Preconditions.checkNotNull(mCallback); 121 122 // The callbacks from the microphone happen before the mView is attached to the window. 123 // I need to investigate how to change the code to avoid it. 124 refreshUi(); 125 } 126 showRecording()127 public void showRecording() { 128 updateState(State.RECORDING); 129 } 130 showListening()131 public void showListening() { 132 updateState(State.LISTENING); 133 } 134 showNotListening()135 public void showNotListening() { 136 updateState(State.NOT_LISTENING); 137 } 138 showRecognizing()139 public void showRecognizing() { 140 updateState(State.RECOGNIZING); 141 } 142 setCallback(final Callback callback)143 public void setCallback(final Callback callback) { 144 mCallback = callback; 145 } 146 updateState(State newState)147 private void updateState(State newState) { 148 if (DEBUG) Log.d(TAG, mState + " -> " + newState); 149 mState = newState; 150 refreshUi(); 151 } 152 setSpeechLevelSource(SpeechLevelSource source)153 public void setSpeechLevelSource(SpeechLevelSource source) { 154 mSoundLevels.setLevelSource(source); 155 } 156 157 @Override onSaveInstanceState()158 public Parcelable onSaveInstanceState() { 159 SavedState ss = new SavedState(super.onSaveInstanceState()); 160 ss.mState = mState; 161 return ss; 162 } 163 164 @Override onRestoreInstanceState(Parcelable state)165 public void onRestoreInstanceState(Parcelable state) { 166 if (!(state instanceof SavedState)) { 167 super.onRestoreInstanceState(state); 168 return; 169 } 170 171 SavedState ss = (SavedState) state; 172 super.onRestoreInstanceState(ss.getSuperState()); 173 174 mState = ss.mState; 175 } 176 refreshUi()177 protected void refreshUi() { 178 if (!mEnabled) { 179 return; 180 } 181 switch (mState) { 182 case MIC_INITIALIZING: 183 mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); 184 mSoundLevels.setEnabled(false); 185 break; 186 case LISTENING: 187 mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); 188 mSoundLevels.setEnabled(true); 189 break; 190 case RECORDING: 191 mMicButton.setImageResource(R.drawable.vs_micbtn_rec_selector); 192 mSoundLevels.setEnabled(true); 193 break; 194 case NOT_LISTENING: 195 mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); 196 mSoundLevels.setEnabled(false); 197 break; 198 case RECOGNIZING: 199 mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); 200 mSoundLevels.setEnabled(false); 201 break; 202 } 203 } 204 setMicFocused(boolean hasFocus)205 public void setMicFocused(boolean hasFocus) { 206 if (mEnabled) { 207 if (hasFocus) { 208 mMicButton.setImageResource(R.drawable.ic_voice_focus); 209 } else { 210 mMicButton.setImageResource(R.drawable.ic_voice_available); 211 } 212 213 LeanbackUtils.sendAccessibilityEvent(mMicButton, hasFocus); 214 } 215 } 216 setMicEnabled(boolean enabled)217 public void setMicEnabled(boolean enabled) { 218 mEnabled = enabled; 219 if (enabled) { 220 mMicButton.setAlpha(1.0f); 221 mMicButton.setImageResource(R.drawable.ic_voice_available); 222 } else { 223 mMicButton.setAlpha(0.1f); 224 mMicButton.setImageResource(R.drawable.ic_voice_off); 225 } 226 } 227 showInitializingMic()228 public void showInitializingMic() { 229 updateState(State.MIC_INITIALIZING); 230 } 231 232 public interface Callback { onStartRecordingClicked()233 void onStartRecordingClicked(); onStopRecordingClicked()234 void onStopRecordingClicked(); onCancelRecordingClicked()235 void onCancelRecordingClicked(); 236 } 237 238 public static class SavedState extends View.BaseSavedState { 239 State mState; 240 SavedState(Parcelable superState)241 public SavedState(Parcelable superState) { 242 super(superState); 243 } 244 245 @Override writeToParcel(Parcel out, int flags)246 public void writeToParcel(Parcel out, int flags) { 247 super.writeToParcel(out, flags); 248 out.writeString(mState.toString()); 249 } 250 251 @SuppressWarnings("hiding") 252 public static final Parcelable.Creator<SavedState> CREATOR 253 = new Parcelable.Creator<SavedState>() { 254 255 @Override 256 public SavedState createFromParcel(Parcel in) { 257 return new SavedState(in); 258 } 259 260 @Override 261 public SavedState[] newArray(int size) { 262 return new SavedState[size]; 263 } 264 }; 265 SavedState(Parcel in)266 private SavedState(Parcel in) { 267 super(in); 268 mState = State.valueOf(in.readString()); 269 } 270 } 271 } 272