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