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