• 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.ValueAnimator;
22 import android.annotation.IntDef;
23 import android.app.AlarmManager;
24 import android.content.Context;
25 import android.graphics.Color;
26 import android.graphics.drawable.Drawable;
27 import android.os.Handler;
28 import android.os.Trace;
29 import android.util.Log;
30 import android.util.MathUtils;
31 import android.view.View;
32 import android.view.ViewTreeObserver;
33 import android.view.animation.DecelerateInterpolator;
34 import android.view.animation.Interpolator;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.colorextraction.ColorExtractor;
38 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
39 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
40 import com.android.internal.graphics.ColorUtils;
41 import com.android.internal.util.function.TriConsumer;
42 import com.android.keyguard.KeyguardUpdateMonitor;
43 import com.android.keyguard.KeyguardUpdateMonitorCallback;
44 import com.android.systemui.Dependency;
45 import com.android.systemui.Dumpable;
46 import com.android.systemui.R;
47 import com.android.systemui.colorextraction.SysuiColorExtractor;
48 import com.android.systemui.statusbar.ScrimView;
49 import com.android.systemui.statusbar.notification.stack.ViewState;
50 import com.android.systemui.util.AlarmTimeout;
51 import com.android.systemui.util.wakelock.DelayedWakeLock;
52 import com.android.systemui.util.wakelock.WakeLock;
53 
54 import java.io.FileDescriptor;
55 import java.io.PrintWriter;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
58 import java.util.function.Consumer;
59 
60 /**
61  * Controls both the scrim behind the notifications and in front of the notifications (when a
62  * security method gets shown).
63  */
64 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener,
65         Dumpable {
66 
67     static final String TAG = "ScrimController";
68     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
69 
70     /**
71      * General scrim animation duration.
72      */
73     public static final long ANIMATION_DURATION = 220;
74     /**
75      * Longer duration, currently only used when going to AOD.
76      */
77     public static final long ANIMATION_DURATION_LONG = 1000;
78     /**
79      * When both scrims have 0 alpha.
80      */
81     public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
82     /**
83      * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
84      */
85     public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
86     /**
87      * When at least 1 scrim is fully opaque (alpha set to 1.)
88      */
89     public static final int VISIBILITY_FULLY_OPAQUE = 2;
90 
91     @IntDef(prefix = { "VISIBILITY_" }, value = {
92             VISIBILITY_FULLY_TRANSPARENT,
93             VISIBILITY_SEMI_TRANSPARENT,
94             VISIBILITY_FULLY_OPAQUE
95     })
96     @Retention(RetentionPolicy.SOURCE)
97     public @interface ScrimVisibility {}
98 
99     /**
100      * Default alpha value for most scrims.
101      */
102     public static final float GRADIENT_SCRIM_ALPHA = 0.2f;
103     /**
104      * Scrim opacity when the phone is about to wake-up.
105      */
106     public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f;
107     /**
108      * A scrim varies its opacity based on a busyness factor, for example
109      * how many notifications are currently visible.
110      */
111     public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.7f;
112 
113     /**
114      * The most common scrim, the one under the keyguard.
115      */
116     protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
117 
118     static final int TAG_KEY_ANIM = R.id.scrim;
119     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
120     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
121     private static final float NOT_INITIALIZED = -1;
122 
123     private ScrimState mState = ScrimState.UNINITIALIZED;
124     private final Context mContext;
125     protected final ScrimView mScrimBehind;
126     protected final ScrimView mScrimInFront;
127     private final UnlockMethodCache mUnlockMethodCache;
128     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
129     private final DozeParameters mDozeParameters;
130     private final AlarmTimeout mTimeTicker;
131     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
132     private final Handler mHandler;
133 
134     private final SysuiColorExtractor mColorExtractor;
135     private GradientColors mColors;
136     private boolean mNeedsDrawableColorUpdate;
137 
138     protected float mScrimBehindAlpha;
139     protected float mScrimBehindAlphaResValue;
140     protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
141 
142     // Assuming the shade is expanded during initialization
143     private float mExpansionFraction = 1f;
144 
145     private boolean mDarkenWhileDragging;
146     private boolean mExpansionAffectsAlpha = true;
147     protected boolean mAnimateChange;
148     private boolean mUpdatePending;
149     private boolean mTracking;
150     protected long mAnimationDuration = -1;
151     private long mAnimationDelay;
152     private Runnable mOnAnimationFinished;
153     private boolean mDeferFinishedListener;
154     private final Interpolator mInterpolator = new DecelerateInterpolator();
155     private float mCurrentInFrontAlpha  = NOT_INITIALIZED;
156     private float mCurrentBehindAlpha = NOT_INITIALIZED;
157     private int mCurrentInFrontTint;
158     private int mCurrentBehindTint;
159     private boolean mWallpaperVisibilityTimedOut;
160     private int mScrimsVisibility;
161     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
162     private final Consumer<Integer> mScrimVisibleListener;
163     private boolean mBlankScreen;
164     private boolean mScreenBlankingCallbackCalled;
165     private Callback mCallback;
166     private boolean mWallpaperSupportsAmbientMode;
167     private boolean mScreenOn;
168 
169     // Scrim blanking callbacks
170     private Runnable mPendingFrameCallback;
171     private Runnable mBlankingTransitionRunnable;
172 
173     private final WakeLock mWakeLock;
174     private boolean mWakeLockHeld;
175     private boolean mKeyguardOccluded;
176 
ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, TriConsumer<ScrimState, Float, GradientColors> scrimStateListener, Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters, AlarmManager alarmManager)177     public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
178             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
179             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
180             AlarmManager alarmManager) {
181         mScrimBehind = scrimBehind;
182         mScrimInFront = scrimInFront;
183         mScrimStateListener = scrimStateListener;
184         mScrimVisibleListener = scrimVisibleListener;
185         mContext = scrimBehind.getContext();
186         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
187         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
188         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
189         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
190         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
191         mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
192         mHandler = getHandler();
193         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
194                 "hide_aod_wallpaper", mHandler);
195         mWakeLock = createWakeLock();
196         // Scrim alpha is initially set to the value on the resource but might be changed
197         // to make sure that text on top of it is legible.
198         mScrimBehindAlpha = mScrimBehindAlphaResValue;
199         mDozeParameters = dozeParameters;
200 
201         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
202         mColorExtractor.addOnColorsChangedListener(this);
203         mColors = mColorExtractor.getNeutralColors();
204         mNeedsDrawableColorUpdate = true;
205 
206         final ScrimState[] states = ScrimState.values();
207         for (int i = 0; i < states.length; i++) {
208             states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
209             states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
210         }
211 
212         mScrimBehind.setDefaultFocusHighlightEnabled(false);
213         mScrimInFront.setDefaultFocusHighlightEnabled(false);
214 
215         updateScrims();
216     }
217 
transitionTo(ScrimState state)218     public void transitionTo(ScrimState state) {
219         transitionTo(state, null);
220     }
221 
transitionTo(ScrimState state, Callback callback)222     public void transitionTo(ScrimState state, Callback callback) {
223         if (state == mState) {
224             // Call the callback anyway, unless it's already enqueued
225             if (callback != null && mCallback != callback) {
226                 callback.onFinished();
227             }
228             return;
229         } else if (DEBUG) {
230             Log.d(TAG, "State changed to: " + state);
231         }
232 
233         if (state == ScrimState.UNINITIALIZED) {
234             throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
235         }
236 
237         final ScrimState oldState = mState;
238         mState = state;
239         Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.getIndex());
240 
241         if (mCallback != null) {
242             mCallback.onCancelled();
243         }
244         mCallback = callback;
245 
246         state.prepare(oldState);
247         mScreenBlankingCallbackCalled = false;
248         mAnimationDelay = 0;
249         mBlankScreen = state.getBlanksScreen();
250         mAnimateChange = state.getAnimateChange();
251         mAnimationDuration = state.getAnimationDuration();
252         mCurrentInFrontTint = state.getFrontTint();
253         mCurrentBehindTint = state.getBehindTint();
254         mCurrentInFrontAlpha = state.getFrontAlpha();
255         mCurrentBehindAlpha = state.getBehindAlpha();
256         applyExpansionToAlpha();
257 
258         // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
259         // We need to disable focus otherwise AOD would end up with a gray overlay.
260         mScrimInFront.setFocusable(!state.isLowPowerState());
261         mScrimBehind.setFocusable(!state.isLowPowerState());
262 
263         // Cancel blanking transitions that were pending before we requested a new state
264         if (mPendingFrameCallback != null) {
265             mScrimBehind.removeCallbacks(mPendingFrameCallback);
266             mPendingFrameCallback = null;
267         }
268         if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
269             mHandler.removeCallbacks(mBlankingTransitionRunnable);
270             mBlankingTransitionRunnable = null;
271         }
272 
273         // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
274         // to do the same when you're just showing the brightness mirror.
275         mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
276 
277         // The device might sleep if it's entering AOD, we need to make sure that
278         // the animation plays properly until the last frame.
279         // It's important to avoid holding the wakelock unless necessary because
280         // WakeLock#aqcuire will trigger an IPC and will cause jank.
281         if (mState.isLowPowerState()) {
282             holdWakeLock();
283         }
284 
285         // AOD wallpapers should fade away after a while.
286         // Docking pulses may take a long time, wallpapers should also fade away after a while.
287         mWallpaperVisibilityTimedOut = false;
288         if (shouldFadeAwayWallpaper()) {
289             mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
290                     AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
291         } else {
292             mTimeTicker.cancel();
293         }
294 
295         if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
296             // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
297             // with too many things at this case, in order to not skip the initial frames.
298             mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
299             mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
300         } else if ((!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD)
301                 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
302             // Scheduling a frame isn't enough when:
303             //  • Leaving doze and we need to modify scrim color immediately
304             //  • ColorFade will not kick-in and scrim cannot wait for pre-draw.
305             onPreDraw();
306         } else {
307             scheduleUpdate();
308         }
309 
310         dispatchScrimState(mScrimBehind.getViewAlpha());
311     }
312 
shouldFadeAwayWallpaper()313     private boolean shouldFadeAwayWallpaper() {
314         if (!mWallpaperSupportsAmbientMode) {
315             return false;
316         }
317 
318         if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
319             return true;
320         }
321 
322         if (mState == ScrimState.PULSING
323                 && mCallback != null && mCallback.shouldTimeoutWallpaper()) {
324             return true;
325         }
326 
327         return false;
328     }
329 
getState()330     public ScrimState getState() {
331         return mState;
332     }
333 
setScrimBehindValues(float scrimBehindAlphaKeyguard)334     protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) {
335         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
336         ScrimState[] states = ScrimState.values();
337         for (int i = 0; i < states.length; i++) {
338             states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
339         }
340         scheduleUpdate();
341     }
342 
onTrackingStarted()343     public void onTrackingStarted() {
344         mTracking = true;
345         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
346     }
347 
onExpandingFinished()348     public void onExpandingFinished() {
349         mTracking = false;
350     }
351 
352     @VisibleForTesting
onHideWallpaperTimeout()353     protected void onHideWallpaperTimeout() {
354         if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
355             return;
356         }
357 
358         holdWakeLock();
359         mWallpaperVisibilityTimedOut = true;
360         mAnimateChange = true;
361         mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
362         scheduleUpdate();
363     }
364 
holdWakeLock()365     private void holdWakeLock() {
366         if (!mWakeLockHeld) {
367             if (mWakeLock != null) {
368                 mWakeLockHeld = true;
369                 mWakeLock.acquire(TAG);
370             } else {
371                 Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
372             }
373         }
374     }
375 
376     /**
377      * Current state of the shade expansion when pulling it from the top.
378      * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
379      *
380      * The expansion fraction is tied to the scrim opacity.
381      *
382      * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
383      */
setPanelExpansion(float fraction)384     public void setPanelExpansion(float fraction) {
385         if (mExpansionFraction != fraction) {
386             mExpansionFraction = fraction;
387 
388             final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
389                     || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
390             if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
391                 return;
392             }
393 
394             applyExpansionToAlpha();
395 
396             if (mUpdatePending) {
397                 return;
398             }
399 
400             setOrAdaptCurrentAnimation(mScrimBehind);
401             setOrAdaptCurrentAnimation(mScrimInFront);
402 
403             dispatchScrimState(mScrimBehind.getViewAlpha());
404 
405             // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
406             // and docking.
407             if (mWallpaperVisibilityTimedOut) {
408                 mWallpaperVisibilityTimedOut = false;
409                 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
410                         AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
411             }
412         }
413     }
414 
setOrAdaptCurrentAnimation(View scrim)415     private void setOrAdaptCurrentAnimation(View scrim) {
416         if (!isAnimating(scrim)) {
417             updateScrimColor(scrim, getCurrentScrimAlpha(scrim), getCurrentScrimTint(scrim));
418         } else {
419             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
420             float alpha = getCurrentScrimAlpha(scrim);
421             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
422             float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
423             float relativeDiff = alpha - previousEndValue;
424             float newStartValue = previousStartValue + relativeDiff;
425             scrim.setTag(TAG_START_ALPHA, newStartValue);
426             scrim.setTag(TAG_END_ALPHA, alpha);
427             previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
428         }
429     }
430 
applyExpansionToAlpha()431     private void applyExpansionToAlpha() {
432         if (!mExpansionAffectsAlpha) {
433             return;
434         }
435 
436         if (mState == ScrimState.UNLOCKED) {
437             // Darken scrim as you pull down the shade when unlocked
438             float behindFraction = getInterpolatedFraction();
439             behindFraction = (float) Math.pow(behindFraction, 0.8f);
440             mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
441             mCurrentInFrontAlpha = 0;
442         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
443             // Either darken of make the scrim transparent when you
444             // pull down the shade
445             float interpolatedFract = getInterpolatedFraction();
446             float alphaBehind = mState.getBehindAlpha();
447             if (mDarkenWhileDragging) {
448                 mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
449                         interpolatedFract);
450                 mCurrentInFrontAlpha = 0;
451             } else {
452                 mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
453                         interpolatedFract);
454                 mCurrentInFrontAlpha = 0;
455             }
456             mCurrentBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
457                     mState.getBehindTint(), interpolatedFract);
458         }
459     }
460 
461     /**
462      * Sets the given drawable as the background of the scrim that shows up behind the
463      * notifications.
464      */
setScrimBehindDrawable(Drawable drawable)465     public void setScrimBehindDrawable(Drawable drawable) {
466         mScrimBehind.setDrawable(drawable);
467     }
468 
469     /**
470      * Sets the front scrim opacity in AOD so it's not as bright.
471      * <p>
472      * Displays usually don't support multiple dimming settings when in low power mode.
473      * The workaround is to modify the front scrim opacity when in AOD, so it's not as
474      * bright when you're at the movies or lying down on bed.
475      * <p>
476      * This value will be lost during transitions and only updated again after the the
477      * device is dozing when the light sensor is on.
478      */
setAodFrontScrimAlpha(float alpha)479     public void setAodFrontScrimAlpha(float alpha) {
480         if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
481                 && mCurrentInFrontAlpha != alpha) {
482             mCurrentInFrontAlpha = alpha;
483             updateScrims();
484         }
485 
486         mState.AOD.setAodFrontScrimAlpha(alpha);
487     }
488 
489     /**
490      * If the lock screen sensor is active.
491      */
setWakeLockScreenSensorActive(boolean active)492     public void setWakeLockScreenSensorActive(boolean active) {
493         for (ScrimState state : ScrimState.values()) {
494             state.setWakeLockScreenSensorActive(active);
495         }
496 
497         if (mState == ScrimState.PULSING) {
498             float newBehindAlpha = mState.getBehindAlpha();
499             if (mCurrentBehindAlpha != newBehindAlpha) {
500                 mCurrentBehindAlpha = newBehindAlpha;
501                 updateScrims();
502             }
503         }
504     }
505 
scheduleUpdate()506     protected void scheduleUpdate() {
507         if (mUpdatePending) return;
508 
509         // Make sure that a frame gets scheduled.
510         mScrimBehind.invalidate();
511         mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
512         mUpdatePending = true;
513     }
514 
updateScrims()515     protected void updateScrims() {
516         // Make sure we have the right gradients and their opacities will satisfy GAR.
517         if (mNeedsDrawableColorUpdate) {
518             mNeedsDrawableColorUpdate = false;
519             // Only animate scrim color if the scrim view is actually visible
520             boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
521             boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
522             mScrimInFront.setColors(mColors, animateScrimInFront);
523             mScrimBehind.setColors(mColors, animateScrimBehind);
524 
525             // Calculate minimum scrim opacity for white or black text.
526             int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
527             int mainColor = mColors.getMainColor();
528             float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor,
529                     4.5f /* minimumContrast */) / 255f;
530             mScrimBehindAlpha = Math.max(mScrimBehindAlphaResValue, minOpacity);
531             dispatchScrimState(mScrimBehind.getViewAlpha());
532         }
533 
534         // We want to override the back scrim opacity for the AOD state
535         // when it's time to fade the wallpaper away.
536         boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
537                 && mWallpaperVisibilityTimedOut;
538         // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
539         boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
540                 && mKeyguardOccluded;
541         if (aodWallpaperTimeout || occludedKeyguard) {
542             mCurrentBehindAlpha = 1;
543         }
544 
545         setScrimInFrontAlpha(mCurrentInFrontAlpha);
546         setScrimBehindAlpha(mCurrentBehindAlpha);
547 
548         dispatchScrimsVisible();
549     }
550 
dispatchScrimState(float alpha)551     private void dispatchScrimState(float alpha) {
552         mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
553     }
554 
dispatchScrimsVisible()555     private void dispatchScrimsVisible() {
556         final int currentScrimVisibility;
557         if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
558             currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
559         } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
560             currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
561         } else {
562             currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
563         }
564 
565         if (mScrimsVisibility != currentScrimVisibility) {
566             mScrimsVisibility = currentScrimVisibility;
567             mScrimVisibleListener.accept(currentScrimVisibility);
568         }
569     }
570 
getInterpolatedFraction()571     private float getInterpolatedFraction() {
572         float frac = mExpansionFraction;
573         // let's start this 20% of the way down the screen
574         frac = frac * 1.2f - 0.2f;
575         if (frac <= 0) {
576             return 0;
577         } else {
578             // woo, special effects
579             return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
580         }
581     }
582 
setScrimBehindAlpha(float alpha)583     private void setScrimBehindAlpha(float alpha) {
584         setScrimAlpha(mScrimBehind, alpha);
585     }
586 
setScrimInFrontAlpha(float alpha)587     private void setScrimInFrontAlpha(float alpha) {
588         setScrimAlpha(mScrimInFront, alpha);
589     }
590 
setScrimAlpha(ScrimView scrim, float alpha)591     private void setScrimAlpha(ScrimView scrim, float alpha) {
592         if (alpha == 0f) {
593             scrim.setClickable(false);
594         } else {
595             // Eat touch events (unless dozing).
596             scrim.setClickable(mState != ScrimState.AOD);
597         }
598         updateScrim(scrim, alpha);
599     }
600 
updateScrimColor(View scrim, float alpha, int tint)601     private void updateScrimColor(View scrim, float alpha, int tint) {
602         alpha = Math.max(0, Math.min(1.0f, alpha));
603         if (scrim instanceof ScrimView) {
604             ScrimView scrimView = (ScrimView) scrim;
605 
606             Trace.traceCounter(Trace.TRACE_TAG_APP,
607                     scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
608                     (int) (alpha * 255));
609 
610             Trace.traceCounter(Trace.TRACE_TAG_APP,
611                     scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
612                     Color.alpha(tint));
613 
614             scrimView.setTint(tint);
615             scrimView.setViewAlpha(alpha);
616         } else {
617             scrim.setAlpha(alpha);
618         }
619         dispatchScrimsVisible();
620     }
621 
startScrimAnimation(final View scrim, float current)622     private void startScrimAnimation(final View scrim, float current) {
623         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
624         final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
625                 Color.TRANSPARENT;
626         anim.addUpdateListener(animation -> {
627             final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA);
628             final float animAmount = (float) animation.getAnimatedValue();
629             final int finalScrimTint = getCurrentScrimTint(scrim);
630             final float finalScrimAlpha = getCurrentScrimAlpha(scrim);
631             float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount);
632             alpha = MathUtils.constrain(alpha, 0f, 1f);
633             int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
634             updateScrimColor(scrim, alpha, tint);
635             dispatchScrimsVisible();
636         });
637         anim.setInterpolator(mInterpolator);
638         anim.setStartDelay(mAnimationDelay);
639         anim.setDuration(mAnimationDuration);
640         anim.addListener(new AnimatorListenerAdapter() {
641             private Callback lastCallback = mCallback;
642 
643             @Override
644             public void onAnimationEnd(Animator animation) {
645                 onFinished(lastCallback);
646 
647                 scrim.setTag(TAG_KEY_ANIM, null);
648                 dispatchScrimsVisible();
649 
650                 if (!mDeferFinishedListener && mOnAnimationFinished != null) {
651                     mOnAnimationFinished.run();
652                     mOnAnimationFinished = null;
653                 }
654             }
655         });
656 
657         // Cache alpha values because we might want to update this animator in the future if
658         // the user expands the panel while the animation is still running.
659         scrim.setTag(TAG_START_ALPHA, current);
660         scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim));
661 
662         scrim.setTag(TAG_KEY_ANIM, anim);
663         anim.start();
664     }
665 
getCurrentScrimAlpha(View scrim)666     private float getCurrentScrimAlpha(View scrim) {
667         if (scrim == mScrimInFront) {
668             return mCurrentInFrontAlpha;
669         } else if (scrim == mScrimBehind) {
670             return mCurrentBehindAlpha;
671         } else {
672             throw new IllegalArgumentException("Unknown scrim view");
673         }
674     }
675 
getCurrentScrimTint(View scrim)676     private int getCurrentScrimTint(View scrim) {
677         if (scrim == mScrimInFront) {
678             return mCurrentInFrontTint;
679         } else if (scrim == mScrimBehind) {
680             return mCurrentBehindTint;
681         } else {
682             throw new IllegalArgumentException("Unknown scrim view");
683         }
684     }
685 
686     @Override
onPreDraw()687     public boolean onPreDraw() {
688         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
689         mUpdatePending = false;
690         if (mCallback != null) {
691             mCallback.onStart();
692         }
693         updateScrims();
694         if (mOnAnimationFinished != null && !isAnimating(mScrimInFront)
695                 && !isAnimating(mScrimBehind)) {
696             mOnAnimationFinished.run();
697             mOnAnimationFinished = null;
698         }
699         return true;
700     }
701 
onFinished()702     private void onFinished() {
703         onFinished(mCallback);
704     }
705 
onFinished(Callback callback)706     private void onFinished(Callback callback) {
707         if (mWakeLockHeld) {
708             mWakeLock.release(TAG);
709             mWakeLockHeld = false;
710         }
711 
712         if (callback != null) {
713             callback.onFinished();
714 
715             if (callback == mCallback) {
716                 mCallback = null;
717             }
718         }
719 
720         // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
721         // At the end of the animation we need to remove the tint.
722         if (mState == ScrimState.UNLOCKED) {
723             mCurrentInFrontTint = Color.TRANSPARENT;
724             mCurrentBehindTint = Color.TRANSPARENT;
725         }
726     }
727 
isAnimating(View scrim)728     private boolean isAnimating(View scrim) {
729         return scrim.getTag(TAG_KEY_ANIM) != null;
730     }
731 
732     @VisibleForTesting
setOnAnimationFinished(Runnable onAnimationFinished)733     void setOnAnimationFinished(Runnable onAnimationFinished) {
734         mOnAnimationFinished = onAnimationFinished;
735     }
736 
updateScrim(ScrimView scrim, float alpha)737     private void updateScrim(ScrimView scrim, float alpha) {
738         final float currentAlpha = scrim.getViewAlpha();
739 
740         ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
741         if (previousAnimator != null) {
742             if (mAnimateChange) {
743                 // We are not done yet! Defer calling the finished listener.
744                 mDeferFinishedListener = true;
745             }
746             // Previous animators should always be cancelled. Not doing so would cause
747             // overlap, especially on states that don't animate, leading to flickering,
748             // and in the worst case, an internal state that doesn't represent what
749             // transitionTo requested.
750             cancelAnimator(previousAnimator);
751             mDeferFinishedListener = false;
752         }
753 
754         if (mPendingFrameCallback != null) {
755             // Display is off and we're waiting.
756             return;
757         } else if (mBlankScreen) {
758             // Need to blank the display before continuing.
759             blankDisplay();
760             return;
761         } else if (!mScreenBlankingCallbackCalled) {
762             // Not blanking the screen. Letting the callback know that we're ready
763             // to replace what was on the screen before.
764             if (mCallback != null) {
765                 mCallback.onDisplayBlanked();
766                 mScreenBlankingCallbackCalled = true;
767             }
768         }
769 
770         if (scrim == mScrimBehind) {
771             dispatchScrimState(alpha);
772         }
773 
774         final boolean wantsAlphaUpdate = alpha != currentAlpha;
775         final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim);
776 
777         if (wantsAlphaUpdate || wantsTintUpdate) {
778             if (mAnimateChange) {
779                 startScrimAnimation(scrim, currentAlpha);
780             } else {
781                 // update the alpha directly
782                 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
783                 onFinished();
784             }
785         } else {
786             onFinished();
787         }
788     }
789 
790     @VisibleForTesting
cancelAnimator(ValueAnimator previousAnimator)791     protected void cancelAnimator(ValueAnimator previousAnimator) {
792         if (previousAnimator != null) {
793             previousAnimator.cancel();
794         }
795     }
796 
blankDisplay()797     private void blankDisplay() {
798         updateScrimColor(mScrimInFront, 1, Color.BLACK);
799 
800         // Notify callback that the screen is completely black and we're
801         // ready to change the display power mode
802         mPendingFrameCallback = () -> {
803             if (mCallback != null) {
804                 mCallback.onDisplayBlanked();
805                 mScreenBlankingCallbackCalled = true;
806             }
807 
808             mBlankingTransitionRunnable = () -> {
809                 mBlankingTransitionRunnable = null;
810                 mPendingFrameCallback = null;
811                 mBlankScreen = false;
812                 // Try again.
813                 updateScrims();
814             };
815 
816             // Setting power states can happen after we push out the frame. Make sure we
817             // stay fully opaque until the power state request reaches the lower levels.
818             final int delay = mScreenOn ? 32 : 500;
819             if (DEBUG) {
820                 Log.d(TAG, "Fading out scrims with delay: " + delay);
821             }
822             mHandler.postDelayed(mBlankingTransitionRunnable, delay);
823         };
824         doOnTheNextFrame(mPendingFrameCallback);
825     }
826 
827     /**
828      * Executes a callback after the frame has hit the display.
829      * @param callback What to run.
830      */
831     @VisibleForTesting
doOnTheNextFrame(Runnable callback)832     protected void doOnTheNextFrame(Runnable callback) {
833         // Just calling View#postOnAnimation isn't enough because the frame might not have reached
834         // the display yet. A timeout is the safest solution.
835         mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
836     }
837 
838     @VisibleForTesting
getHandler()839     protected Handler getHandler() {
840         return new Handler();
841     }
842 
getBackgroundColor()843     public int getBackgroundColor() {
844         int color = mColors.getMainColor();
845         return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
846                 Color.red(color), Color.green(color), Color.blue(color));
847     }
848 
setScrimBehindChangeRunnable(Runnable changeRunnable)849     public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
850         mScrimBehind.setChangeRunnable(changeRunnable);
851     }
852 
setCurrentUser(int currentUser)853     public void setCurrentUser(int currentUser) {
854         // Don't care in the base class.
855     }
856 
857     @Override
onColorsChanged(ColorExtractor colorExtractor, int which)858     public void onColorsChanged(ColorExtractor colorExtractor, int which) {
859         mColors = mColorExtractor.getNeutralColors();
860         mNeedsDrawableColorUpdate = true;
861         scheduleUpdate();
862     }
863 
864     @VisibleForTesting
createWakeLock()865     protected WakeLock createWakeLock() {
866         return new DelayedWakeLock(mHandler, WakeLock.createPartial(mContext, "Scrims"));
867     }
868 
869     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)870     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
871         pw.println(" ScrimController: ");
872         pw.print("  state: "); pw.println(mState);
873         pw.print("  frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
874         pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
875         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
876 
877         pw.print("  backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
878         pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
879         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
880 
881         pw.print("   mTracking="); pw.println(mTracking);
882     }
883 
setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode)884     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
885         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
886         ScrimState[] states = ScrimState.values();
887         for (int i = 0; i < states.length; i++) {
888             states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
889         }
890     }
891 
892     /**
893      * Interrupts blanking transitions once the display notifies that it's already on.
894      */
onScreenTurnedOn()895     public void onScreenTurnedOn() {
896         mScreenOn = true;
897         if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
898             if (DEBUG) {
899                 Log.d(TAG, "Shorter blanking because screen turned on. All good.");
900             }
901             mHandler.removeCallbacks(mBlankingTransitionRunnable);
902             mBlankingTransitionRunnable.run();
903         }
904     }
905 
onScreenTurnedOff()906     public void onScreenTurnedOff() {
907         mScreenOn = false;
908     }
909 
setExpansionAffectsAlpha(boolean expansionAffectsAlpha)910     public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
911         mExpansionAffectsAlpha = expansionAffectsAlpha;
912     }
913 
setKeyguardOccluded(boolean keyguardOccluded)914     public void setKeyguardOccluded(boolean keyguardOccluded) {
915         mKeyguardOccluded = keyguardOccluded;
916         updateScrims();
917     }
918 
setHasBackdrop(boolean hasBackdrop)919     public void setHasBackdrop(boolean hasBackdrop) {
920         for (ScrimState state : ScrimState.values()) {
921             state.setHasBackdrop(hasBackdrop);
922         }
923 
924         // Backdrop event may arrive after state was already applied,
925         // in this case, back-scrim needs to be re-evaluated
926         if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
927             float newBehindAlpha = mState.getBehindAlpha();
928             if (mCurrentBehindAlpha != newBehindAlpha) {
929                 mCurrentBehindAlpha = newBehindAlpha;
930                 updateScrims();
931             }
932         }
933     }
934 
setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)935     public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
936         for (ScrimState state : ScrimState.values()) {
937             state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
938         }
939     }
940 
941     public interface Callback {
onStart()942         default void onStart() {
943         }
onDisplayBlanked()944         default void onDisplayBlanked() {
945         }
onFinished()946         default void onFinished() {
947         }
onCancelled()948         default void onCancelled() {
949         }
950         /** Returns whether to timeout wallpaper or not. */
shouldTimeoutWallpaper()951         default boolean shouldTimeoutWallpaper() {
952             return false;
953         }
954     }
955 
956     /**
957      * Simple keyguard callback that updates scrims when keyguard visibility changes.
958      */
959     private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
960 
961         @Override
onKeyguardVisibilityChanged(boolean showing)962         public void onKeyguardVisibilityChanged(boolean showing) {
963             mNeedsDrawableColorUpdate = true;
964             scheduleUpdate();
965         }
966     }
967 }
968