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