/*
* Copyright (C) 2014 The Android Open Source 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.phone;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
/**
* Controller which handles all the doze animations of the scrims.
*/
public class DozeScrimController {
private static final String TAG = "DozeScrimController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final DozeParameters mDozeParameters;
private final Interpolator mPulseInInterpolator = PhoneStatusBar.ALPHA_OUT;
private final Interpolator mPulseInInterpolatorPickup;
private final Interpolator mPulseOutInterpolator = PhoneStatusBar.ALPHA_IN;
private final Interpolator mDozeAnimationInterpolator;
private final Handler mHandler = new Handler();
private final ScrimController mScrimController;
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
private Animator mInFrontAnimator;
private Animator mBehindAnimator;
private float mInFrontTarget;
private float mBehindTarget;
public DozeScrimController(ScrimController scrimController, Context context) {
mScrimController = scrimController;
mDozeParameters = new DozeParameters(context);
mDozeAnimationInterpolator = mPulseInInterpolatorPickup =
AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
}
public void setDozing(boolean dozing, boolean animate) {
if (mDozing == dozing) return;
mDozing = dozing;
if (mDozing) {
abortAnimations();
mScrimController.setDozeBehindAlpha(1f);
mScrimController.setDozeInFrontAlpha(1f);
} else {
cancelPulsing();
if (animate) {
startScrimAnimation(false /* inFront */, 0f /* target */,
NotificationPanelView.DOZE_ANIMATION_DURATION, mDozeAnimationInterpolator);
startScrimAnimation(true /* inFront */, 0f /* target */,
NotificationPanelView.DOZE_ANIMATION_DURATION, mDozeAnimationInterpolator);
} else {
abortAnimations();
mScrimController.setDozeBehindAlpha(0f);
mScrimController.setDozeInFrontAlpha(0f);
}
}
}
/** When dozing, fade screen contents in and out using the front scrim. */
public void pulse(@NonNull DozeHost.PulseCallback callback, int reason) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
if (!mDozing || mPulseCallback != null) {
// Pulse suppressed.
callback.onPulseFinished();
return;
}
// Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
mPulseCallback = callback;
mPulseReason = reason;
mHandler.post(mPulseIn);
}
public boolean isPulsing() {
return mPulseCallback != null;
}
private void cancelPulsing() {
if (DEBUG) Log.d(TAG, "Cancel pulsing");
if (mPulseCallback != null) {
mHandler.removeCallbacks(mPulseIn);
mHandler.removeCallbacks(mPulseOut);
pulseFinished();
}
}
private void pulseStarted() {
if (mPulseCallback != null) {
mPulseCallback.onPulseStarted();
}
}
private void pulseFinished() {
if (mPulseCallback != null) {
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
}
private void abortAnimations() {
if (mInFrontAnimator != null) {
mInFrontAnimator.cancel();
}
if (mBehindAnimator != null) {
mBehindAnimator.cancel();
}
}
private void startScrimAnimation(final boolean inFront, float target, long duration,
Interpolator interpolator) {
startScrimAnimation(inFront, target, duration, interpolator, 0 /* delay */,
null /* endRunnable */);
}
private void startScrimAnimation(final boolean inFront, float target, long duration,
Interpolator interpolator, long delay, final Runnable endRunnable) {
Animator current = getCurrentAnimator(inFront);
if (current != null) {
float currentTarget = getCurrentTarget(inFront);
if (currentTarget == target) {
return;
}
current.cancel();
}
ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
setDozeAlpha(inFront, value);
}
});
anim.setInterpolator(interpolator);
anim.setDuration(duration);
anim.setStartDelay(delay);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setCurrentAnimator(inFront, null);
if (endRunnable != null) {
endRunnable.run();
}
}
});
anim.start();
setCurrentAnimator(inFront, anim);
setCurrentTarget(inFront, target);
}
private float getCurrentTarget(boolean inFront) {
return inFront ? mInFrontTarget : mBehindTarget;
}
private void setCurrentTarget(boolean inFront, float target) {
if (inFront) {
mInFrontTarget = target;
} else {
mBehindTarget = target;
}
}
private Animator getCurrentAnimator(boolean inFront) {
return inFront ? mInFrontAnimator : mBehindAnimator;
}
private void setCurrentAnimator(boolean inFront, Animator animator) {
if (inFront) {
mInFrontAnimator = animator;
} else {
mBehindAnimator = animator;
}
}
private void setDozeAlpha(boolean inFront, float alpha) {
if (inFront) {
mScrimController.setDozeInFrontAlpha(alpha);
} else {
mScrimController.setDozeBehindAlpha(alpha);
}
}
private float getDozeAlpha(boolean inFront) {
return inFront
? mScrimController.getDozeInFrontAlpha()
: mScrimController.getDozeBehindAlpha();
}
private final Runnable mPulseIn = new Runnable() {
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+ DozeLog.pulseReasonToString(mPulseReason));
if (!mDozing) return;
DozeLog.tracePulseStart(mPulseReason);
final boolean pickup = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
startScrimAnimation(true /* inFront */, 0f,
mDozeParameters.getPulseInDuration(pickup),
pickup ? mPulseInInterpolatorPickup : mPulseInInterpolator,
mDozeParameters.getPulseInDelay(pickup),
mPulseInFinished);
// Signal that the pulse is ready to turn the screen on and draw.
pulseStarted();
}
};
private final Runnable mPulseInFinished = new Runnable() {
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
if (!mDozing) return;
mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
}
};
private final Runnable mPulseOut = new Runnable() {
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
if (!mDozing) return;
startScrimAnimation(true /* inFront */, 1f, mDozeParameters.getPulseOutDuration(),
mPulseOutInterpolator, 0 /* delay */, mPulseOutFinished);
}
};
private final Runnable mPulseOutFinished = new Runnable() {
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Pulse out finished");
DozeLog.tracePulseFinish();
// Signal that the pulse is all finished so we can turn the screen off now.
pulseFinished();
}
};
}