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