• 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 static java.lang.Float.isNaN;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ValueAnimator;
24 import android.annotation.IntDef;
25 import android.app.AlarmManager;
26 import android.graphics.Color;
27 import android.os.Handler;
28 import android.os.Trace;
29 import android.util.Log;
30 import android.util.MathUtils;
31 import android.util.Pair;
32 import android.view.View;
33 import android.view.ViewTreeObserver;
34 import android.view.animation.DecelerateInterpolator;
35 import android.view.animation.Interpolator;
36 
37 import androidx.annotation.Nullable;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
41 import com.android.internal.graphics.ColorUtils;
42 import com.android.internal.util.function.TriConsumer;
43 import com.android.keyguard.KeyguardUpdateMonitor;
44 import com.android.keyguard.KeyguardUpdateMonitorCallback;
45 import com.android.settingslib.Utils;
46 import com.android.systemui.DejankUtils;
47 import com.android.systemui.Dumpable;
48 import com.android.systemui.R;
49 import com.android.systemui.animation.Interpolators;
50 import com.android.systemui.dagger.SysUISingleton;
51 import com.android.systemui.dagger.qualifiers.Main;
52 import com.android.systemui.dock.DockManager;
53 import com.android.systemui.scrim.ScrimView;
54 import com.android.systemui.statusbar.notification.stack.ViewState;
55 import com.android.systemui.statusbar.policy.ConfigurationController;
56 import com.android.systemui.statusbar.policy.KeyguardStateController;
57 import com.android.systemui.util.AlarmTimeout;
58 import com.android.systemui.util.wakelock.DelayedWakeLock;
59 import com.android.systemui.util.wakelock.WakeLock;
60 
61 import java.io.FileDescriptor;
62 import java.io.PrintWriter;
63 import java.lang.annotation.Retention;
64 import java.lang.annotation.RetentionPolicy;
65 import java.util.concurrent.Executor;
66 import java.util.function.Consumer;
67 
68 import javax.inject.Inject;
69 
70 /**
71  * Controls both the scrim behind the notifications and in front of the notifications (when a
72  * security method gets shown).
73  */
74 @SysUISingleton
75 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
76 
77     static final String TAG = "ScrimController";
78     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
79 
80     /**
81      * General scrim animation duration.
82      */
83     public static final long ANIMATION_DURATION = 220;
84     /**
85      * Longer duration, currently only used when going to AOD.
86      */
87     public static final long ANIMATION_DURATION_LONG = 1000;
88     /**
89      * When both scrims have 0 alpha.
90      */
91     public static final int TRANSPARENT = 0;
92     /**
93      * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
94      */
95     public static final int SEMI_TRANSPARENT = 1;
96     /**
97      * When at least 1 scrim is fully opaque (alpha set to 1.)
98      */
99     public static final int OPAQUE = 2;
100     private boolean mClipsQsScrim;
101 
102     /**
103      * The amount of progress we are currently in if we're transitioning to the full shade.
104      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
105      * shade.
106      */
107     private float mTransitionToFullShadeProgress;
108 
109     /**
110      * If we're currently transitioning to the full shade.
111      */
112     private boolean mTransitioningToFullShade;
113 
114     @IntDef(prefix = {"VISIBILITY_"}, value = {
115             TRANSPARENT,
116             SEMI_TRANSPARENT,
117             OPAQUE
118     })
119     @Retention(RetentionPolicy.SOURCE)
120     public @interface ScrimVisibility {
121     }
122 
123     /**
124      * Default alpha value for most scrims.
125      */
126     protected static final float KEYGUARD_SCRIM_ALPHA = 0.2f;
127     /**
128      * Scrim opacity when the phone is about to wake-up.
129      */
130     public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f;
131 
132     /**
133      * The default scrim under the shade and dialogs.
134      * This should not be lower than 0.54, otherwise we won't pass GAR.
135      */
136     public static final float BUSY_SCRIM_ALPHA = 1f;
137 
138     /**
139      * The default scrim under the expanded bubble stack.
140      * This should not be lower than 0.54, otherwise we won't pass GAR.
141      */
142     public static final float BUBBLE_SCRIM_ALPHA = 0.6f;
143 
144     /**
145      * Scrim opacity that can have text on top.
146      */
147     public static final float GAR_SCRIM_ALPHA = 0.6f;
148 
149     static final int TAG_KEY_ANIM = R.id.scrim;
150     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
151     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
152     private static final float NOT_INITIALIZED = -1;
153 
154     private ScrimState mState = ScrimState.UNINITIALIZED;
155 
156     private ScrimView mScrimInFront;
157     private ScrimView mNotificationsScrim;
158     private ScrimView mScrimBehind;
159     @Nullable
160     private ScrimView mScrimForBubble;
161 
162     private Runnable mScrimBehindChangeRunnable;
163 
164     private final KeyguardStateController mKeyguardStateController;
165     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
166     private final DozeParameters mDozeParameters;
167     private final DockManager mDockManager;
168     private final AlarmTimeout mTimeTicker;
169     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
170     private final Handler mHandler;
171     private final Executor mMainExecutor;
172     private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
173 
174     private GradientColors mColors;
175     private boolean mNeedsDrawableColorUpdate;
176 
177     private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
178     private final float mDefaultScrimAlpha;
179 
180     // Assuming the shade is expanded during initialization
181     private float mPanelExpansion = 1f;
182     private float mQsExpansion;
183     private boolean mQsBottomVisible;
184 
185     private boolean mDarkenWhileDragging;
186     private boolean mExpansionAffectsAlpha = true;
187     private boolean mAnimateChange;
188     private boolean mUpdatePending;
189     private boolean mTracking;
190     private long mAnimationDuration = -1;
191     private long mAnimationDelay;
192     private Animator.AnimatorListener mAnimatorListener;
193     private final Interpolator mInterpolator = new DecelerateInterpolator();
194 
195     private float mInFrontAlpha = NOT_INITIALIZED;
196     private float mBehindAlpha = NOT_INITIALIZED;
197     private float mNotificationsAlpha = NOT_INITIALIZED;
198     private float mBubbleAlpha = NOT_INITIALIZED;
199 
200     private int mInFrontTint;
201     private int mBehindTint;
202     private int mNotificationsTint;
203     private int mBubbleTint;
204 
205     private boolean mWallpaperVisibilityTimedOut;
206     private int mScrimsVisibility;
207     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
208     private Consumer<Integer> mScrimVisibleListener;
209     private boolean mBlankScreen;
210     private boolean mScreenBlankingCallbackCalled;
211     private Callback mCallback;
212     private boolean mWallpaperSupportsAmbientMode;
213     private boolean mScreenOn;
214 
215     // Scrim blanking callbacks
216     private Runnable mPendingFrameCallback;
217     private Runnable mBlankingTransitionRunnable;
218 
219     private final WakeLock mWakeLock;
220     private boolean mWakeLockHeld;
221     private boolean mKeyguardOccluded;
222 
223     @Inject
ScrimController(LightBarController lightBarController, DozeParameters dozeParameters, AlarmManager alarmManager, KeyguardStateController keyguardStateController, DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, ConfigurationController configurationController, @Main Executor mainExecutor, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController)224     public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters,
225             AlarmManager alarmManager, KeyguardStateController keyguardStateController,
226             DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
227             KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager,
228             ConfigurationController configurationController, @Main Executor mainExecutor,
229             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
230         mScrimStateListener = lightBarController::setScrimState;
231         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
232         ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(BUBBLE_SCRIM_ALPHA);
233 
234         mKeyguardStateController = keyguardStateController;
235         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
236         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
237         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
238         mHandler = handler;
239         mMainExecutor = mainExecutor;
240         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
241         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
242                 "hide_aod_wallpaper", mHandler);
243         mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build();
244         // Scrim alpha is initially set to the value on the resource but might be changed
245         // to make sure that text on top of it is legible.
246         mDozeParameters = dozeParameters;
247         mDockManager = dockManager;
248         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
249             @Override
250             public void onKeyguardFadingAwayChanged() {
251                 setKeyguardFadingAway(keyguardStateController.isKeyguardFadingAway(),
252                         keyguardStateController.getKeyguardFadingAwayDuration());
253             }
254         });
255         configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
256             @Override
257             public void onThemeChanged() {
258                 ScrimController.this.onThemeChanged();
259             }
260 
261             @Override
262             public void onOverlayChanged() {
263                 ScrimController.this.onThemeChanged();
264             }
265 
266             @Override
267             public void onUiModeChanged() {
268                 ScrimController.this.onThemeChanged();
269             }
270         });
271 
272         mColors = new GradientColors();
273     }
274 
275     /**
276      * Attach the controller to the supplied views.
277      */
attachViews(ScrimView behindScrim, ScrimView notificationsScrim, ScrimView scrimInFront, @Nullable ScrimView scrimForBubble)278     public void attachViews(ScrimView behindScrim, ScrimView notificationsScrim,
279                             ScrimView scrimInFront, @Nullable ScrimView scrimForBubble) {
280         mNotificationsScrim = notificationsScrim;
281         mScrimBehind = behindScrim;
282         mScrimInFront = scrimInFront;
283         mScrimForBubble = scrimForBubble;
284         updateThemeColors();
285 
286         behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
287         mNotificationsScrim.enableRoundedCorners(true);
288 
289         if (mScrimBehindChangeRunnable != null) {
290             mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
291             mScrimBehindChangeRunnable = null;
292         }
293 
294         final ScrimState[] states = ScrimState.values();
295         for (int i = 0; i < states.length; i++) {
296             states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters,
297                     mDockManager);
298             states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
299             states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
300         }
301 
302         mScrimBehind.setDefaultFocusHighlightEnabled(false);
303         mNotificationsScrim.setDefaultFocusHighlightEnabled(false);
304         mScrimInFront.setDefaultFocusHighlightEnabled(false);
305         if (mScrimForBubble != null) {
306             mScrimForBubble.setDefaultFocusHighlightEnabled(false);
307         }
308         updateScrims();
309         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
310     }
311 
312     /**
313      * Sets corner radius of scrims.
314      */
setScrimCornerRadius(int radius)315     public void setScrimCornerRadius(int radius) {
316         if (mScrimBehind == null || mNotificationsScrim == null) {
317             return;
318         }
319         mScrimBehind.setCornerRadius(radius);
320         mNotificationsScrim.setCornerRadius(radius);
321     }
322 
setScrimVisibleListener(Consumer<Integer> listener)323     void setScrimVisibleListener(Consumer<Integer> listener) {
324         mScrimVisibleListener = listener;
325     }
326 
transitionTo(ScrimState state)327     public void transitionTo(ScrimState state) {
328         transitionTo(state, null);
329     }
330 
transitionTo(ScrimState state, Callback callback)331     public void transitionTo(ScrimState state, Callback callback) {
332         if (state == mState) {
333             // Call the callback anyway, unless it's already enqueued
334             if (callback != null && mCallback != callback) {
335                 callback.onFinished();
336             }
337             return;
338         } else if (DEBUG) {
339             Log.d(TAG, "State changed to: " + state);
340         }
341 
342         if (state == ScrimState.UNINITIALIZED) {
343             throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
344         }
345 
346         final ScrimState oldState = mState;
347         mState = state;
348         Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal());
349 
350         if (mCallback != null) {
351             mCallback.onCancelled();
352         }
353         mCallback = callback;
354 
355         state.prepare(oldState);
356         mScreenBlankingCallbackCalled = false;
357         mAnimationDelay = 0;
358         mBlankScreen = state.getBlanksScreen();
359         mAnimateChange = state.getAnimateChange();
360         mAnimationDuration = state.getAnimationDuration();
361 
362         mInFrontTint = state.getFrontTint();
363         mBehindTint = state.getBehindTint();
364         mNotificationsTint = state.getNotifTint();
365         mBubbleTint = state.getBubbleTint();
366 
367         mInFrontAlpha = state.getFrontAlpha();
368         mBehindAlpha = state.getBehindAlpha();
369         mBubbleAlpha = state.getBubbleAlpha();
370         mNotificationsAlpha = state.getNotifAlpha();
371         if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
372             throw new IllegalStateException("Scrim opacity is NaN for state: " + state + ", front: "
373                     + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
374                     + mNotificationsAlpha);
375         }
376         applyStateToAlpha();
377 
378         // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
379         // We need to disable focus otherwise AOD would end up with a gray overlay.
380         mScrimInFront.setFocusable(!state.isLowPowerState());
381         mScrimBehind.setFocusable(!state.isLowPowerState());
382         mNotificationsScrim.setFocusable(!state.isLowPowerState());
383 
384         // Cancel blanking transitions that were pending before we requested a new state
385         if (mPendingFrameCallback != null) {
386             mScrimBehind.removeCallbacks(mPendingFrameCallback);
387             mPendingFrameCallback = null;
388         }
389         if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
390             mHandler.removeCallbacks(mBlankingTransitionRunnable);
391             mBlankingTransitionRunnable = null;
392         }
393 
394         // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
395         // to do the same when you're just showing the brightness mirror.
396         mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
397 
398         // The device might sleep if it's entering AOD, we need to make sure that
399         // the animation plays properly until the last frame.
400         // It's important to avoid holding the wakelock unless necessary because
401         // WakeLock#aqcuire will trigger an IPC and will cause jank.
402         if (mState.isLowPowerState()) {
403             holdWakeLock();
404         }
405 
406         // AOD wallpapers should fade away after a while.
407         // Docking pulses may take a long time, wallpapers should also fade away after a while.
408         mWallpaperVisibilityTimedOut = false;
409         if (shouldFadeAwayWallpaper()) {
410             DejankUtils.postAfterTraversal(() -> {
411                 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
412                         AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
413             });
414         } else {
415             DejankUtils.postAfterTraversal(mTimeTicker::cancel);
416         }
417 
418         if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
419             mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
420             scheduleUpdate();
421         } else if ((oldState == ScrimState.AOD  // leaving doze
422                 && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED))
423                 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
424             // Scheduling a frame isn't enough when:
425             //  • Leaving doze and we need to modify scrim color immediately
426             //  • ColorFade will not kick-in and scrim cannot wait for pre-draw.
427             onPreDraw();
428         } else {
429             // Schedule a frame
430             scheduleUpdate();
431         }
432 
433         dispatchBackScrimState(mScrimBehind.getViewAlpha());
434     }
435 
shouldFadeAwayWallpaper()436     private boolean shouldFadeAwayWallpaper() {
437         if (!mWallpaperSupportsAmbientMode) {
438             return false;
439         }
440 
441         if (mState == ScrimState.AOD
442                 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
443             return true;
444         }
445 
446         return false;
447     }
448 
getState()449     public ScrimState getState() {
450         return mState;
451     }
452 
setScrimBehindValues(float scrimBehindAlphaKeyguard)453     protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) {
454         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
455         ScrimState[] states = ScrimState.values();
456         for (int i = 0; i < states.length; i++) {
457             states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
458         }
459         scheduleUpdate();
460     }
461 
onTrackingStarted()462     public void onTrackingStarted() {
463         mTracking = true;
464         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
465     }
466 
onExpandingFinished()467     public void onExpandingFinished() {
468         mTracking = false;
469     }
470 
471     @VisibleForTesting
onHideWallpaperTimeout()472     protected void onHideWallpaperTimeout() {
473         if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
474             return;
475         }
476 
477         holdWakeLock();
478         mWallpaperVisibilityTimedOut = true;
479         mAnimateChange = true;
480         mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
481         scheduleUpdate();
482     }
483 
holdWakeLock()484     private void holdWakeLock() {
485         if (!mWakeLockHeld) {
486             if (mWakeLock != null) {
487                 mWakeLockHeld = true;
488                 mWakeLock.acquire(TAG);
489             } else {
490                 Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
491             }
492         }
493     }
494 
495     /**
496      * Current state of the shade expansion when pulling it from the top.
497      * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
498      *
499      * The expansion fraction is tied to the scrim opacity.
500      *
501      * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
502      */
setPanelExpansion(float fraction)503     public void setPanelExpansion(float fraction) {
504         if (isNaN(fraction)) {
505             throw new IllegalArgumentException("Fraction should not be NaN");
506         }
507         if (mPanelExpansion != fraction) {
508             mPanelExpansion = fraction;
509 
510             boolean relevantState = (mState == ScrimState.UNLOCKED
511                     || mState == ScrimState.KEYGUARD
512                     || mState == ScrimState.SHADE_LOCKED
513                     || mState == ScrimState.PULSING
514                     || mState == ScrimState.BUBBLE_EXPANDED);
515             if (!(relevantState && mExpansionAffectsAlpha)) {
516                 return;
517             }
518             applyAndDispatchState();
519         }
520     }
521 
522     /**
523      * Set the amount of progress we are currently in if we're transitioning to the full shade.
524      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
525      * shade.
526      */
setTransitionToFullShadeProgress(float progress)527     public void setTransitionToFullShadeProgress(float progress) {
528         if (progress != mTransitionToFullShadeProgress) {
529             mTransitionToFullShadeProgress = progress;
530             setTransitionToFullShade(progress > 0.0f);
531             applyAndDispatchState();
532         }
533     }
534 
535     /**
536      * Set if we're currently transitioning to the full shade
537      */
setTransitionToFullShade(boolean transitioning)538     private void setTransitionToFullShade(boolean transitioning) {
539         if (transitioning != mTransitioningToFullShade) {
540             mTransitioningToFullShade = transitioning;
541             if (transitioning) {
542                 // Let's make sure the shade locked is ready
543                 ScrimState.SHADE_LOCKED.prepare(mState);
544             }
545         }
546     }
547 
548 
549     /**
550      * Set bounds for notifications background, all coordinates are absolute
551      */
setNotificationsBounds(float left, float top, float right, float bottom)552     public void setNotificationsBounds(float left, float top, float right, float bottom) {
553         if (mClipsQsScrim) {
554             // notification scrim's rounded corners are anti-aliased, but clipping of the QS/behind
555             // scrim can't be and it's causing jagged corners. That's why notification scrim needs
556             // to overlap QS scrim by one pixel horizontally (left - 1 and right + 1)
557             // see: b/186644628
558             mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom);
559             mScrimBehind.setBottomEdgePosition((int) top);
560         } else {
561             mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
562         }
563     }
564 
565     /**
566      * Current state of the QuickSettings when pulling it from the top.
567      *
568      * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
569      * @param qsPanelBottomY Absolute Y position of qs panel bottom
570      */
setQsPosition(float expansionFraction, int qsPanelBottomY)571     public void setQsPosition(float expansionFraction, int qsPanelBottomY) {
572         if (isNaN(expansionFraction)) {
573             return;
574         }
575         boolean qsBottomVisible = qsPanelBottomY > 0;
576         if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
577             mQsExpansion = expansionFraction;
578             mQsBottomVisible = qsBottomVisible;
579             boolean relevantState = (mState == ScrimState.SHADE_LOCKED
580                     || mState == ScrimState.KEYGUARD
581                     || mState == ScrimState.PULSING
582                     || mState == ScrimState.BUBBLE_EXPANDED);
583             if (!(relevantState && mExpansionAffectsAlpha)) {
584                 return;
585             }
586             applyAndDispatchState();
587         }
588     }
589 
590     /**
591      * If QS and notification scrims should not overlap, and should be clipped to each other's
592      * bounds instead.
593      */
setClipsQsScrim(boolean clipScrim)594     public void setClipsQsScrim(boolean clipScrim) {
595         if (clipScrim == mClipsQsScrim) {
596             return;
597         }
598         mClipsQsScrim = clipScrim;
599         for (ScrimState state : ScrimState.values()) {
600             state.setClipQsScrim(mClipsQsScrim);
601         }
602         if (mScrimBehind != null) {
603             mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim);
604         }
605         if (mState != ScrimState.UNINITIALIZED) {
606             // the clipScrimState has changed, let's reprepare ourselves
607             mState.prepare(mState);
608             applyAndDispatchState();
609         }
610     }
611 
612     @VisibleForTesting
getClipQsScrim()613     public boolean getClipQsScrim() {
614         return mClipsQsScrim;
615     }
616 
setOrAdaptCurrentAnimation(@ullable View scrim)617     private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
618         if (scrim == null) {
619             return;
620         }
621 
622         float alpha = getCurrentScrimAlpha(scrim);
623         boolean qsScrimPullingDown = scrim == mScrimBehind && mQsBottomVisible;
624         if (isAnimating(scrim) && !qsScrimPullingDown) {
625             // Adapt current animation.
626             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
627             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
628             float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
629             float relativeDiff = alpha - previousEndValue;
630             float newStartValue = previousStartValue + relativeDiff;
631             scrim.setTag(TAG_START_ALPHA, newStartValue);
632             scrim.setTag(TAG_END_ALPHA, alpha);
633             previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
634         } else {
635             // Set animation.
636             updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
637         }
638     }
639 
applyStateToAlpha()640     private void applyStateToAlpha() {
641         if (!mExpansionAffectsAlpha) {
642             return;
643         }
644 
645         if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
646             // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
647             // because we're doing the screen off animation.
648             if (!mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
649                 float behindFraction = getInterpolatedFraction();
650                 behindFraction = (float) Math.pow(behindFraction, 0.8f);
651                 if (mClipsQsScrim) {
652                     mBehindAlpha = 1;
653                     mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
654                 } else {
655                     mBehindAlpha = behindFraction * mDefaultScrimAlpha;
656                     mNotificationsAlpha = mBehindAlpha;
657                 }
658                 mInFrontAlpha = 0;
659             }
660         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
661                 || mState == ScrimState.PULSING) {
662             Pair<Integer, Float> result = calculateBackStateForState(mState);
663             int behindTint = result.first;
664             float behindAlpha = result.second;
665             if (mTransitionToFullShadeProgress > 0.0f) {
666                 Pair<Integer, Float> shadeResult = calculateBackStateForState(
667                         ScrimState.SHADE_LOCKED);
668                 behindAlpha = MathUtils.lerp(behindAlpha, shadeResult.second,
669                         mTransitionToFullShadeProgress);
670                 behindTint = ColorUtils.blendARGB(behindTint, shadeResult.first,
671                         mTransitionToFullShadeProgress);
672             }
673             mInFrontAlpha = mState.getFrontAlpha();
674             if (mClipsQsScrim) {
675                 mNotificationsAlpha = behindAlpha;
676                 mNotificationsTint = behindTint;
677                 mBehindAlpha = 1;
678                 mBehindTint = Color.BLACK;
679             } else {
680                 mBehindAlpha = behindAlpha;
681                 if (mState == ScrimState.SHADE_LOCKED) {
682                     // going from KEYGUARD to SHADE_LOCKED state
683                     mNotificationsAlpha = getInterpolatedFraction();
684                 } else {
685                     mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion);
686                 }
687                 if (mState == ScrimState.KEYGUARD && mTransitionToFullShadeProgress > 0.0f) {
688                     // Interpolate the notification alpha when transitioning!
689                     mNotificationsAlpha = MathUtils.lerp(
690                             mNotificationsAlpha,
691                             getInterpolatedFraction(),
692                             mTransitionToFullShadeProgress);
693                 }
694                 mNotificationsTint = mState.getNotifTint();
695                 mBehindTint = behindTint;
696             }
697         }
698         if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
699             throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
700                     + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
701                     + mNotificationsAlpha);
702         }
703     }
704 
calculateBackStateForState(ScrimState state)705     private Pair<Integer, Float> calculateBackStateForState(ScrimState state) {
706         // Either darken of make the scrim transparent when you
707         // pull down the shade
708         float interpolatedFract = getInterpolatedFraction();
709         float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha();
710         float behindAlpha;
711         int behindTint;
712         if (mDarkenWhileDragging) {
713             behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
714                     interpolatedFract);
715         } else {
716             behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
717                     interpolatedFract);
718         }
719         if (mClipsQsScrim) {
720             behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
721                     state.getNotifTint(), interpolatedFract);
722         } else {
723             behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
724                     state.getBehindTint(), interpolatedFract);
725         }
726         if (mQsExpansion > 0) {
727             behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
728             int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint()
729                     : ScrimState.SHADE_LOCKED.getBehindTint();
730             behindTint = ColorUtils.blendARGB(behindTint, stateTint, mQsExpansion);
731         }
732         return new Pair<>(behindTint, behindAlpha);
733     }
734 
735 
applyAndDispatchState()736     private void applyAndDispatchState() {
737         applyStateToAlpha();
738         if (mUpdatePending) {
739             return;
740         }
741         setOrAdaptCurrentAnimation(mScrimBehind);
742         setOrAdaptCurrentAnimation(mNotificationsScrim);
743         setOrAdaptCurrentAnimation(mScrimInFront);
744         setOrAdaptCurrentAnimation(mScrimForBubble);
745         dispatchBackScrimState(mScrimBehind.getViewAlpha());
746 
747         // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
748         // and docking.
749         if (mWallpaperVisibilityTimedOut) {
750             mWallpaperVisibilityTimedOut = false;
751             DejankUtils.postAfterTraversal(() -> {
752                 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
753                         AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
754             });
755         }
756     }
757 
758     /**
759      * Sets the front scrim opacity in AOD so it's not as bright.
760      * <p>
761      * Displays usually don't support multiple dimming settings when in low power mode.
762      * The workaround is to modify the front scrim opacity when in AOD, so it's not as
763      * bright when you're at the movies or lying down on bed.
764      * <p>
765      * This value will be lost during transitions and only updated again after the the
766      * device is dozing when the light sensor is on.
767      */
setAodFrontScrimAlpha(float alpha)768     public void setAodFrontScrimAlpha(float alpha) {
769         if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) {
770             mInFrontAlpha = alpha;
771             updateScrims();
772         }
773 
774         mState.AOD.setAodFrontScrimAlpha(alpha);
775         mState.PULSING.setAodFrontScrimAlpha(alpha);
776     }
777 
shouldUpdateFrontScrimAlpha()778     private boolean shouldUpdateFrontScrimAlpha() {
779         if (mState == ScrimState.AOD
780                 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
781             return true;
782         }
783 
784         if (mState == ScrimState.PULSING) {
785             return true;
786         }
787 
788         return false;
789     }
790 
791     /**
792      * If the lock screen sensor is active.
793      */
setWakeLockScreenSensorActive(boolean active)794     public void setWakeLockScreenSensorActive(boolean active) {
795         for (ScrimState state : ScrimState.values()) {
796             state.setWakeLockScreenSensorActive(active);
797         }
798 
799         if (mState == ScrimState.PULSING) {
800             float newBehindAlpha = mState.getBehindAlpha();
801             if (mBehindAlpha != newBehindAlpha) {
802                 mBehindAlpha = newBehindAlpha;
803                 if (isNaN(mBehindAlpha)) {
804                     throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
805                             + ", back: " + mBehindAlpha);
806                 }
807                 updateScrims();
808             }
809         }
810     }
811 
scheduleUpdate()812     protected void scheduleUpdate() {
813         if (mUpdatePending || mScrimBehind == null) return;
814 
815         // Make sure that a frame gets scheduled.
816         mScrimBehind.invalidate();
817         mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
818         mUpdatePending = true;
819     }
820 
updateScrims()821     protected void updateScrims() {
822         // Make sure we have the right gradients and their opacities will satisfy GAR.
823         if (mNeedsDrawableColorUpdate) {
824             mNeedsDrawableColorUpdate = false;
825             // Only animate scrim color if the scrim view is actually visible
826             boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
827             boolean animateBehindScrim = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
828             boolean animateScrimNotifications = mNotificationsScrim.getViewAlpha() != 0
829                     && !mBlankScreen;
830 
831             mScrimInFront.setColors(mColors, animateScrimInFront);
832             mScrimBehind.setColors(mColors, animateBehindScrim);
833             mNotificationsScrim.setColors(mColors, animateScrimNotifications);
834 
835             dispatchBackScrimState(mScrimBehind.getViewAlpha());
836         }
837 
838         // We want to override the back scrim opacity for the AOD state
839         // when it's time to fade the wallpaper away.
840         boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
841                 && mWallpaperVisibilityTimedOut;
842         // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
843         boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
844                 && mKeyguardOccluded;
845         if (aodWallpaperTimeout || occludedKeyguard) {
846             mBehindAlpha = 1;
847         }
848         setScrimAlpha(mScrimInFront, mInFrontAlpha);
849         setScrimAlpha(mScrimBehind, mBehindAlpha);
850         setScrimAlpha(mNotificationsScrim, mNotificationsAlpha);
851 
852         if (mScrimForBubble != null) {
853             boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
854             mScrimForBubble.setColors(mColors, animateScrimForBubble);
855             setScrimAlpha(mScrimForBubble, mBubbleAlpha);
856         }
857         // The animation could have all already finished, let's call onFinished just in case
858         onFinished(mState);
859         dispatchScrimsVisible();
860     }
861 
dispatchBackScrimState(float alpha)862     private void dispatchBackScrimState(float alpha) {
863         // When clipping QS, the notification scrim is the one that feels behind.
864         // mScrimBehind will be drawing black and its opacity will always be 1.
865         if (mClipsQsScrim && mQsBottomVisible) {
866             alpha = mNotificationsAlpha;
867         }
868         mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
869     }
870 
dispatchScrimsVisible()871     private void dispatchScrimsVisible() {
872         final ScrimView backScrim = mClipsQsScrim ? mNotificationsScrim : mScrimBehind;
873         final int currentScrimVisibility;
874         if (mScrimInFront.getViewAlpha() == 1 || backScrim.getViewAlpha() == 1) {
875             currentScrimVisibility = OPAQUE;
876         } else if (mScrimInFront.getViewAlpha() == 0 && backScrim.getViewAlpha() == 0) {
877             currentScrimVisibility = TRANSPARENT;
878         } else {
879             currentScrimVisibility = SEMI_TRANSPARENT;
880         }
881 
882         if (mScrimsVisibility != currentScrimVisibility) {
883             mScrimsVisibility = currentScrimVisibility;
884             mScrimVisibleListener.accept(currentScrimVisibility);
885         }
886     }
887 
getInterpolatedFraction()888     private float getInterpolatedFraction() {
889         return Interpolators.getNotificationScrimAlpha(mPanelExpansion, false /* notification */);
890     }
891 
setScrimAlpha(ScrimView scrim, float alpha)892     private void setScrimAlpha(ScrimView scrim, float alpha) {
893         if (alpha == 0f) {
894             scrim.setClickable(false);
895         } else {
896             // Eat touch events (unless dozing).
897             scrim.setClickable(mState != ScrimState.AOD);
898         }
899         updateScrim(scrim, alpha);
900     }
901 
getScrimName(ScrimView scrim)902     private String getScrimName(ScrimView scrim) {
903         if (scrim == mScrimInFront) {
904             return "front_scrim";
905         } else if (scrim == mScrimBehind) {
906             return "behind_scrim";
907         } else if (scrim == mNotificationsScrim) {
908             return "notifications_scrim";
909         } else if (scrim == mScrimForBubble) {
910             return "bubble_scrim";
911         }
912         return "unknown_scrim";
913     }
914 
updateScrimColor(View scrim, float alpha, int tint)915     private void updateScrimColor(View scrim, float alpha, int tint) {
916         alpha = Math.max(0, Math.min(1.0f, alpha));
917         if (scrim instanceof ScrimView) {
918             ScrimView scrimView = (ScrimView) scrim;
919 
920             Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
921                     (int) (alpha * 255));
922 
923             Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
924                     Color.alpha(tint));
925             scrimView.setTint(tint);
926             scrimView.setViewAlpha(alpha);
927         } else {
928             scrim.setAlpha(alpha);
929         }
930         dispatchScrimsVisible();
931     }
932 
startScrimAnimation(final View scrim, float current)933     private void startScrimAnimation(final View scrim, float current) {
934         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
935         if (mAnimatorListener != null) {
936             anim.addListener(mAnimatorListener);
937         }
938         final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
939                 Color.TRANSPARENT;
940         anim.addUpdateListener(animation -> {
941             final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA);
942             final float animAmount = (float) animation.getAnimatedValue();
943             final int finalScrimTint = getCurrentScrimTint(scrim);
944             final float finalScrimAlpha = getCurrentScrimAlpha(scrim);
945             float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount);
946             alpha = MathUtils.constrain(alpha, 0f, 1f);
947             int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
948             updateScrimColor(scrim, alpha, tint);
949             dispatchScrimsVisible();
950         });
951         anim.setInterpolator(mInterpolator);
952         anim.setStartDelay(mAnimationDelay);
953         anim.setDuration(mAnimationDuration);
954         anim.addListener(new AnimatorListenerAdapter() {
955             private final ScrimState mLastState = mState;
956             private final Callback mLastCallback = mCallback;
957 
958             @Override
959             public void onAnimationEnd(Animator animation) {
960                 scrim.setTag(TAG_KEY_ANIM, null);
961                 onFinished(mLastCallback, mLastState);
962 
963                 dispatchScrimsVisible();
964             }
965         });
966 
967         // Cache alpha values because we might want to update this animator in the future if
968         // the user expands the panel while the animation is still running.
969         scrim.setTag(TAG_START_ALPHA, current);
970         scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim));
971 
972         scrim.setTag(TAG_KEY_ANIM, anim);
973         anim.start();
974     }
975 
getCurrentScrimAlpha(View scrim)976     private float getCurrentScrimAlpha(View scrim) {
977         if (scrim == mScrimInFront) {
978             return mInFrontAlpha;
979         } else if (scrim == mScrimBehind) {
980             return mBehindAlpha;
981         } else if (scrim == mNotificationsScrim) {
982             return mNotificationsAlpha;
983         } else if (scrim == mScrimForBubble) {
984             return mBubbleAlpha;
985         } else {
986             throw new IllegalArgumentException("Unknown scrim view");
987         }
988     }
989 
getCurrentScrimTint(View scrim)990     private int getCurrentScrimTint(View scrim) {
991         if (scrim == mScrimInFront) {
992             return mInFrontTint;
993         } else if (scrim == mScrimBehind) {
994             return mBehindTint;
995         } else if (scrim == mNotificationsScrim) {
996             return mNotificationsTint;
997         } else if (scrim == mScrimForBubble) {
998             return mBubbleTint;
999         } else {
1000             throw new IllegalArgumentException("Unknown scrim view");
1001         }
1002     }
1003 
1004     @Override
onPreDraw()1005     public boolean onPreDraw() {
1006         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
1007         mUpdatePending = false;
1008         if (mCallback != null) {
1009             mCallback.onStart();
1010         }
1011         updateScrims();
1012         return true;
1013     }
1014 
1015     /**
1016      * @param state that finished
1017      */
onFinished(ScrimState state)1018     private void onFinished(ScrimState state) {
1019         onFinished(mCallback, state);
1020     }
1021 
onFinished(Callback callback, ScrimState state)1022     private void onFinished(Callback callback, ScrimState state) {
1023         if (mPendingFrameCallback != null) {
1024             // No animations can finish while we're waiting on the blanking to finish
1025             return;
1026 
1027         }
1028         if (isAnimating(mScrimBehind)
1029                 || isAnimating(mNotificationsScrim)
1030                 || isAnimating(mScrimInFront)
1031                 || isAnimating(mScrimForBubble)) {
1032             if (callback != null && callback != mCallback) {
1033                 // Since we only notify the callback that we're finished once everything has
1034                 // finished, we need to make sure that any changing callbacks are also invoked
1035                 callback.onFinished();
1036             }
1037             return;
1038         }
1039         if (mWakeLockHeld) {
1040             mWakeLock.release(TAG);
1041             mWakeLockHeld = false;
1042         }
1043 
1044         if (callback != null) {
1045             callback.onFinished();
1046 
1047             if (callback == mCallback) {
1048                 mCallback = null;
1049             }
1050         }
1051 
1052         // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
1053         // At the end of the animation we need to remove the tint.
1054         if (state == ScrimState.UNLOCKED) {
1055             mInFrontTint = Color.TRANSPARENT;
1056             mBehindTint = mState.getBehindTint();
1057             mNotificationsTint = mState.getNotifTint();
1058             mBubbleTint = Color.TRANSPARENT;
1059             updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
1060             updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
1061             updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint);
1062             if (mScrimForBubble != null) {
1063                 updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
1064             }
1065         }
1066     }
1067 
isAnimating(@ullable View scrim)1068     private boolean isAnimating(@Nullable View scrim) {
1069         return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null;
1070     }
1071 
1072     @VisibleForTesting
setAnimatorListener(Animator.AnimatorListener animatorListener)1073     void setAnimatorListener(Animator.AnimatorListener animatorListener) {
1074         mAnimatorListener = animatorListener;
1075     }
1076 
updateScrim(ScrimView scrim, float alpha)1077     private void updateScrim(ScrimView scrim, float alpha) {
1078         final float currentAlpha = scrim.getViewAlpha();
1079 
1080         ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
1081         if (previousAnimator != null) {
1082             // Previous animators should always be cancelled. Not doing so would cause
1083             // overlap, especially on states that don't animate, leading to flickering,
1084             // and in the worst case, an internal state that doesn't represent what
1085             // transitionTo requested.
1086             cancelAnimator(previousAnimator);
1087         }
1088 
1089         if (mPendingFrameCallback != null) {
1090             // Display is off and we're waiting.
1091             return;
1092         } else if (mBlankScreen) {
1093             // Need to blank the display before continuing.
1094             blankDisplay();
1095             return;
1096         } else if (!mScreenBlankingCallbackCalled) {
1097             // Not blanking the screen. Letting the callback know that we're ready
1098             // to replace what was on the screen before.
1099             if (mCallback != null) {
1100                 mCallback.onDisplayBlanked();
1101                 mScreenBlankingCallbackCalled = true;
1102             }
1103         }
1104 
1105         if (scrim == mScrimBehind) {
1106             dispatchBackScrimState(alpha);
1107         }
1108 
1109         final boolean wantsAlphaUpdate = alpha != currentAlpha;
1110         final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim);
1111 
1112         if (wantsAlphaUpdate || wantsTintUpdate) {
1113             if (mAnimateChange) {
1114                 startScrimAnimation(scrim, currentAlpha);
1115             } else {
1116                 // update the alpha directly
1117                 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
1118             }
1119         }
1120     }
1121 
cancelAnimator(ValueAnimator previousAnimator)1122     private void cancelAnimator(ValueAnimator previousAnimator) {
1123         if (previousAnimator != null) {
1124             previousAnimator.cancel();
1125         }
1126     }
1127 
blankDisplay()1128     private void blankDisplay() {
1129         updateScrimColor(mScrimInFront, 1, Color.BLACK);
1130 
1131         // Notify callback that the screen is completely black and we're
1132         // ready to change the display power mode
1133         mPendingFrameCallback = () -> {
1134             if (mCallback != null) {
1135                 mCallback.onDisplayBlanked();
1136                 mScreenBlankingCallbackCalled = true;
1137             }
1138 
1139             mBlankingTransitionRunnable = () -> {
1140                 mBlankingTransitionRunnable = null;
1141                 mPendingFrameCallback = null;
1142                 mBlankScreen = false;
1143                 // Try again.
1144                 updateScrims();
1145             };
1146 
1147             // Setting power states can happen after we push out the frame. Make sure we
1148             // stay fully opaque until the power state request reaches the lower levels.
1149             final int delay = mScreenOn ? 32 : 500;
1150             if (DEBUG) {
1151                 Log.d(TAG, "Fading out scrims with delay: " + delay);
1152             }
1153             mHandler.postDelayed(mBlankingTransitionRunnable, delay);
1154         };
1155         doOnTheNextFrame(mPendingFrameCallback);
1156     }
1157 
1158     /**
1159      * Executes a callback after the frame has hit the display.
1160      *
1161      * @param callback What to run.
1162      */
1163     @VisibleForTesting
doOnTheNextFrame(Runnable callback)1164     protected void doOnTheNextFrame(Runnable callback) {
1165         // Just calling View#postOnAnimation isn't enough because the frame might not have reached
1166         // the display yet. A timeout is the safest solution.
1167         mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
1168     }
1169 
setScrimBehindChangeRunnable(Runnable changeRunnable)1170     public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
1171         // TODO: remove this. This is necessary because of an order-of-operations limitation.
1172         // The fix is to move more of these class into @StatusBarScope
1173         if (mScrimBehind == null) {
1174             mScrimBehindChangeRunnable = changeRunnable;
1175         } else {
1176             mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
1177         }
1178     }
1179 
setCurrentUser(int currentUser)1180     public void setCurrentUser(int currentUser) {
1181         // Don't care in the base class.
1182     }
1183 
updateThemeColors()1184     private void updateThemeColors() {
1185         if (mScrimBehind == null) return;
1186         int background = Utils.getColorAttr(mScrimBehind.getContext(),
1187                 android.R.attr.colorBackgroundFloating).getDefaultColor();
1188         int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
1189         mColors.setMainColor(background);
1190         mColors.setSecondaryColor(accent);
1191         mColors.setSupportsDarkText(
1192                 ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
1193         mNeedsDrawableColorUpdate = true;
1194     }
1195 
onThemeChanged()1196     private void onThemeChanged() {
1197         updateThemeColors();
1198         scheduleUpdate();
1199     }
1200 
1201     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1202     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1203         pw.println(" ScrimController: ");
1204         pw.print("  state: ");
1205         pw.println(mState);
1206 
1207         pw.print("  frontScrim:");
1208         pw.print(" viewAlpha=");
1209         pw.print(mScrimInFront.getViewAlpha());
1210         pw.print(" alpha=");
1211         pw.print(mInFrontAlpha);
1212         pw.print(" tint=0x");
1213         pw.println(Integer.toHexString(mScrimInFront.getTint()));
1214 
1215         pw.print("  behindScrim:");
1216         pw.print(" viewAlpha=");
1217         pw.print(mScrimBehind.getViewAlpha());
1218         pw.print(" alpha=");
1219         pw.print(mBehindAlpha);
1220         pw.print(" tint=0x");
1221         pw.println(Integer.toHexString(mScrimBehind.getTint()));
1222 
1223         pw.print("  notificationsScrim:");
1224         pw.print(" viewAlpha=");
1225         pw.print(mNotificationsScrim.getViewAlpha());
1226         pw.print(" alpha=");
1227         pw.print(mNotificationsAlpha);
1228         pw.print(" tint=0x");
1229         pw.println(Integer.toHexString(mNotificationsScrim.getTint()));
1230 
1231         pw.print("  bubbleScrim:");
1232         pw.print(" viewAlpha=");
1233         pw.print(mScrimForBubble.getViewAlpha());
1234         pw.print(" alpha=");
1235         pw.print(mBubbleAlpha);
1236         pw.print(" tint=0x");
1237         pw.println(Integer.toHexString(mScrimForBubble.getTint()));
1238 
1239         pw.print("  mTracking=");
1240         pw.println(mTracking);
1241         pw.print("  mDefaultScrimAlpha=");
1242         pw.println(mDefaultScrimAlpha);
1243         pw.print("  mExpansionFraction=");
1244         pw.println(mPanelExpansion);
1245 
1246         pw.print("  mState.getMaxLightRevealScrimAlpha=");
1247         pw.println(mState.getMaxLightRevealScrimAlpha());
1248     }
1249 
setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode)1250     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
1251         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
1252         ScrimState[] states = ScrimState.values();
1253         for (int i = 0; i < states.length; i++) {
1254             states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
1255         }
1256     }
1257 
1258     /**
1259      * Interrupts blanking transitions once the display notifies that it's already on.
1260      */
onScreenTurnedOn()1261     public void onScreenTurnedOn() {
1262         mScreenOn = true;
1263         if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
1264             if (DEBUG) {
1265                 Log.d(TAG, "Shorter blanking because screen turned on. All good.");
1266             }
1267             mHandler.removeCallbacks(mBlankingTransitionRunnable);
1268             mBlankingTransitionRunnable.run();
1269         }
1270     }
1271 
onScreenTurnedOff()1272     public void onScreenTurnedOff() {
1273         mScreenOn = false;
1274     }
1275 
setExpansionAffectsAlpha(boolean expansionAffectsAlpha)1276     public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
1277         mExpansionAffectsAlpha = expansionAffectsAlpha;
1278         if (expansionAffectsAlpha) {
1279             applyAndDispatchState();
1280         }
1281     }
1282 
setKeyguardOccluded(boolean keyguardOccluded)1283     public void setKeyguardOccluded(boolean keyguardOccluded) {
1284         mKeyguardOccluded = keyguardOccluded;
1285         updateScrims();
1286     }
1287 
setHasBackdrop(boolean hasBackdrop)1288     public void setHasBackdrop(boolean hasBackdrop) {
1289         for (ScrimState state : ScrimState.values()) {
1290             state.setHasBackdrop(hasBackdrop);
1291         }
1292 
1293         // Backdrop event may arrive after state was already applied,
1294         // in this case, back-scrim needs to be re-evaluated
1295         if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
1296             float newBehindAlpha = mState.getBehindAlpha();
1297             if (isNaN(newBehindAlpha)) {
1298                 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
1299                         + ", back: " + mBehindAlpha);
1300             }
1301             if (mBehindAlpha != newBehindAlpha) {
1302                 mBehindAlpha = newBehindAlpha;
1303                 updateScrims();
1304             }
1305         }
1306     }
1307 
setKeyguardFadingAway(boolean fadingAway, long duration)1308     private void setKeyguardFadingAway(boolean fadingAway, long duration) {
1309         for (ScrimState state : ScrimState.values()) {
1310             state.setKeyguardFadingAway(fadingAway, duration);
1311         }
1312     }
1313 
setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)1314     public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
1315         for (ScrimState state : ScrimState.values()) {
1316             state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
1317         }
1318     }
1319 
1320     public interface Callback {
onStart()1321         default void onStart() {
1322         }
1323 
onDisplayBlanked()1324         default void onDisplayBlanked() {
1325         }
1326 
onFinished()1327         default void onFinished() {
1328         }
1329 
onCancelled()1330         default void onCancelled() {
1331         }
1332     }
1333 
1334     /**
1335      * Simple keyguard callback that updates scrims when keyguard visibility changes.
1336      */
1337     private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
1338 
1339         @Override
onKeyguardVisibilityChanged(boolean showing)1340         public void onKeyguardVisibilityChanged(boolean showing) {
1341             mNeedsDrawableColorUpdate = true;
1342             scheduleUpdate();
1343         }
1344     }
1345 }
1346