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