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