/*
* Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.audiofx.Visualizer;
import android.os.AsyncTask;
import android.os.UserHandle;
import android.support.v7.graphics.Palette;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.android.systemui.tuner.TunerService;
import cyanogenmod.providers.CMSettings;
public class VisualizerView extends View
implements Palette.PaletteAsyncListener, TunerService.Tunable {
private static final String TAG = VisualizerView.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String LOCKSCREEN_VISUALIZER_ENABLED =
"cmsecure:" + CMSettings.Secure.LOCKSCREEN_VISUALIZER_ENABLED;
private Paint mPaint;
private Visualizer mVisualizer;
private ObjectAnimator mVisualizerColorAnimator;
private ValueAnimator[] mValueAnimators;
private float[] mFFTPoints;
private int mStatusBarState;
private boolean mVisualizerEnabled = false;
private boolean mVisible = false;
private boolean mPlaying = false;
private boolean mPowerSaveMode = false;
private boolean mDisplaying = false; // the state we're animating to
private boolean mDozing = false;
private boolean mOccluded = false;
private int mColor;
private Bitmap mCurrentBitmap;
private Visualizer.OnDataCaptureListener mVisualizerListener =
new Visualizer.OnDataCaptureListener() {
byte rfk, ifk;
int dbValue;
float magnitude;
@Override
public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
}
@Override
public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
for (int i = 0; i < 32; i++) {
mValueAnimators[i].cancel();
rfk = fft[i * 2 + 2];
ifk = fft[i * 2 + 3];
magnitude = rfk * rfk + ifk * ifk;
dbValue = magnitude > 0 ? (int) (10 * Math.log10(magnitude)) : 0;
mValueAnimators[i].setFloatValues(mFFTPoints[i * 4 + 1],
mFFTPoints[3] - (dbValue * 16f));
mValueAnimators[i].start();
}
}
};
private final Runnable mLinkVisualizer = new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.w(TAG, "+++ mLinkVisualizer run()");
}
try {
mVisualizer = new Visualizer(0);
} catch (Exception e) {
Log.e(TAG, "error initializing visualizer", e);
return;
}
mVisualizer.setEnabled(false);
mVisualizer.setCaptureSize(66);
mVisualizer.setDataCaptureListener(mVisualizerListener,Visualizer.getMaxCaptureRate(),
false, true);
mVisualizer.setEnabled(true);
if (DEBUG) {
Log.w(TAG, "--- mLinkVisualizer run()");
}
}
};
private final Runnable mAsyncUnlinkVisualizer = new Runnable() {
@Override
public void run() {
AsyncTask.execute(mUnlinkVisualizer);
}
};
private final Runnable mUnlinkVisualizer = new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.w(TAG, "+++ mUnlinkVisualizer run(), mVisualizer: " + mVisualizer);
}
if (mVisualizer != null) {
mVisualizer.setEnabled(false);
mVisualizer.release();
mVisualizer = null;
}
if (DEBUG) {
Log.w(TAG, "--- mUninkVisualizer run()");
}
}
};
public VisualizerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mColor = Color.TRANSPARENT;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mColor);
mFFTPoints = new float[128];
mValueAnimators = new ValueAnimator[32];
for (int i = 0; i < 32; i++) {
final int j = i * 4 + 1;
mValueAnimators[i] = new ValueAnimator();
mValueAnimators[i].setDuration(128);
mValueAnimators[i].addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFFTPoints[j] = (float) animation.getAnimatedValue();
postInvalidate();
}
});
}
}
public VisualizerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VisualizerView(Context context) {
this(context, null, 0);
}
private void updateViewVisibility() {
final int curVis = getVisibility();
final int newVis = mStatusBarState != StatusBarState.SHADE
&& mVisualizerEnabled ? View.VISIBLE : View.GONE;
if (curVis != newVis) {
setVisibility(newVis);
checkStateChanged();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
TunerService.get(mContext).addTunable(this, LOCKSCREEN_VISUALIZER_ENABLED);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
TunerService.get(mContext).removeTunable(this);
mCurrentBitmap = null;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
float barUnit = w / 32f;
float barWidth = barUnit * 8f / 9f;
barUnit = barWidth + (barUnit - barWidth) * 32f / 31f;
mPaint.setStrokeWidth(barWidth);
for (int i = 0; i < 32; i++) {
mFFTPoints[i * 4] = mFFTPoints[i * 4 + 2] = i * barUnit + (barWidth / 2);
mFFTPoints[i * 4 + 1] = h;
mFFTPoints[i * 4 + 3] = h;
}
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mVisualizer != null) {
canvas.drawLines(mFFTPoints, mPaint);
}
}
public void setVisible(boolean visible) {
if (mVisible != visible) {
if (DEBUG) {
Log.i(TAG, "setVisible() called with visible = [" + visible + "]");
}
mVisible = visible;
checkStateChanged();
}
}
public void setDozing(boolean dozing) {
if (mDozing != dozing) {
if (DEBUG) {
Log.i(TAG, "setDozing() called with dozing = [" + dozing + "]");
}
mDozing = dozing;
checkStateChanged();
}
}
public void setPlaying(boolean playing) {
if (mPlaying != playing) {
if (DEBUG) {
Log.i(TAG, "setPlaying() called with playing = [" + playing + "]");
}
mPlaying = playing;
checkStateChanged();
}
}
public void setPowerSaveMode(boolean powerSaveMode) {
if (mPowerSaveMode != powerSaveMode) {
if (DEBUG) {
Log.i(TAG, "setPowerSaveMode() called with powerSaveMode = [" + powerSaveMode + "]");
}
mPowerSaveMode = powerSaveMode;
checkStateChanged();
}
}
public void setOccluded(boolean occluded) {
if (mOccluded != occluded) {
if (DEBUG) {
Log.i(TAG, "setOccluded() called with occluded = [" + occluded + "]");
}
mOccluded = occluded;
checkStateChanged();
}
}
public void setStatusBarState(int statusBarState) {
if (mStatusBarState != statusBarState) {
mStatusBarState = statusBarState;
updateViewVisibility();
}
}
public void setBitmap(Bitmap bitmap) {
if (mCurrentBitmap == bitmap) {
return;
}
mCurrentBitmap = bitmap;
if (bitmap != null) {
Palette.generateAsync(bitmap, this);
} else {
setColor(Color.TRANSPARENT);
}
}
@Override
public void onGenerated(Palette palette) {
int color = Color.TRANSPARENT;
color = palette.getVibrantColor(color);
if (color == Color.TRANSPARENT) {
color = palette.getLightVibrantColor(color);
if (color == Color.TRANSPARENT) {
color = palette.getDarkVibrantColor(color);
}
}
setColor(color);
}
private void setColor(int color) {
if (color == Color.TRANSPARENT) {
color = Color.WHITE;
}
color = Color.argb(140, Color.red(color), Color.green(color), Color.blue(color));
if (mColor != color) {
mColor = color;
if (mVisualizer != null) {
if (mVisualizerColorAnimator != null) {
mVisualizerColorAnimator.cancel();
}
mVisualizerColorAnimator = ObjectAnimator.ofArgb(mPaint, "color",
mPaint.getColor(), mColor);
mVisualizerColorAnimator.setStartDelay(600);
mVisualizerColorAnimator.setDuration(1200);
mVisualizerColorAnimator.start();
} else {
mPaint.setColor(mColor);
}
}
}
private void checkStateChanged() {
if (getVisibility() == View.VISIBLE && mVisible && mPlaying && !mDozing && !mPowerSaveMode
&& mVisualizerEnabled && !mOccluded) {
if (!mDisplaying) {
mDisplaying = true;
AsyncTask.execute(mLinkVisualizer);
animate()
.alpha(1f)
.withEndAction(null)
.setDuration(800);
}
} else {
if (mDisplaying) {
mDisplaying = false;
if (mVisible) {
animate()
.alpha(0f)
.withEndAction(mAsyncUnlinkVisualizer)
.setDuration(600);
} else {
animate().
alpha(0f)
.withEndAction(mAsyncUnlinkVisualizer)
.setDuration(0);
}
}
}
}
@Override
public void onTuningChanged(String key, String newValue) {
if (!LOCKSCREEN_VISUALIZER_ENABLED.equals(key)) {
return;
}
mVisualizerEnabled = newValue == null || Integer.parseInt(newValue) != 0;
checkStateChanged();
updateViewVisibility();
}
}