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 package com.android.car.bugreport; 17 18 import android.content.Context; 19 import android.graphics.Canvas; 20 import android.graphics.Color; 21 import android.graphics.Paint; 22 import android.graphics.Rect; 23 import android.graphics.drawable.BitmapDrawable; 24 import android.media.MediaRecorder; 25 import android.util.AttributeSet; 26 import android.util.Log; 27 import android.view.View; 28 29 import androidx.annotation.Nullable; 30 31 /** 32 * A view that draws MIC icon and an animated ellipsoid. The ellipsoid animation shows the sound 33 * amplitude from {@link MediaRecorder}. 34 * 35 * <p>All the constant values are chosen experimentally. 36 */ 37 public class VoiceRecordingView extends View { 38 private static final String TAG = VoiceRecordingView.class.getSimpleName(); 39 40 private static final float DROPOFF_STEP = 10f; 41 private static final long ANIMATION_INTERVAL_MS = 70; 42 private static final float RECORDER_AMPLITUDE_NORMALIZER_COEF = 16192.0f; 43 44 private final Paint mPaint; 45 private final BitmapDrawable mMicIconDrawable; 46 47 private float mCurrentRadius; 48 private MediaRecorder mRecorder; 49 VoiceRecordingView(Context context, @Nullable AttributeSet attrs)50 public VoiceRecordingView(Context context, @Nullable AttributeSet attrs) { 51 super(context, attrs); 52 mMicIconDrawable = (BitmapDrawable) context.getDrawable( 53 android.R.drawable.ic_btn_speak_now); 54 55 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 56 mPaint.setColor(Color.LTGRAY); 57 mPaint.setStyle(Paint.Style.FILL); 58 } 59 60 /** Sets MediaRecorder that will be used to animate the ellipsoid. */ setRecorder(@ullable MediaRecorder recorder)61 public void setRecorder(@Nullable MediaRecorder recorder) { 62 mRecorder = recorder; 63 invalidate(); 64 } 65 66 @Override onSizeChanged(int w, int h, int oldW, int oldH)67 protected void onSizeChanged(int w, int h, int oldW, int oldH) { 68 super.onSizeChanged(w, h, oldW, oldH); 69 70 float micIconWidth = mMicIconDrawable.getBitmap().getWidth(); 71 float micIconHeight = mMicIconDrawable.getBitmap().getHeight(); 72 int micIconDrawableWidth = (int) (micIconWidth / micIconHeight * h); 73 int micIconDrawableLeft = (w - micIconDrawableWidth) / 2; 74 mMicIconDrawable.setBounds( 75 new Rect(micIconDrawableLeft, 0, micIconDrawableLeft + micIconDrawableWidth, h)); 76 } 77 updateCurrentRadius(int width)78 private void updateCurrentRadius(int width) { 79 final float maxRadius = width / 4; 80 float radius = 0; 81 82 if (mRecorder != null) { 83 try { 84 radius += maxRadius * mRecorder.getMaxAmplitude() 85 / RECORDER_AMPLITUDE_NORMALIZER_COEF; 86 } catch (IllegalStateException e) { 87 Log.v(TAG, "Failed to get max amplitude from MediaRecorder"); 88 } 89 } 90 91 if (radius > mCurrentRadius) { 92 mCurrentRadius = radius; 93 } else { 94 mCurrentRadius = Math.max(radius, mCurrentRadius - DROPOFF_STEP); 95 } 96 mCurrentRadius = Math.min(maxRadius, mCurrentRadius); 97 } 98 99 @Override onDraw(Canvas canvas)100 protected void onDraw(Canvas canvas) { 101 super.onDraw(canvas); 102 103 final int width = canvas.getWidth(); 104 final int height = canvas.getHeight(); 105 106 updateCurrentRadius(width); 107 108 // Draws an ellipsoid with horizontal radius calculated from MediaRecorder's amplitude. 109 final int mx = width / 2; 110 final int my = height / 2; 111 canvas.drawCircle(mx, my, height / 2, mPaint); 112 canvas.drawCircle(mx - mCurrentRadius, my, height / 2, mPaint); 113 canvas.drawCircle(mx + mCurrentRadius, my, height / 2, mPaint); 114 canvas.drawRect(mx - mCurrentRadius, 0, mx + mCurrentRadius, height, mPaint); 115 116 if (mRecorder != null) { 117 postInvalidateDelayed(ANIMATION_INTERVAL_MS); 118 } 119 120 mMicIconDrawable.draw(canvas); 121 } 122 } 123