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