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