Home | History | Annotate | Download | only in mediapicker
      1 /*
      2  * Copyright (C) 2015 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.messaging.ui.mediapicker;
     17 
     18 import android.animation.ObjectAnimator;
     19 import android.animation.TimeAnimator;
     20 import android.animation.TimeAnimator.TimeListener;
     21 import android.content.Context;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Canvas;
     24 import android.graphics.Color;
     25 import android.graphics.Paint;
     26 import android.graphics.Paint.Style;
     27 import android.util.AttributeSet;
     28 import android.util.Log;
     29 import android.view.View;
     30 import android.view.accessibility.AccessibilityNodeInfo;
     31 
     32 import com.android.messaging.R;
     33 import com.android.messaging.util.LogUtil;
     34 
     35 /**
     36  * This view draws circular sound levels. By default the sound levels are black, unless
     37  * otherwise defined via {@link #mPrimaryLevelPaint}.
     38  */
     39 public class SoundLevels extends View {
     40     private static final String TAG = LogUtil.BUGLE_TAG;
     41     private static final boolean DEBUG = false;
     42 
     43     private boolean mCenterDefined;
     44     private int mCenterX;
     45     private int mCenterY;
     46 
     47     // Paint for the main level meter, most closely follows the mic.
     48     private final Paint mPrimaryLevelPaint;
     49 
     50     // The minimum size of the levels as a percentage of the max, that is the size when volume is 0.
     51     private final float mMinimumLevel;
     52 
     53     // The minimum size of the levels, that is the size when volume is 0.
     54     private final float mMinimumLevelSize;
     55 
     56     // The maximum size of the levels, that is the size when volume is 100.
     57     private final float mMaximumLevelSize;
     58 
     59     // Generates clock ticks for the animation using the global animation loop.
     60     private final TimeAnimator mSpeechLevelsAnimator;
     61 
     62     private float mCurrentVolume;
     63 
     64     // Indicates whether we should be animating the sound level.
     65     private boolean mIsEnabled;
     66 
     67     // Input level is pulled from here.
     68     private AudioLevelSource mLevelSource;
     69 
     70     public SoundLevels(final Context context) {
     71         this(context, null);
     72     }
     73 
     74     public SoundLevels(final Context context, final AttributeSet attrs) {
     75         this(context, attrs, 0);
     76     }
     77 
     78     public SoundLevels(final Context context, final AttributeSet attrs, final int defStyle) {
     79         super(context, attrs, defStyle);
     80 
     81         // Safe source, replaced with system one when attached.
     82         mLevelSource = new AudioLevelSource();
     83         mLevelSource.setSpeechLevel(0);
     84 
     85         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SoundLevels,
     86                 defStyle, 0);
     87 
     88         mMaximumLevelSize = a.getDimensionPixelOffset(
     89                 R.styleable.SoundLevels_maxLevelRadius, 0);
     90         mMinimumLevelSize = a.getDimensionPixelOffset(
     91                 R.styleable.SoundLevels_minLevelRadius, 0);
     92         mMinimumLevel = mMinimumLevelSize / mMaximumLevelSize;
     93 
     94         mPrimaryLevelPaint = new Paint();
     95         mPrimaryLevelPaint.setColor(
     96                 a.getColor(R.styleable.SoundLevels_primaryColor, Color.BLACK));
     97         mPrimaryLevelPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
     98 
     99         a.recycle();
    100 
    101         // This animator generates ticks that invalidate the
    102         // view so that the animation is synced with the global animation loop.
    103         // TODO: We could probably remove this in favor of using postInvalidateOnAnimation
    104         // which might improve things further.
    105         mSpeechLevelsAnimator = new TimeAnimator();
    106         mSpeechLevelsAnimator.setRepeatCount(ObjectAnimator.INFINITE);
    107         mSpeechLevelsAnimator.setTimeListener(new TimeListener() {
    108             @Override
    109             public void onTimeUpdate(final TimeAnimator animation, final long totalTime,
    110                     final long deltaTime) {
    111                 invalidate();
    112             }
    113         });
    114     }
    115 
    116     @Override
    117     protected void onDraw(final Canvas canvas) {
    118         if (!mIsEnabled) {
    119             return;
    120         }
    121 
    122         if (!mCenterDefined) {
    123             // One time computation here, because we can't rely on getWidth() to be computed at
    124             // constructor time or in onFinishInflate :(.
    125             mCenterX = getWidth() / 2;
    126             mCenterY = getWidth() / 2;
    127             mCenterDefined = true;
    128         }
    129 
    130         final int level = mLevelSource.getSpeechLevel();
    131         // Either ease towards the target level, or decay away from it depending on whether
    132         // its higher or lower than the current.
    133         if (level > mCurrentVolume) {
    134             mCurrentVolume = mCurrentVolume + ((level - mCurrentVolume) / 4);
    135         } else {
    136             mCurrentVolume = mCurrentVolume * 0.95f;
    137         }
    138 
    139         final float radius = mMinimumLevel + (1f - mMinimumLevel) * mCurrentVolume / 100;
    140         mPrimaryLevelPaint.setStyle(Style.FILL);
    141         canvas.drawCircle(mCenterX, mCenterY, radius * mMaximumLevelSize, mPrimaryLevelPaint);
    142     }
    143 
    144     public void setLevelSource(final AudioLevelSource source) {
    145         if (DEBUG) {
    146             Log.d(TAG, "Speech source set.");
    147         }
    148         mLevelSource = source;
    149     }
    150 
    151     private void startSpeechLevelsAnimator() {
    152         if (DEBUG) {
    153             Log.d(TAG, "startAnimator()");
    154         }
    155         if (!mSpeechLevelsAnimator.isStarted()) {
    156             mSpeechLevelsAnimator.start();
    157         }
    158     }
    159 
    160     private void stopSpeechLevelsAnimator() {
    161         if (DEBUG) {
    162             Log.d(TAG, "stopAnimator()");
    163         }
    164         if (mSpeechLevelsAnimator.isStarted()) {
    165             mSpeechLevelsAnimator.end();
    166         }
    167     }
    168 
    169     @Override
    170     protected void onDetachedFromWindow() {
    171         super.onDetachedFromWindow();
    172         stopSpeechLevelsAnimator();
    173     }
    174 
    175     @Override
    176     public void setEnabled(final boolean enabled) {
    177         if (enabled == mIsEnabled) {
    178             return;
    179         }
    180         if (DEBUG) {
    181             Log.d("TAG", "setEnabled: " + enabled);
    182         }
    183         super.setEnabled(enabled);
    184         mIsEnabled = enabled;
    185         setKeepScreenOn(enabled);
    186         updateSpeechLevelsAnimatorState();
    187     }
    188 
    189     private void updateSpeechLevelsAnimatorState() {
    190         if (mIsEnabled) {
    191             startSpeechLevelsAnimator();
    192         } else {
    193             stopSpeechLevelsAnimator();
    194         }
    195     }
    196 
    197     /**
    198      * This is required to make the View findable by uiautomator
    199      */
    200     @Override
    201     public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
    202         super.onInitializeAccessibilityNodeInfo(info);
    203         info.setClassName(SoundLevels.class.getCanonicalName());
    204     }
    205 
    206     /**
    207      * Set the alpha level of the sound circles.
    208      */
    209     public void setPrimaryColorAlpha(final int alpha) {
    210         mPrimaryLevelPaint.setAlpha(alpha);
    211     }
    212 }
    213