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