ScrimController.java revision 5e08e6957d1ada027ee93a0c8f745a9f8b89bb60
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.graphics.Color;
25import android.util.Log;
26import android.view.View;
27import android.view.ViewTreeObserver;
28import android.view.animation.AnimationUtils;
29import android.view.animation.DecelerateInterpolator;
30import android.view.animation.Interpolator;
31
32import com.android.systemui.R;
33import com.android.systemui.doze.DozeHost;
34import com.android.systemui.doze.DozeLog;
35import com.android.systemui.statusbar.BackDropView;
36import com.android.systemui.statusbar.ScrimView;
37
38/**
39 * Controls both the scrim behind the notifications and in front of the notifications (when a
40 * security method gets shown).
41 */
42public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
43    private static final String TAG = "ScrimController";
44    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
45
46    public static final long ANIMATION_DURATION = 220;
47
48    private static final float SCRIM_BEHIND_ALPHA = 0.62f;
49    private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.55f;
50    private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
51    private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
52    private static final int TAG_KEY_ANIM = R.id.scrim;
53
54    private final ScrimView mScrimBehind;
55    private final ScrimView mScrimInFront;
56    private final UnlockMethodCache mUnlockMethodCache;
57    private final DozeParameters mDozeParameters;
58
59    private boolean mKeyguardShowing;
60    private float mFraction;
61
62    private boolean mDarkenWhileDragging;
63    private boolean mBouncerShowing;
64    private boolean mAnimateChange;
65    private boolean mUpdatePending;
66    private boolean mExpanding;
67    private boolean mAnimateKeyguardFadingOut;
68    private long mDurationOverride = -1;
69    private long mAnimationDelay;
70    private Runnable mOnAnimationFinished;
71    private boolean mAnimationStarted;
72    private boolean mDozing;
73    private DozeHost.PulseCallback mPulseCallback;
74    private final Interpolator mInterpolator = new DecelerateInterpolator();
75    private final Interpolator mLinearOutSlowInInterpolator;
76    private BackDropView mBackDropView;
77    private boolean mScrimSrcEnabled;
78
79    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, boolean scrimSrcEnabled) {
80        mScrimBehind = scrimBehind;
81        mScrimInFront = scrimInFront;
82        final Context context = scrimBehind.getContext();
83        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
84        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
85                android.R.interpolator.linear_out_slow_in);
86        mDozeParameters = new DozeParameters(context);
87        mScrimSrcEnabled = scrimSrcEnabled;
88    }
89
90    public void setKeyguardShowing(boolean showing) {
91        mKeyguardShowing = showing;
92        scheduleUpdate();
93    }
94
95    public void onTrackingStarted() {
96        mExpanding = true;
97        mDarkenWhileDragging = !mUnlockMethodCache.isMethodInsecure();
98    }
99
100    public void onExpandingFinished() {
101        mExpanding = false;
102    }
103
104    public void setPanelExpansion(float fraction) {
105        if (mFraction != fraction) {
106            mFraction = fraction;
107            scheduleUpdate();
108        }
109    }
110
111    public void setBouncerShowing(boolean showing) {
112        mBouncerShowing = showing;
113        mAnimateChange = !mExpanding;
114        scheduleUpdate();
115    }
116
117    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished) {
118        mAnimateKeyguardFadingOut = true;
119        mDurationOverride = duration;
120        mAnimationDelay = delay;
121        mAnimateChange = true;
122        mOnAnimationFinished = onAnimationFinished;
123        scheduleUpdate();
124    }
125
126    public void animateGoingToFullShade(long delay, long duration) {
127        mDurationOverride = duration;
128        mAnimationDelay = delay;
129        mAnimateChange = true;
130        scheduleUpdate();
131    }
132
133    public void setDozing(boolean dozing) {
134        if (mDozing == dozing) return;
135        mDozing = dozing;
136        if (!mDozing) {
137            cancelPulsing();
138            mAnimateChange = true;
139        } else {
140            mAnimateChange = false;
141        }
142        scheduleUpdate();
143    }
144
145    /** When dozing, fade screen contents in and out using the front scrim. */
146    public void pulse(@NonNull DozeHost.PulseCallback callback) {
147        if (callback == null) {
148            throw new IllegalArgumentException("callback must not be null");
149        }
150
151        if (!mDozing || mPulseCallback != null) {
152            // Pulse suppressed.
153            callback.onPulseFinished();
154            return;
155        }
156
157        // Begin pulse.  Note that it's very important that the pulse finished callback
158        // be invoked when we're done so that the caller can drop the pulse wakelock.
159        mPulseCallback = callback;
160        mScrimInFront.post(mPulseIn);
161    }
162
163    public boolean isPulsing() {
164        return mPulseCallback != null;
165    }
166
167    private void cancelPulsing() {
168        if (DEBUG) Log.d(TAG, "Cancel pulsing");
169
170        if (mPulseCallback != null) {
171            mScrimInFront.removeCallbacks(mPulseIn);
172            mScrimInFront.removeCallbacks(mPulseOut);
173            pulseFinished();
174        }
175    }
176
177    private void pulseStarted() {
178        if (mPulseCallback != null) {
179            mPulseCallback.onPulseStarted();
180        }
181    }
182
183    private void pulseFinished() {
184        if (mPulseCallback != null) {
185            mPulseCallback.onPulseFinished();
186            mPulseCallback = null;
187        }
188    }
189
190    private void scheduleUpdate() {
191        if (mUpdatePending) return;
192
193        // Make sure that a frame gets scheduled.
194        mScrimBehind.invalidate();
195        mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
196        mUpdatePending = true;
197    }
198
199    private void updateScrims() {
200        if (mAnimateKeyguardFadingOut) {
201            setScrimInFrontColor(0f);
202            setScrimBehindColor(0f);
203        } else if (!mKeyguardShowing && !mBouncerShowing) {
204            updateScrimNormal();
205            setScrimInFrontColor(0);
206        } else {
207            updateScrimKeyguard();
208        }
209        mAnimateChange = false;
210    }
211
212    private void updateScrimKeyguard() {
213        if (mExpanding && mDarkenWhileDragging) {
214            float behindFraction = Math.max(0, Math.min(mFraction, 1));
215            float fraction = 1 - behindFraction;
216            fraction = (float) Math.pow(fraction, 0.8f);
217            behindFraction = (float) Math.pow(behindFraction, 0.8f);
218            setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
219            setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD);
220        } else if (mBouncerShowing) {
221            setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
222            setScrimBehindColor(0f);
223        } else if (mDozing) {
224            setScrimInFrontColor(1);
225        } else {
226            float fraction = Math.max(0, Math.min(mFraction, 1));
227            setScrimInFrontColor(0f);
228            setScrimBehindColor(fraction
229                    * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING)
230                    + SCRIM_BEHIND_ALPHA_UNLOCKING);
231        }
232    }
233
234    private void updateScrimNormal() {
235        float frac = mFraction;
236        // let's start this 20% of the way down the screen
237        frac = frac * 1.2f - 0.2f;
238        if (frac <= 0) {
239            setScrimBehindColor(0);
240        } else {
241            // woo, special effects
242            final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
243            setScrimBehindColor(k * SCRIM_BEHIND_ALPHA);
244        }
245    }
246
247    private void setScrimBehindColor(float alpha) {
248        setScrimColor(mScrimBehind, alpha);
249    }
250
251    private void setScrimInFrontColor(float alpha) {
252        setScrimColor(mScrimInFront, alpha);
253        if (alpha == 0f) {
254            mScrimInFront.setClickable(false);
255        } else {
256
257            // Eat touch events (unless dozing).
258            mScrimInFront.setClickable(!mDozing);
259        }
260    }
261
262    private void setScrimColor(ScrimView scrim, float alpha) {
263        Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
264        if (runningAnim instanceof ValueAnimator) {
265            ((ValueAnimator) runningAnim).cancel();
266            scrim.setTag(TAG_KEY_ANIM, null);
267        }
268        int color = Color.argb((int) (alpha * 255), 0, 0, 0);
269        if (mAnimateChange) {
270            startScrimAnimation(scrim, color);
271        } else {
272            scrim.setScrimColor(color);
273        }
274    }
275
276    private void startScrimAnimation(final ScrimView scrim, int targetColor) {
277        int current = Color.alpha(scrim.getScrimColor());
278        int target = Color.alpha(targetColor);
279        if (current == targetColor) {
280            return;
281        }
282        ValueAnimator anim = ValueAnimator.ofInt(current, target);
283        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
284            @Override
285            public void onAnimationUpdate(ValueAnimator animation) {
286                int value = (int) animation.getAnimatedValue();
287                scrim.setScrimColor(Color.argb(value, 0, 0, 0));
288            }
289        });
290        anim.setInterpolator(mAnimateKeyguardFadingOut
291                ? mLinearOutSlowInInterpolator
292                : mInterpolator);
293        anim.setStartDelay(mAnimationDelay);
294        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
295        anim.addListener(new AnimatorListenerAdapter() {
296            @Override
297            public void onAnimationEnd(Animator animation) {
298                if (mOnAnimationFinished != null) {
299                    mOnAnimationFinished.run();
300                    mOnAnimationFinished = null;
301                }
302                scrim.setTag(TAG_KEY_ANIM, null);
303            }
304        });
305        anim.start();
306        scrim.setTag(TAG_KEY_ANIM, anim);
307        mAnimationStarted = true;
308    }
309
310    @Override
311    public boolean onPreDraw() {
312        mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
313        mUpdatePending = false;
314        updateScrims();
315        mAnimateKeyguardFadingOut = false;
316        mDurationOverride = -1;
317        mAnimationDelay = 0;
318
319        // Make sure that we always call the listener even if we didn't start an animation.
320        if (!mAnimationStarted && mOnAnimationFinished != null) {
321            mOnAnimationFinished.run();
322            mOnAnimationFinished = null;
323        }
324        mAnimationStarted = false;
325        return true;
326    }
327
328    private final Runnable mPulseIn = new Runnable() {
329        @Override
330        public void run() {
331            if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing);
332            if (!mDozing) return;
333            DozeLog.tracePulseStart();
334            mDurationOverride = mDozeParameters.getPulseInDuration();
335            mAnimationDelay = 0;
336            mAnimateChange = true;
337            mOnAnimationFinished = mPulseInFinished;
338            setScrimColor(mScrimInFront, 0);
339
340            // Signal that the pulse is ready to turn the screen on and draw.
341            pulseStarted();
342        }
343    };
344
345    private final Runnable mPulseInFinished = new Runnable() {
346        @Override
347        public void run() {
348            if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
349            if (!mDozing) return;
350            mScrimInFront.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
351        }
352    };
353
354    private final Runnable mPulseOut = new Runnable() {
355        @Override
356        public void run() {
357            if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
358            if (!mDozing) return;
359            mDurationOverride = mDozeParameters.getPulseOutDuration();
360            mAnimationDelay = 0;
361            mAnimateChange = true;
362            mOnAnimationFinished = mPulseOutFinished;
363            setScrimColor(mScrimInFront, 1);
364        }
365    };
366
367    private final Runnable mPulseOutFinished = new Runnable() {
368        @Override
369        public void run() {
370            if (DEBUG) Log.d(TAG, "Pulse out finished");
371            DozeLog.tracePulseFinish();
372
373            // Signal that the pulse is all finished so we can turn the screen off now.
374            pulseFinished();
375        }
376    };
377
378    public void setBackDropView(BackDropView backDropView) {
379        mBackDropView = backDropView;
380        mBackDropView.setOnVisibilityChangedRunnable(new Runnable() {
381            @Override
382            public void run() {
383                updateScrimBehindDrawingMode();
384            }
385        });
386        updateScrimBehindDrawingMode();
387    }
388
389    private void updateScrimBehindDrawingMode() {
390        boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled;
391        mScrimBehind.setDrawAsSrc(asSrc);
392    }
393}
394