/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.inputmethod.leanback.voice; import com.android.inputmethod.leanback.LeanbackUtils; import com.android.inputmethod.leanback.R; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.RelativeLayout; /** * Displays the recognizer status. * This mView includes a {@link BitmapSoundLevelsView} to display the recording value. */ public class RecognizerView extends RelativeLayout { private static final boolean DEBUG = false; private static final String TAG = "RecognizerView"; private BitmapSoundLevelView mSoundLevels; protected ImageView mMicButton; private Callback mCallback; private State mState; private boolean mEnabled; private enum State { NOT_LISTENING, MIC_INITIALIZING, LISTENING, RECORDING, RECOGNIZING, } public RecognizerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public RecognizerView(Context context, AttributeSet attrs) { super(context, attrs); } public RecognizerView(Context context) { super(context); } @Override public void onFinishInflate() { LayoutInflater inflater = LayoutInflater.from(getContext()); inflater.inflate(R.layout.recognizer_view, this, true); mSoundLevels = (BitmapSoundLevelView) findViewById(R.id.microphone); mMicButton = (ImageView) findViewById(R.id.recognizer_mic_button); mState = State.NOT_LISTENING; } public View getMicButton() { return mMicButton; } public void onClick() { if (DEBUG) Log.v(TAG, "onClick " + mState); switch (mState) { case MIC_INITIALIZING: if (DEBUG) Log.d(TAG, "Ignore #onClick as mic is initializing"); return; case LISTENING: mCallback.onCancelRecordingClicked(); break; case RECORDING: mCallback.onStopRecordingClicked(); break; case RECOGNIZING: mCallback.onCancelRecordingClicked(); break; case NOT_LISTENING: mCallback.onStartRecordingClicked(); break; default: return; } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); // When the mView is attached to a window, a callback has to be already set. // // This isn't true when this mView is used in the intent API layout. When the user hits // retry, we reattach the layout to the window, which is being shown. onAttachedToWindow( ) // will be called from inside setContentView( ) and before we can attach a callback to it. // We ensure that the callback is not used when not set. // // Preconditions.checkNotNull(mCallback); // The callbacks from the microphone happen before the mView is attached to the window. // I need to investigate how to change the code to avoid it. refreshUi(); } public void showRecording() { updateState(State.RECORDING); } public void showListening() { updateState(State.LISTENING); } public void showNotListening() { updateState(State.NOT_LISTENING); } public void showRecognizing() { updateState(State.RECOGNIZING); } public void setCallback(final Callback callback) { mCallback = callback; } private void updateState(State newState) { if (DEBUG) Log.d(TAG, mState + " -> " + newState); mState = newState; refreshUi(); } public void setSpeechLevelSource(SpeechLevelSource source) { mSoundLevels.setLevelSource(source); } @Override public Parcelable onSaveInstanceState() { SavedState ss = new SavedState(super.onSaveInstanceState()); ss.mState = mState; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); mState = ss.mState; } protected void refreshUi() { if (!mEnabled) { return; } switch (mState) { case MIC_INITIALIZING: mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); mSoundLevels.setEnabled(false); break; case LISTENING: mMicButton.setImageResource(R.drawable.vs_micbtn_on_selector); mSoundLevels.setEnabled(true); break; case RECORDING: mMicButton.setImageResource(R.drawable.vs_micbtn_rec_selector); mSoundLevels.setEnabled(true); break; case NOT_LISTENING: mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); mSoundLevels.setEnabled(false); break; case RECOGNIZING: mMicButton.setImageResource(R.drawable.vs_micbtn_off_selector); mSoundLevels.setEnabled(false); break; } } public void setMicFocused(boolean hasFocus) { if (mEnabled) { if (hasFocus) { mMicButton.setImageResource(R.drawable.ic_voice_focus); } else { mMicButton.setImageResource(R.drawable.ic_voice_available); } LeanbackUtils.sendAccessibilityEvent(mMicButton, hasFocus); } } public void setMicEnabled(boolean enabled) { mEnabled = enabled; if (enabled) { mMicButton.setAlpha(1.0f); mMicButton.setImageResource(R.drawable.ic_voice_available); } else { mMicButton.setAlpha(0.1f); mMicButton.setImageResource(R.drawable.ic_voice_off); } } public void showInitializingMic() { updateState(State.MIC_INITIALIZING); } public interface Callback { void onStartRecordingClicked(); void onStopRecordingClicked(); void onCancelRecordingClicked(); } public static class SavedState extends View.BaseSavedState { State mState; public SavedState(Parcelable superState) { super(superState); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeString(mState.toString()); } @SuppressWarnings("hiding") public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public SavedState createFromParcel(Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; private SavedState(Parcel in) { super(in); mState = State.valueOf(in.readString()); } } }