ScrimController.java revision ab45a217b0002dcf586d529ab8581cacbbfc37e9
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.PropertyValuesHolder;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.graphics.Color;
25import android.view.View;
26import android.view.ViewTreeObserver;
27import android.view.animation.DecelerateInterpolator;
28import android.view.animation.Interpolator;
29import android.view.animation.PathInterpolator;
30
31import com.android.systemui.R;
32import com.android.systemui.statusbar.BackDropView;
33import com.android.systemui.statusbar.ExpandableNotificationRow;
34import com.android.systemui.statusbar.NotificationData;
35import com.android.systemui.statusbar.ScrimView;
36import com.android.systemui.statusbar.policy.HeadsUpManager;
37import com.android.systemui.statusbar.stack.StackStateAnimator;
38
39/**
40 * Controls both the scrim behind the notifications and in front of the notifications (when a
41 * security method gets shown).
42 */
43public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
44        HeadsUpManager.OnHeadsUpChangedListener {
45    public static final long ANIMATION_DURATION = 220;
46    public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
47            = new PathInterpolator(0f, 0, 0.7f, 1f);
48
49    private static final float SCRIM_BEHIND_ALPHA = 0.62f;
50    private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
51    private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
52    private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
53    private static final int TAG_KEY_ANIM = R.id.scrim;
54    private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
55    private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start;
56    private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end;
57
58    private final ScrimView mScrimBehind;
59    private final ScrimView mScrimInFront;
60    private final UnlockMethodCache mUnlockMethodCache;
61    private final View mHeadsUpScrim;
62
63    private boolean mKeyguardShowing;
64    private float mFraction;
65
66    private boolean mDarkenWhileDragging;
67    private boolean mBouncerShowing;
68    private boolean mWakeAndUnlocking;
69    private boolean mAnimateChange;
70    private boolean mUpdatePending;
71    private boolean mExpanding;
72    private boolean mAnimateKeyguardFadingOut;
73    private long mDurationOverride = -1;
74    private long mAnimationDelay;
75    private Runnable mOnAnimationFinished;
76    private final Interpolator mInterpolator = new DecelerateInterpolator();
77    private BackDropView mBackDropView;
78    private boolean mScrimSrcEnabled;
79    private boolean mDozing;
80    private float mDozeInFrontAlpha;
81    private float mDozeBehindAlpha;
82    private float mCurrentInFrontAlpha;
83    private float mCurrentBehindAlpha;
84    private float mCurrentHeadsUpAlpha = 1;
85    private int mPinnedHeadsUpCount;
86    private float mTopHeadsUpDragAmount;
87    private View mDraggedHeadsUpView;
88    private boolean mForceHideScrims;
89    private boolean mSkipFirstFrame;
90
91    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
92            boolean scrimSrcEnabled) {
93        mScrimBehind = scrimBehind;
94        mScrimInFront = scrimInFront;
95        mHeadsUpScrim = headsUpScrim;
96        final Context context = scrimBehind.getContext();
97        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
98        mScrimSrcEnabled = scrimSrcEnabled;
99        updateHeadsUpScrim(false);
100    }
101
102    public void setKeyguardShowing(boolean showing) {
103        mKeyguardShowing = showing;
104        scheduleUpdate();
105    }
106
107    public void onTrackingStarted() {
108        mExpanding = true;
109        mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
110    }
111
112    public void onExpandingFinished() {
113        mExpanding = false;
114    }
115
116    public void setPanelExpansion(float fraction) {
117        if (mFraction != fraction) {
118            mFraction = fraction;
119            scheduleUpdate();
120            if (mPinnedHeadsUpCount != 0) {
121                updateHeadsUpScrim(false);
122            }
123        }
124    }
125
126    public void setBouncerShowing(boolean showing) {
127        mBouncerShowing = showing;
128        mAnimateChange = !mExpanding;
129        scheduleUpdate();
130    }
131
132    public void setWakeAndUnlocking() {
133        mWakeAndUnlocking = true;
134        scheduleUpdate();
135    }
136
137    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
138            boolean skipFirstFrame) {
139        mWakeAndUnlocking = false;
140        mAnimateKeyguardFadingOut = true;
141        mDurationOverride = duration;
142        mAnimationDelay = delay;
143        mAnimateChange = true;
144        mSkipFirstFrame = skipFirstFrame;
145        mOnAnimationFinished = onAnimationFinished;
146        scheduleUpdate();
147
148        // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
149        // the changes we just scheduled.
150        onPreDraw();
151    }
152
153    public void abortKeyguardFadingOut() {
154        if (mAnimateKeyguardFadingOut) {
155            endAnimateKeyguardFadingOut(true /* force */);
156        }
157    }
158
159    public void animateGoingToFullShade(long delay, long duration) {
160        mDurationOverride = duration;
161        mAnimationDelay = delay;
162        mAnimateChange = true;
163        scheduleUpdate();
164    }
165
166    public void setDozing(boolean dozing) {
167        if (mDozing != dozing) {
168            mDozing = dozing;
169            scheduleUpdate();
170        }
171    }
172
173    public void setDozeInFrontAlpha(float alpha) {
174        mDozeInFrontAlpha = alpha;
175        updateScrimColor(mScrimInFront);
176    }
177
178    public void setDozeBehindAlpha(float alpha) {
179        mDozeBehindAlpha = alpha;
180        updateScrimColor(mScrimBehind);
181    }
182
183    public float getDozeBehindAlpha() {
184        return mDozeBehindAlpha;
185    }
186
187    public float getDozeInFrontAlpha() {
188        return mDozeInFrontAlpha;
189    }
190
191    private void scheduleUpdate() {
192        if (mUpdatePending) return;
193
194        // Make sure that a frame gets scheduled.
195        mScrimBehind.invalidate();
196        mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
197        mUpdatePending = true;
198    }
199
200    private void updateScrims() {
201        if (mAnimateKeyguardFadingOut || mForceHideScrims) {
202            setScrimInFrontColor(0f);
203            setScrimBehindColor(0f);
204        } else if (mWakeAndUnlocking) {
205
206            // During wake and unlock, we first hide everything behind a black scrim, which then
207            // gets faded out from animateKeyguardFadingOut.
208            if (mDozing) {
209                setScrimInFrontColor(0f);
210                setScrimBehindColor(1f);
211            } else {
212                setScrimInFrontColor(1f);
213                setScrimBehindColor(0f);
214            }
215        } else if (!mKeyguardShowing && !mBouncerShowing) {
216            updateScrimNormal();
217            setScrimInFrontColor(0);
218        } else {
219            updateScrimKeyguard();
220        }
221        mAnimateChange = false;
222    }
223
224    private void updateScrimKeyguard() {
225        if (mExpanding && mDarkenWhileDragging) {
226            float behindFraction = Math.max(0, Math.min(mFraction, 1));
227            float fraction = 1 - behindFraction;
228            fraction = (float) Math.pow(fraction, 0.8f);
229            behindFraction = (float) Math.pow(behindFraction, 0.8f);
230            setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
231            setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD);
232        } else if (mBouncerShowing) {
233            setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
234            setScrimBehindColor(0f);
235        } else {
236            float fraction = Math.max(0, Math.min(mFraction, 1));
237            setScrimInFrontColor(0f);
238            setScrimBehindColor(fraction
239                    * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING)
240                    + SCRIM_BEHIND_ALPHA_UNLOCKING);
241        }
242    }
243
244    private void updateScrimNormal() {
245        float frac = mFraction;
246        // let's start this 20% of the way down the screen
247        frac = frac * 1.2f - 0.2f;
248        if (frac <= 0) {
249            setScrimBehindColor(0);
250        } else {
251            // woo, special effects
252            final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
253            setScrimBehindColor(k * SCRIM_BEHIND_ALPHA);
254        }
255    }
256
257    private void setScrimBehindColor(float alpha) {
258        setScrimColor(mScrimBehind, alpha);
259    }
260
261    private void setScrimInFrontColor(float alpha) {
262        setScrimColor(mScrimInFront, alpha);
263        if (alpha == 0f) {
264            mScrimInFront.setClickable(false);
265        } else {
266
267            // Eat touch events (unless dozing).
268            mScrimInFront.setClickable(!mDozing);
269        }
270    }
271
272    private void setScrimColor(View scrim, float alpha) {
273        ValueAnimator runningAnim = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
274        Float target = (Float) scrim.getTag(TAG_KEY_ANIM_TARGET);
275        if (runningAnim != null && target != null) {
276            if (alpha != target) {
277                runningAnim.cancel();
278            } else {
279                return;
280            }
281        }
282        if (mAnimateChange) {
283            startScrimAnimation(scrim, alpha);
284        } else {
285            setCurrentScrimAlpha(scrim, alpha);
286            updateScrimColor(scrim);
287        }
288    }
289
290    private float getDozeAlpha(View scrim) {
291        return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
292    }
293
294    private float getCurrentScrimAlpha(View scrim) {
295        return scrim == mScrimBehind ? mCurrentBehindAlpha
296                : scrim == mScrimInFront ? mCurrentInFrontAlpha
297                : mCurrentHeadsUpAlpha;
298    }
299
300    private void setCurrentScrimAlpha(View scrim, float alpha) {
301        if (scrim == mScrimBehind) {
302            mCurrentBehindAlpha = alpha;
303        } else if (scrim == mScrimInFront) {
304            mCurrentInFrontAlpha = alpha;
305        } else {
306            alpha = Math.max(0.0f, Math.min(1.0f, alpha));
307            mCurrentHeadsUpAlpha = alpha;
308        }
309    }
310
311    private void updateScrimColor(View scrim) {
312        float alpha1 = getCurrentScrimAlpha(scrim);
313        if (scrim instanceof ScrimView) {
314            float alpha2 = getDozeAlpha(scrim);
315            float alpha = 1 - (1 - alpha1) * (1 - alpha2);
316            ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
317        } else {
318            scrim.setAlpha(alpha1);
319        }
320    }
321
322    private void startScrimAnimation(final View scrim, float target) {
323        float current = getCurrentScrimAlpha(scrim);
324        ValueAnimator anim = ValueAnimator.ofFloat(current, target);
325        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
326            @Override
327            public void onAnimationUpdate(ValueAnimator animation) {
328                float alpha = (float) animation.getAnimatedValue();
329                setCurrentScrimAlpha(scrim, alpha);
330                updateScrimColor(scrim);
331            }
332        });
333        anim.setInterpolator(getInterpolator());
334        anim.setStartDelay(mAnimationDelay);
335        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
336        anim.addListener(new AnimatorListenerAdapter() {
337            @Override
338            public void onAnimationEnd(Animator animation) {
339                if (mOnAnimationFinished != null) {
340                    mOnAnimationFinished.run();
341                    mOnAnimationFinished = null;
342                }
343                scrim.setTag(TAG_KEY_ANIM, null);
344                scrim.setTag(TAG_KEY_ANIM_TARGET, null);
345            }
346        });
347        anim.start();
348        if (mSkipFirstFrame) {
349            anim.setCurrentPlayTime(16);
350        }
351        scrim.setTag(TAG_KEY_ANIM, anim);
352        scrim.setTag(TAG_KEY_ANIM_TARGET, target);
353    }
354
355    private Interpolator getInterpolator() {
356        return mAnimateKeyguardFadingOut ? KEYGUARD_FADE_OUT_INTERPOLATOR : mInterpolator;
357    }
358
359    @Override
360    public boolean onPreDraw() {
361        mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
362        mUpdatePending = false;
363        updateScrims();
364        mDurationOverride = -1;
365        mAnimationDelay = 0;
366        mSkipFirstFrame = false;
367
368        // Make sure that we always call the listener even if we didn't start an animation.
369        endAnimateKeyguardFadingOut(false /* force */);
370        return true;
371    }
372
373    private void endAnimateKeyguardFadingOut(boolean force) {
374        mAnimateKeyguardFadingOut = false;
375        if ((force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind)))
376                && mOnAnimationFinished != null) {
377            mOnAnimationFinished.run();
378            mOnAnimationFinished = null;
379        }
380    }
381
382    private boolean isAnimating(View scrim) {
383        return scrim.getTag(TAG_KEY_ANIM) != null;
384    }
385
386    public void setBackDropView(BackDropView backDropView) {
387        mBackDropView = backDropView;
388        mBackDropView.setOnVisibilityChangedRunnable(new Runnable() {
389            @Override
390            public void run() {
391                updateScrimBehindDrawingMode();
392            }
393        });
394        updateScrimBehindDrawingMode();
395    }
396
397    private void updateScrimBehindDrawingMode() {
398        boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled;
399        mScrimBehind.setDrawAsSrc(asSrc);
400    }
401
402    @Override
403    public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
404    }
405
406    @Override
407    public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
408        mPinnedHeadsUpCount++;
409        updateHeadsUpScrim(true);
410    }
411
412    @Override
413    public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
414        mPinnedHeadsUpCount--;
415        if (headsUp == mDraggedHeadsUpView) {
416            mDraggedHeadsUpView = null;
417            mTopHeadsUpDragAmount = 0.0f;
418        }
419        updateHeadsUpScrim(true);
420    }
421
422    @Override
423    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
424    }
425
426    private void updateHeadsUpScrim(boolean animate) {
427        float alpha = calculateHeadsUpAlpha();
428        ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim,
429                TAG_KEY_ANIM);
430        float animEndValue = -1;
431        if (previousAnimator != null) {
432            if (animate || alpha == mCurrentHeadsUpAlpha) {
433                previousAnimator.cancel();
434            } else {
435                animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_END_ALPHA);
436            }
437        }
438        if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) {
439            if (animate) {
440                startScrimAnimation(mHeadsUpScrim, alpha);
441                mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha);
442                mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
443            } else {
444                if (previousAnimator != null) {
445                    float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
446                            TAG_HUN_START_ALPHA);
447                    float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
448                           TAG_HUN_END_ALPHA);
449                    // we need to increase all animation keyframes of the previous animator by the
450                    // relative change to the end value
451                    PropertyValuesHolder[] values = previousAnimator.getValues();
452                    float relativeDiff = alpha - previousEndValue;
453                    float newStartValue = previousStartValue + relativeDiff;
454                    values[0].setFloatValues(newStartValue, alpha);
455                    mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue);
456                    mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
457                    previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
458                } else {
459                    // update the alpha directly
460                    setCurrentScrimAlpha(mHeadsUpScrim, alpha);
461                    updateScrimColor(mHeadsUpScrim);
462                }
463            }
464        }
465    }
466
467    /**
468     * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
469     * the heads up is in its resting space and 1 means it's fully dragged out.
470     *
471     * @param draggedHeadsUpView the dragged view
472     * @param topHeadsUpDragAmount how far is it dragged
473     */
474    public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) {
475        mTopHeadsUpDragAmount = topHeadsUpDragAmount;
476        mDraggedHeadsUpView = draggedHeadsUpView;
477        updateHeadsUpScrim(false);
478    }
479
480    private float calculateHeadsUpAlpha() {
481        float alpha;
482        if (mPinnedHeadsUpCount >= 2) {
483            alpha = 1.0f;
484        } else if (mPinnedHeadsUpCount == 0) {
485            alpha = 0.0f;
486        } else {
487            alpha = 1.0f - mTopHeadsUpDragAmount;
488        }
489        float expandFactor = (1.0f - mFraction);
490        expandFactor = Math.max(expandFactor, 0.0f);
491        return alpha * expandFactor;
492    }
493
494    public void forceHideScrims(boolean hide) {
495        mForceHideScrims = hide;
496        mAnimateChange = false;
497        scheduleUpdate();
498    }
499}
500