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