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