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