package ee.ioc.phon.android.speechutils.view; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import java.util.ArrayList; import java.util.List; import ee.ioc.phon.android.speechutils.R; // TODO: rather use com.google.android.material.floatingactionbutton.FloatingActionButton public class MicButton extends androidx.appcompat.widget.AppCompatImageButton { public enum State { // Initial state INIT, // An attempt was made to start the recording/transcription, but unclear if it succeeded WAITING, // We are listening (if there is human speech) LISTENING, // We are recording and sending the (speech) audio for transcription RECORDING, // Recording has stopped but not all the recorded audio has been processed yet TRANSCRIBING, // An error has occurred ERROR } // TODO: take these from some device specific configuration private static final float DB_MIN = 15.0f; private static final float DB_MAX = 30.0f; private Drawable mDrawableMic; private Drawable mDrawableMicTranscribing; private Drawable mDrawableMicWaiting; private List<Drawable> mVolumeLevels; private Animation mAnimFadeInOutInf; private int mVolumeLevel = 0; private int mMaxLevel; public MicButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (!isInEditMode()) { init(context); } } public MicButton(Context context, AttributeSet attrs) { super(context, attrs); if (!isInEditMode()) { init(context); } } public MicButton(Context context) { super(context); if (!isInEditMode()) { init(context); } } public void setState(State state) { switch (state) { case INIT: case ERROR: setEnabled(true); clearAnimation(); setBackgroundDrawable(mDrawableMic); break; case WAITING: setEnabled(false); setBackgroundDrawable(mDrawableMicWaiting); break; case RECORDING: setEnabled(true); break; case LISTENING: setEnabled(true); setBackgroundDrawable(mVolumeLevels.get(0)); break; case TRANSCRIBING: setEnabled(true); setBackgroundDrawable(mDrawableMicTranscribing); startAnimation(mAnimFadeInOutInf); break; default: break; } } public void setVolumeLevel(float rmsdB) { int index = (int) ((rmsdB - DB_MIN) / (DB_MAX - DB_MIN) * mMaxLevel); int level = Math.min(Math.max(0, index), mMaxLevel); if (level != mVolumeLevel) { mVolumeLevel = level; setBackgroundDrawable(mVolumeLevels.get(level)); } } private void initAnimations(Context context) { Resources res = getResources(); mDrawableMic = res.getDrawable(R.drawable.button_mic); mDrawableMicTranscribing = res.getDrawable(R.drawable.button_mic_transcribing); mDrawableMicWaiting = res.getDrawable(R.drawable.button_mic_waiting); mVolumeLevels = new ArrayList<>(); mVolumeLevels.add(res.getDrawable(R.drawable.button_mic_recording_0)); mVolumeLevels.add(res.getDrawable(R.drawable.button_mic_recording_1)); mVolumeLevels.add(res.getDrawable(R.drawable.button_mic_recording_2)); mVolumeLevels.add(res.getDrawable(R.drawable.button_mic_recording_3)); mMaxLevel = mVolumeLevels.size() - 1; mAnimFadeInOutInf = AnimationUtils.loadAnimation(context, R.anim.fade_inout_inf); } private void init(Context context) { initAnimations(context); // Vibrate when the microphone key is pressed down setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { // TODO: what is the diff between KEYBOARD_TAP and the other constants? // TODO: does not seem to work on Android 7.1 v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); } return false; }); } }