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