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