ScrimController.java revision 131c1e2960fa5bdf54bfb6fcd5ac98c9f728f796
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.AnimationUtils;
28import android.view.animation.DecelerateInterpolator;
29import android.view.animation.Interpolator;
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
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    private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start;
53    private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end;
54
55    private final ScrimView mScrimBehind;
56    private final ScrimView mScrimInFront;
57    private final UnlockMethodCache mUnlockMethodCache;
58    private final View mHeadsUpScrim;
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 final Interpolator mInterpolator = new DecelerateInterpolator();
74    private final Interpolator mLinearOutSlowInInterpolator;
75    private BackDropView mBackDropView;
76    private boolean mScrimSrcEnabled;
77    private boolean mDozing;
78    private float mDozeInFrontAlpha;
79    private float mDozeBehindAlpha;
80    private float mCurrentInFrontAlpha;
81    private float mCurrentBehindAlpha;
82    private float mCurrentHeadsUpAlpha = 1;
83    private int mPinnedHeadsUpCount;
84    private float mTopHeadsUpDragAmount;
85    private View mDraggedHeadsUpView;
86
87    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
88            boolean scrimSrcEnabled) {
89        mScrimBehind = scrimBehind;
90        mScrimInFront = scrimInFront;
91        mHeadsUpScrim = headsUpScrim;
92        final Context context = scrimBehind.getContext();
93        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
94        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
95                android.R.interpolator.linear_out_slow_in);
96        mScrimSrcEnabled = scrimSrcEnabled;
97        updateHeadsUpScrim(false);
98    }
99
100    public void setKeyguardShowing(boolean showing) {
101        mKeyguardShowing = showing;
102        scheduleUpdate();
103    }
104
105    public void onTrackingStarted() {
106        mExpanding = true;
107        mDarkenWhileDragging = !mUnlockMethodCache.isCurrentlyInsecure();
108    }
109
110    public void onExpandingFinished() {
111        mExpanding = false;
112    }
113
114    public void setPanelExpansion(float fraction) {
115        if (mFraction != fraction) {
116            mFraction = fraction;
117            scheduleUpdate();
118            if (mPinnedHeadsUpCount != 0) {
119                updateHeadsUpScrim(false);
120            }
121        }
122    }
123
124    public void setBouncerShowing(boolean showing) {
125        mBouncerShowing = showing;
126        mAnimateChange = !mExpanding;
127        scheduleUpdate();
128    }
129
130    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished) {
131        mAnimateKeyguardFadingOut = true;
132        mDurationOverride = duration;
133        mAnimationDelay = delay;
134        mAnimateChange = true;
135        mOnAnimationFinished = onAnimationFinished;
136        scheduleUpdate();
137    }
138
139    public void animateGoingToFullShade(long delay, long duration) {
140        mDurationOverride = duration;
141        mAnimationDelay = delay;
142        mAnimateChange = true;
143        scheduleUpdate();
144    }
145
146    public void setDozing(boolean dozing) {
147        mDozing = dozing;
148        scheduleUpdate();
149    }
150
151    public void setDozeInFrontAlpha(float alpha) {
152        mDozeInFrontAlpha = alpha;
153        updateScrimColor(mScrimInFront);
154    }
155
156    public void setDozeBehindAlpha(float alpha) {
157        mDozeBehindAlpha = alpha;
158        updateScrimColor(mScrimBehind);
159    }
160
161    public float getDozeBehindAlpha() {
162        return mDozeBehindAlpha;
163    }
164
165    public float getDozeInFrontAlpha() {
166        return mDozeInFrontAlpha;
167    }
168
169    private void scheduleUpdate() {
170        if (mUpdatePending) return;
171
172        // Make sure that a frame gets scheduled.
173        mScrimBehind.invalidate();
174        mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
175        mUpdatePending = true;
176    }
177
178    private void updateScrims() {
179        if (mAnimateKeyguardFadingOut) {
180            setScrimInFrontColor(0f);
181            setScrimBehindColor(0f);
182        } else if (!mKeyguardShowing && !mBouncerShowing) {
183            updateScrimNormal();
184            setScrimInFrontColor(0);
185        } else {
186            updateScrimKeyguard();
187        }
188        mAnimateChange = false;
189    }
190
191    private void updateScrimKeyguard() {
192        if (mExpanding && mDarkenWhileDragging) {
193            float behindFraction = Math.max(0, Math.min(mFraction, 1));
194            float fraction = 1 - behindFraction;
195            fraction = (float) Math.pow(fraction, 0.8f);
196            behindFraction = (float) Math.pow(behindFraction, 0.8f);
197            setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
198            setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD);
199        } else if (mBouncerShowing) {
200            setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
201            setScrimBehindColor(0f);
202        } else {
203            float fraction = Math.max(0, Math.min(mFraction, 1));
204            setScrimInFrontColor(0f);
205            setScrimBehindColor(fraction
206                    * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING)
207                    + SCRIM_BEHIND_ALPHA_UNLOCKING);
208        }
209    }
210
211    private void updateScrimNormal() {
212        float frac = mFraction;
213        // let's start this 20% of the way down the screen
214        frac = frac * 1.2f - 0.2f;
215        if (frac <= 0) {
216            setScrimBehindColor(0);
217        } else {
218            // woo, special effects
219            final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
220            setScrimBehindColor(k * SCRIM_BEHIND_ALPHA);
221        }
222    }
223
224    private void setScrimBehindColor(float alpha) {
225        setScrimColor(mScrimBehind, alpha);
226    }
227
228    private void setScrimInFrontColor(float alpha) {
229        setScrimColor(mScrimInFront, alpha);
230        if (alpha == 0f) {
231            mScrimInFront.setClickable(false);
232        } else {
233
234            // Eat touch events (unless dozing).
235            mScrimInFront.setClickable(!mDozing);
236        }
237    }
238
239    private void setScrimColor(View scrim, float alpha) {
240        Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
241        if (runningAnim instanceof ValueAnimator) {
242            ((ValueAnimator) runningAnim).cancel();
243            scrim.setTag(TAG_KEY_ANIM, null);
244        }
245        if (mAnimateChange) {
246            startScrimAnimation(scrim, alpha);
247        } else {
248            setCurrentScrimAlpha(scrim, alpha);
249            updateScrimColor(scrim);
250        }
251    }
252
253    private float getDozeAlpha(View scrim) {
254        return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
255    }
256
257    private float getCurrentScrimAlpha(View scrim) {
258        return scrim == mScrimBehind ? mCurrentBehindAlpha
259                : scrim == mScrimInFront ? mCurrentInFrontAlpha
260                : mCurrentHeadsUpAlpha;
261    }
262
263    private void setCurrentScrimAlpha(View scrim, float alpha) {
264        if (scrim == mScrimBehind) {
265            mCurrentBehindAlpha = alpha;
266        } else if (scrim == mScrimInFront) {
267            mCurrentInFrontAlpha = alpha;
268        } else {
269            alpha = Math.max(0.0f, Math.min(1.0f, alpha));
270            mCurrentHeadsUpAlpha = alpha;
271        }
272    }
273
274    private void updateScrimColor(View scrim) {
275        float alpha1 = getCurrentScrimAlpha(scrim);
276        if (scrim instanceof ScrimView) {
277            float alpha2 = getDozeAlpha(scrim);
278            float alpha = 1 - (1 - alpha1) * (1 - alpha2);
279            ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
280        } else {
281            scrim.setAlpha(alpha1);
282        }
283    }
284
285    private void startScrimAnimation(final View scrim, float target) {
286        float current = getCurrentScrimAlpha(scrim);
287        ValueAnimator anim = ValueAnimator.ofFloat(current, target);
288        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
289            @Override
290            public void onAnimationUpdate(ValueAnimator animation) {
291                float alpha = (float) animation.getAnimatedValue();
292                setCurrentScrimAlpha(scrim, alpha);
293                updateScrimColor(scrim);
294            }
295        });
296        anim.setInterpolator(getInterpolator());
297        anim.setStartDelay(mAnimationDelay);
298        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
299        anim.addListener(new AnimatorListenerAdapter() {
300            @Override
301            public void onAnimationEnd(Animator animation) {
302                if (mOnAnimationFinished != null) {
303                    mOnAnimationFinished.run();
304                    mOnAnimationFinished = null;
305                }
306                scrim.setTag(TAG_KEY_ANIM, null);
307            }
308        });
309        anim.start();
310        scrim.setTag(TAG_KEY_ANIM, anim);
311        mAnimationStarted = true;
312    }
313
314    private Interpolator getInterpolator() {
315        return mAnimateKeyguardFadingOut ? mLinearOutSlowInInterpolator : mInterpolator;
316    }
317
318    @Override
319    public boolean onPreDraw() {
320        mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
321        mUpdatePending = false;
322        updateScrims();
323        mAnimateKeyguardFadingOut = false;
324        mDurationOverride = -1;
325        mAnimationDelay = 0;
326
327        // Make sure that we always call the listener even if we didn't start an animation.
328        if (!mAnimationStarted && mOnAnimationFinished != null) {
329            mOnAnimationFinished.run();
330            mOnAnimationFinished = null;
331        }
332        mAnimationStarted = false;
333        return true;
334    }
335
336    public void setBackDropView(BackDropView backDropView) {
337        mBackDropView = backDropView;
338        mBackDropView.setOnVisibilityChangedRunnable(new Runnable() {
339            @Override
340            public void run() {
341                updateScrimBehindDrawingMode();
342            }
343        });
344        updateScrimBehindDrawingMode();
345    }
346
347    private void updateScrimBehindDrawingMode() {
348        boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled;
349        mScrimBehind.setDrawAsSrc(asSrc);
350    }
351
352    @Override
353    public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
354    }
355
356    @Override
357    public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
358        mPinnedHeadsUpCount++;
359        updateHeadsUpScrim(true);
360    }
361
362    @Override
363    public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
364        mPinnedHeadsUpCount--;
365        if (headsUp == mDraggedHeadsUpView) {
366            mDraggedHeadsUpView = null;
367            mTopHeadsUpDragAmount = 0.0f;
368        }
369        updateHeadsUpScrim(true);
370    }
371
372    @Override
373    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
374    }
375
376    private void updateHeadsUpScrim(boolean animate) {
377        float alpha = calculateHeadsUpAlpha();
378        ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim,
379                TAG_KEY_ANIM);
380        float animEndValue = -1;
381        if (previousAnimator != null) {
382            if (animate || alpha == mCurrentHeadsUpAlpha) {
383                previousAnimator.cancel();
384            } else {
385                animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_END_ALPHA);
386            }
387        }
388        if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) {
389            if (animate) {
390                startScrimAnimation(mHeadsUpScrim, alpha);
391                mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha);
392                mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
393            } else {
394                if (previousAnimator != null) {
395                    float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
396                            TAG_HUN_START_ALPHA);
397                    float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
398                           TAG_HUN_END_ALPHA);
399                    // we need to increase all animation keyframes of the previous animator by the
400                    // relative change to the end value
401                    PropertyValuesHolder[] values = previousAnimator.getValues();
402                    float relativeDiff = alpha - previousEndValue;
403                    float newStartValue = previousStartValue + relativeDiff;
404                    values[0].setFloatValues(newStartValue, alpha);
405                    mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue);
406                    mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
407                    previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
408                } else {
409                    // update the alpha directly
410                    setCurrentScrimAlpha(mHeadsUpScrim, alpha);
411                    updateScrimColor(mHeadsUpScrim);
412                }
413            }
414        }
415    }
416
417    /**
418     * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
419     * the heads up is in its resting space and 1 means it's fully dragged out.
420     *
421     * @param draggedHeadsUpView the dragged view
422     * @param topHeadsUpDragAmount how far is it dragged
423     */
424    public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) {
425        mTopHeadsUpDragAmount = topHeadsUpDragAmount;
426        mDraggedHeadsUpView = draggedHeadsUpView;
427        updateHeadsUpScrim(false);
428    }
429
430    private float calculateHeadsUpAlpha() {
431        float alpha;
432        if (mPinnedHeadsUpCount >= 2) {
433            alpha = 1.0f;
434        } else if (mPinnedHeadsUpCount == 0) {
435            alpha = 0.0f;
436        } else {
437            alpha = 1.0f - mTopHeadsUpDragAmount;
438        }
439        float expandFactor = (1.0f - mFraction);
440        expandFactor = Math.max(expandFactor, 0.0f);
441        return alpha * expandFactor;
442    }
443}
444