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