1/*
2 * Copyright (C) 2014 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
17package com.android.systemui.statusbar.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.annotation.NonNull;
23import android.content.Context;
24import android.os.Handler;
25import android.util.Log;
26import android.view.animation.Interpolator;
27
28import com.android.systemui.Interpolators;
29import com.android.systemui.doze.DozeHost;
30import com.android.systemui.doze.DozeLog;
31
32/**
33 * Controller which handles all the doze animations of the scrims.
34 */
35public class DozeScrimController {
36    private static final String TAG = "DozeScrimController";
37    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
38
39    private final DozeParameters mDozeParameters;
40    private final Handler mHandler = new Handler();
41    private final ScrimController mScrimController;
42
43    private boolean mDozing;
44    private DozeHost.PulseCallback mPulseCallback;
45    private int mPulseReason;
46    private Animator mInFrontAnimator;
47    private Animator mBehindAnimator;
48    private float mInFrontTarget;
49    private float mBehindTarget;
50
51    public DozeScrimController(ScrimController scrimController, Context context) {
52        mScrimController = scrimController;
53        mDozeParameters = new DozeParameters(context);
54    }
55
56    public void setDozing(boolean dozing, boolean animate) {
57        if (mDozing == dozing) return;
58        mDozing = dozing;
59        if (mDozing) {
60            abortAnimations();
61            mScrimController.setDozeBehindAlpha(1f);
62            mScrimController.setDozeInFrontAlpha(1f);
63        } else {
64            cancelPulsing();
65            if (animate) {
66                startScrimAnimation(false /* inFront */, 0f /* target */,
67                        NotificationPanelView.DOZE_ANIMATION_DURATION,
68                        Interpolators.LINEAR_OUT_SLOW_IN);
69                startScrimAnimation(true /* inFront */, 0f /* target */,
70                        NotificationPanelView.DOZE_ANIMATION_DURATION,
71                        Interpolators.LINEAR_OUT_SLOW_IN);
72            } else {
73                abortAnimations();
74                mScrimController.setDozeBehindAlpha(0f);
75                mScrimController.setDozeInFrontAlpha(0f);
76            }
77        }
78    }
79
80    /** When dozing, fade screen contents in and out using the front scrim. */
81    public void pulse(@NonNull DozeHost.PulseCallback callback, int reason) {
82        if (callback == null) {
83            throw new IllegalArgumentException("callback must not be null");
84        }
85
86        if (!mDozing || mPulseCallback != null) {
87            // Pulse suppressed.
88            callback.onPulseFinished();
89            return;
90        }
91
92        // Begin pulse.  Note that it's very important that the pulse finished callback
93        // be invoked when we're done so that the caller can drop the pulse wakelock.
94        mPulseCallback = callback;
95        mPulseReason = reason;
96        mHandler.post(mPulseIn);
97    }
98
99    /**
100     * Aborts pulsing immediately.
101     */
102    public void abortPulsing() {
103        cancelPulsing();
104        if (mDozing) {
105            mScrimController.setDozeBehindAlpha(1f);
106            mScrimController.setDozeInFrontAlpha(1f);
107        }
108    }
109
110    public void onScreenTurnedOn() {
111        if (isPulsing()) {
112            final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
113                    || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
114            startScrimAnimation(true /* inFront */, 0f,
115                    mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
116                    pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
117                    mPulseInFinished);
118        }
119    }
120
121    public boolean isPulsing() {
122        return mPulseCallback != null;
123    }
124
125    public boolean isDozing() {
126        return mDozing;
127    }
128
129    private void cancelPulsing() {
130        if (DEBUG) Log.d(TAG, "Cancel pulsing");
131
132        if (mPulseCallback != null) {
133            mHandler.removeCallbacks(mPulseIn);
134            mHandler.removeCallbacks(mPulseOut);
135            pulseFinished();
136        }
137    }
138
139    private void pulseStarted() {
140        if (mPulseCallback != null) {
141            mPulseCallback.onPulseStarted();
142        }
143    }
144
145    private void pulseFinished() {
146        if (mPulseCallback != null) {
147            mPulseCallback.onPulseFinished();
148            mPulseCallback = null;
149        }
150    }
151
152    private void abortAnimations() {
153        if (mInFrontAnimator != null) {
154            mInFrontAnimator.cancel();
155        }
156        if (mBehindAnimator != null) {
157            mBehindAnimator.cancel();
158        }
159    }
160
161    private void startScrimAnimation(final boolean inFront, float target, long duration,
162            Interpolator interpolator) {
163        startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
164    }
165
166    private void startScrimAnimation(final boolean inFront, float target, long duration,
167            Interpolator interpolator, final Runnable endRunnable) {
168        Animator current = getCurrentAnimator(inFront);
169        if (current != null) {
170            float currentTarget = getCurrentTarget(inFront);
171            if (currentTarget == target) {
172                return;
173            }
174            current.cancel();
175        }
176        ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
177        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
178            @Override
179            public void onAnimationUpdate(ValueAnimator animation) {
180                float value = (float) animation.getAnimatedValue();
181                setDozeAlpha(inFront, value);
182            }
183        });
184        anim.setInterpolator(interpolator);
185        anim.setDuration(duration);
186        anim.addListener(new AnimatorListenerAdapter() {
187            @Override
188            public void onAnimationEnd(Animator animation) {
189                setCurrentAnimator(inFront, null);
190                if (endRunnable != null) {
191                    endRunnable.run();
192                }
193            }
194        });
195        anim.start();
196        setCurrentAnimator(inFront, anim);
197        setCurrentTarget(inFront, target);
198    }
199
200    private float getCurrentTarget(boolean inFront) {
201        return inFront ? mInFrontTarget : mBehindTarget;
202    }
203
204    private void setCurrentTarget(boolean inFront, float target) {
205        if (inFront) {
206            mInFrontTarget = target;
207        } else {
208            mBehindTarget = target;
209        }
210    }
211
212    private Animator getCurrentAnimator(boolean inFront) {
213        return inFront ? mInFrontAnimator : mBehindAnimator;
214    }
215
216    private void setCurrentAnimator(boolean inFront, Animator animator) {
217        if (inFront) {
218            mInFrontAnimator = animator;
219        } else {
220            mBehindAnimator = animator;
221        }
222    }
223
224    private void setDozeAlpha(boolean inFront, float alpha) {
225        if (inFront) {
226            mScrimController.setDozeInFrontAlpha(alpha);
227        } else {
228            mScrimController.setDozeBehindAlpha(alpha);
229        }
230    }
231
232    private float getDozeAlpha(boolean inFront) {
233        return inFront
234                ? mScrimController.getDozeInFrontAlpha()
235                : mScrimController.getDozeBehindAlpha();
236    }
237
238    private final Runnable mPulseIn = new Runnable() {
239        @Override
240        public void run() {
241            if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
242                    + DozeLog.pulseReasonToString(mPulseReason));
243            if (!mDozing) return;
244            DozeLog.tracePulseStart(mPulseReason);
245
246            // Signal that the pulse is ready to turn the screen on and draw.
247            pulseStarted();
248        }
249    };
250
251    private final Runnable mPulseInFinished = new Runnable() {
252        @Override
253        public void run() {
254            if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
255            if (!mDozing) return;
256            mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
257        }
258    };
259
260    private final Runnable mPulseOut = new Runnable() {
261        @Override
262        public void run() {
263            if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
264            if (!mDozing) return;
265            startScrimAnimation(true /* inFront */, 1f, mDozeParameters.getPulseOutDuration(),
266                    Interpolators.ALPHA_IN, mPulseOutFinished);
267        }
268    };
269
270    private final Runnable mPulseOutFinished = new Runnable() {
271        @Override
272        public void run() {
273            if (DEBUG) Log.d(TAG, "Pulse out finished");
274            DozeLog.tracePulseFinish();
275
276            // Signal that the pulse is all finished so we can turn the screen off now.
277            pulseFinished();
278        }
279    };
280}
281