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