• 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 com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER;
20 import static com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB;
21 import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
22 import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
23 import static com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER;
24 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
25 
26 import static java.lang.Float.isNaN;
27 
28 import android.animation.Animator;
29 import android.animation.AnimatorListenerAdapter;
30 import android.animation.ValueAnimator;
31 import android.annotation.IntDef;
32 import android.content.Context;
33 import android.graphics.Color;
34 import android.os.Handler;
35 import android.util.Log;
36 import android.util.MathUtils;
37 import android.util.Pair;
38 import android.view.View;
39 import android.view.ViewTreeObserver;
40 import android.view.animation.DecelerateInterpolator;
41 import android.view.animation.Interpolator;
42 
43 import androidx.annotation.FloatRange;
44 import androidx.annotation.Nullable;
45 
46 import com.android.app.tracing.coroutines.TrackTracer;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
49 import com.android.internal.graphics.ColorUtils;
50 import com.android.internal.util.ContrastColorUtil;
51 import com.android.internal.util.function.TriConsumer;
52 import com.android.keyguard.BouncerPanelExpansionCalculator;
53 import com.android.keyguard.KeyguardUpdateMonitor;
54 import com.android.keyguard.KeyguardUpdateMonitorCallback;
55 import com.android.systemui.Dumpable;
56 import com.android.systemui.Flags;
57 import com.android.systemui.animation.ShadeInterpolation;
58 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
59 import com.android.systemui.dagger.SysUISingleton;
60 import com.android.systemui.dagger.qualifiers.Main;
61 import com.android.systemui.dock.DockManager;
62 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
63 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
64 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
65 import com.android.systemui.keyguard.shared.model.Edge;
66 import com.android.systemui.keyguard.shared.model.KeyguardState;
67 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
68 import com.android.systemui.keyguard.shared.model.TransitionState;
69 import com.android.systemui.keyguard.shared.model.TransitionStep;
70 import com.android.systemui.keyguard.ui.transitions.BlurConfig;
71 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
72 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
73 import com.android.systemui.res.R;
74 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
75 import com.android.systemui.scene.shared.model.Scenes;
76 import com.android.systemui.scrim.ScrimView;
77 import com.android.systemui.shade.ShadeViewController;
78 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
79 import com.android.systemui.statusbar.notification.stack.ViewState;
80 import com.android.systemui.statusbar.policy.ConfigurationController;
81 import com.android.systemui.statusbar.policy.KeyguardStateController;
82 import com.android.systemui.util.kotlin.JavaAdapter;
83 import com.android.systemui.util.wakelock.DelayedWakeLock;
84 import com.android.systemui.util.wakelock.WakeLock;
85 import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor;
86 
87 import dagger.Lazy;
88 
89 import kotlinx.coroutines.CoroutineDispatcher;
90 
91 import java.io.PrintWriter;
92 import java.lang.annotation.Retention;
93 import java.lang.annotation.RetentionPolicy;
94 import java.util.concurrent.Executor;
95 import java.util.function.Consumer;
96 import java.util.function.Supplier;
97 
98 import javax.inject.Inject;
99 
100 /**
101  * Controls both the scrim behind the notifications and in front of the notifications (when a
102  * security method gets shown).
103  */
104 @SysUISingleton
105 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
106 
107     static final String TAG = "ScrimController";
108     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
109 
110     // debug mode colors scrims with below debug colors, irrespectively of which state they're in
111     public static final boolean DEBUG_MODE = false;
112 
113     public static final int DEBUG_NOTIFICATIONS_TINT = Color.RED;
114     public static final int DEBUG_FRONT_TINT = Color.GREEN;
115     public static final int DEBUG_BEHIND_TINT = Color.BLUE;
116 
117     /**
118      * General scrim animation duration.
119      */
120     public static final long ANIMATION_DURATION = 220;
121     /**
122      * Longer duration, currently only used when going to AOD.
123      */
124     public static final long ANIMATION_DURATION_LONG = 1000;
125     /**
126      * When both scrims have 0 alpha.
127      */
128     public static final int TRANSPARENT = 0;
129     /**
130      * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
131      */
132     public static final int SEMI_TRANSPARENT = 1;
133     /**
134      * When at least 1 scrim is fully opaque (alpha set to 1.)
135      */
136     public static final int OPAQUE = 2;
137     private boolean mClipsQsScrim;
138 
139     /**
140      * Whether an activity is launching over the lockscreen. During the launch animation, we want to
141      * delay certain scrim changes until after the animation ends.
142      */
143     private boolean mOccludeAnimationPlaying = false;
144 
145     /**
146      * The amount of progress we are currently in if we're transitioning to the full shade.
147      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
148      * shade.
149      */
150     private float mTransitionToFullShadeProgress;
151 
152     /**
153      * Same as {@link #mTransitionToFullShadeProgress}, but specifically for the notifications scrim
154      * on the lock screen.
155      *
156      * On split shade lock screen we want the different scrims to fade in at different times and
157      * rates.
158      */
159     private float mTransitionToLockScreenFullShadeNotificationsProgress;
160 
161     /**
162      * If we're currently transitioning to the full shade.
163      */
164     private boolean mTransitioningToFullShade;
165 
166     /**
167      * The percentage of the bouncer which is hidden. If 1, the bouncer is completely hidden. If
168      * 0, the bouncer is visible.
169      */
170     @FloatRange(from = 0, to = 1)
171     private float mBouncerHiddenFraction = KeyguardBouncerConstants.EXPANSION_HIDDEN;
172     private boolean mIsBlurSupported = false;
173 
getDefaultScrimAlpha(boolean ignoreCurrentState)174     private float getDefaultScrimAlpha(boolean ignoreCurrentState) {
175         if (Flags.bouncerUiRevamp() && mIsBlurSupported) {
176             // Hack to not make the shade transparent when shade blur is not enabled.
177             if (!Flags.notificationShadeBlur() && !ignoreCurrentState) {
178                 // When we expand directly to full quick settings, shade state is KEYGUARD
179                 if (mState == ScrimState.SHADE_LOCKED || (mState == ScrimState.KEYGUARD
180                         && mQsExpansion == 1)) {
181                     return BUSY_SCRIM_ALPHA;
182                 }
183             }
184             return TRANSPARENT_BOUNCER_SCRIM_ALPHA;
185         } else {
186             return BUSY_SCRIM_ALPHA;
187         }
188     }
189 
getDefaultScrimAlpha()190     private float getDefaultScrimAlpha() {
191         return getDefaultScrimAlpha(false);
192     }
193 
194     @IntDef(prefix = {"VISIBILITY_"}, value = {
195             TRANSPARENT,
196             SEMI_TRANSPARENT,
197             OPAQUE
198     })
199     @Retention(RetentionPolicy.SOURCE)
200     public @interface ScrimVisibility {
201     }
202 
203     /**
204      * Default alpha value for most scrims.
205      */
206     protected static final float KEYGUARD_SCRIM_ALPHA = 0.2f;
207     /**
208      * Scrim opacity when the phone is about to wake-up.
209      */
210     public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f;
211 
212     /**
213      * The default scrim under the shade and dialogs.
214      * This should not be lower than 0.54, otherwise we won't pass GAR.
215      */
216     public static final float BUSY_SCRIM_ALPHA = 1f;
217 
218     /**
219      * Scrim opacity that can have text on top.
220      */
221     public static final float GAR_SCRIM_ALPHA = 0.6f;
222 
223     static final int TAG_KEY_ANIM = R.id.scrim;
224     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
225     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
226     private static final float NOT_INITIALIZED = -1;
227 
228     private ScrimState mState = ScrimState.UNINITIALIZED;
229 
230     private Context mContext;
231 
232     private ScrimView mScrimInFront;
233     private ScrimView mNotificationsScrim;
234     private ScrimView mScrimBehind;
235 
236     private final KeyguardStateController mKeyguardStateController;
237     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
238     private final DozeParameters mDozeParameters;
239     private final DockManager mDockManager;
240     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
241     private final Handler mHandler;
242     private final Executor mMainExecutor;
243     private final JavaAdapter mJavaAdapter;
244     private final ScreenOffAnimationController mScreenOffAnimationController;
245     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
246     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
247     private final KeyguardInteractor mKeyguardInteractor;
248 
249     private GradientColors mColors;
250     private boolean mNeedsDrawableColorUpdate;
251 
252     private float mAdditionalScrimBehindAlphaKeyguard = 0f;
253     // Combined scrim behind keyguard alpha of default scrim + additional scrim
254     private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
255 
256     static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
257 
258     private float mRawPanelExpansionFraction;
259     private float mPanelScrimMinFraction;
260     // Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction
261     private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization
262     private float mQsExpansion;
263     private boolean mQsBottomVisible;
264     private boolean mAnimatingPanelExpansionOnUnlock; // don't animate scrim
265 
266     private boolean mDarkenWhileDragging;
267     private boolean mExpansionAffectsAlpha = true;
268     private boolean mAnimateChange;
269     private boolean mUpdatePending;
270     private long mAnimationDuration = -1;
271     private long mAnimationDelay;
272     private Animator.AnimatorListener mAnimatorListener;
273     private final Interpolator mInterpolator = new DecelerateInterpolator();
274 
275     private float mInFrontAlpha = NOT_INITIALIZED;
276     private float mBehindAlpha = NOT_INITIALIZED;
277     private float mNotificationsAlpha = NOT_INITIALIZED;
278 
279     private int mInFrontTint;
280     private int mBehindTint;
281     private int mNotificationsTint;
282 
283     private int mScrimsVisibility;
284     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
285     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
286     private final BlurConfig mBlurConfig;
287     private final Lazy<WindowRootViewBlurInteractor> mWindowRootViewBlurInteractor;
288     private Consumer<Integer> mScrimVisibleListener;
289     private boolean mBlankScreen;
290     private boolean mScreenBlankingCallbackCalled;
291     private Callback mCallback;
292     private boolean mScreenOn;
293     private boolean mTransparentScrimBackground;
294 
295     // Scrim blanking callbacks
296     private Runnable mPendingFrameCallback;
297     private Runnable mBlankingTransitionRunnable;
298 
299     private final WakeLock mWakeLock;
300     private boolean mWakeLockHeld;
301     private boolean mKeyguardOccluded;
302 
303     private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
304     private CoroutineDispatcher mMainDispatcher;
305     private boolean mIsBouncerToGoneTransitionRunning = false;
306     private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
307     private AlternateBouncerToGoneTransitionViewModel mAlternateBouncerToGoneTransitionViewModel;
308     private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
309             (ScrimAlpha alphas) -> {
310                 mInFrontAlpha = alphas.getFrontAlpha();
311                 mScrimInFront.setViewAlpha(mInFrontAlpha);
312 
313                 mNotificationsAlpha = alphas.getNotificationsAlpha();
314                 mNotificationsScrim.setViewAlpha(mNotificationsAlpha);
315 
316                 mBehindAlpha = alphas.getBehindAlpha();
317                 mScrimBehind.setViewAlpha(mBehindAlpha);
318             };
319 
320     /**
321      * Consumer that fades the behind scrim in and out during the transition between the lock screen
322      * and the glanceable hub.
323      *
324      * While the lock screen is showing, the behind scrim is used to slightly darken the lock screen
325      * wallpaper underneath. Since the glanceable hub is under all of the scrims, we want to fade
326      * out the scrim so that the glanceable hub isn't darkened when it opens.
327      *
328      * {@link #applyState()} handles the scrim alphas once on the glanceable hub, this is only
329      * responsible for setting the behind alpha during the transition.
330      */
331     private final Consumer<TransitionStep> mGlanceableHubConsumer = (TransitionStep step) -> {
332         final float baseAlpha = ScrimState.KEYGUARD.getBehindAlpha();
333         final float transitionProgress = step.getValue();
334         if (step.getTo() == KeyguardState.LOCKSCREEN) {
335             // Transitioning back to lock screen, fade in behind scrim again.
336             mBehindAlpha = baseAlpha * transitionProgress;
337         } else if (step.getTo() == GLANCEABLE_HUB) {
338             // Transitioning to glanceable hub, fade out behind scrim.
339             mBehindAlpha = baseAlpha * (1 - transitionProgress);
340         }
341         mScrimBehind.setViewAlpha(mBehindAlpha);
342     };
343 
344     @VisibleForTesting
345     Consumer<TransitionStep> mBouncerToGoneTransition;
346 
347     private boolean mViewsAttached;
348 
349     @Inject
ScrimController( LightBarController lightBarController, DozeParameters dozeParameters, KeyguardStateController keyguardStateController, DelayedWakeLock.Factory delayedWakeLockFactory, @Main Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, ConfigurationController configurationController, @Main Executor mainExecutor, JavaAdapter javaAdapter, ScreenOffAnimationController screenOffAnimationController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel, KeyguardTransitionInteractor keyguardTransitionInteractor, KeyguardInteractor keyguardInteractor, @Main CoroutineDispatcher mainDispatcher, LargeScreenShadeInterpolator largeScreenShadeInterpolator, BlurConfig blurConfig, @Main Context context, Lazy<WindowRootViewBlurInteractor> windowRootViewBlurInteractor)350     public ScrimController(
351             LightBarController lightBarController,
352             DozeParameters dozeParameters,
353             KeyguardStateController keyguardStateController,
354             DelayedWakeLock.Factory delayedWakeLockFactory,
355             @Main Handler handler,
356             KeyguardUpdateMonitor keyguardUpdateMonitor,
357             DockManager dockManager,
358             ConfigurationController configurationController,
359             @Main Executor mainExecutor,
360             JavaAdapter javaAdapter,
361             ScreenOffAnimationController screenOffAnimationController,
362             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
363             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
364             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
365             AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
366             KeyguardTransitionInteractor keyguardTransitionInteractor,
367             KeyguardInteractor keyguardInteractor,
368             @Main CoroutineDispatcher mainDispatcher,
369             LargeScreenShadeInterpolator largeScreenShadeInterpolator,
370             BlurConfig blurConfig,
371             @Main Context context,
372             Lazy<WindowRootViewBlurInteractor> windowRootViewBlurInteractor) {
373         mContext = context;
374         mScrimStateListener = lightBarController::setScrimState;
375         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
376         mBlurConfig = blurConfig;
377         mWindowRootViewBlurInteractor = windowRootViewBlurInteractor;
378 
379         mKeyguardStateController = keyguardStateController;
380         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
381         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
382         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
383         mHandler = handler;
384         mMainExecutor = mainExecutor;
385         mJavaAdapter = javaAdapter;
386         mScreenOffAnimationController = screenOffAnimationController;
387         mWakeLock = delayedWakeLockFactory.create("Scrims");
388         // Scrim alpha is initially set to the value on the resource but might be changed
389         // to make sure that text on top of it is legible.
390         mDozeParameters = dozeParameters;
391         mDockManager = dockManager;
392         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
393         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
394             @Override
395             public void onKeyguardFadingAwayChanged() {
396                 setKeyguardFadingAway(keyguardStateController.isKeyguardFadingAway(),
397                         keyguardStateController.getKeyguardFadingAwayDuration());
398             }
399         });
400         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
401         configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
402             @Override
403             public void onThemeChanged() {
404                 ScrimController.this.onThemeChanged();
405             }
406 
407             @Override
408             public void onUiModeChanged() {
409                 ScrimController.this.onThemeChanged();
410             }
411         });
412         mColors = new GradientColors();
413         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
414         mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
415         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
416         mKeyguardInteractor = keyguardInteractor;
417         mMainDispatcher = mainDispatcher;
418     }
419 
420     /**
421      * Attach the controller to the supplied views.
422      */
attachViews(ScrimView behindScrim, ScrimView notificationsScrim, ScrimView scrimInFront)423     public void attachViews(ScrimView behindScrim, ScrimView notificationsScrim,
424                             ScrimView scrimInFront) {
425         mNotificationsScrim = notificationsScrim;
426         mScrimBehind = behindScrim;
427         mScrimInFront = scrimInFront;
428         updateThemeColors();
429         mNotificationsScrim.setScrimName(getScrimName(mNotificationsScrim));
430         mScrimBehind.setScrimName(getScrimName(mScrimBehind));
431         mScrimInFront.setScrimName(getScrimName(mScrimInFront));
432 
433         behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
434         mNotificationsScrim.enableRoundedCorners(true);
435 
436         final ScrimState[] states = ScrimState.values();
437         for (int i = 0; i < states.length; i++) {
438             states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager,
439                     this::isBlurCurrentlySupported);
440             states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
441             states[i].setDefaultScrimAlpha(getDefaultScrimAlpha());
442         }
443 
444         mTransparentScrimBackground = notificationsScrim.getResources()
445                 .getBoolean(R.bool.notification_scrim_transparent);
446         updateScrims();
447         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
448 
449         // prepare() sets proper initial values for most states
450         for (ScrimState state : ScrimState.values()) {
451             state.prepare(state);
452         }
453 
454         hydrateStateInternally(behindScrim);
455 
456         mViewsAttached = true;
457     }
458 
hydrateStateInternally(ScrimView behindScrim)459     private void hydrateStateInternally(ScrimView behindScrim) {
460         if (SceneContainerFlag.isEnabled()) {
461             return;
462         }
463 
464         // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure
465         // to report back that keyguard has faded away. This fixes cases where the scrim state was
466         // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl
467         mBouncerToGoneTransition =
468                 (TransitionStep step) -> {
469                     TransitionState state = step.getTransitionState();
470 
471                     mIsBouncerToGoneTransitionRunning = state == TransitionState.RUNNING;
472 
473                     if (state == TransitionState.STARTED) {
474                         setExpansionAffectsAlpha(false);
475                         legacyTransitionTo(ScrimState.UNLOCKED);
476                     }
477 
478                     if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) {
479                         setExpansionAffectsAlpha(true);
480                         if (mKeyguardStateController.isKeyguardFadingAway()) {
481                             mStatusBarKeyguardViewManager.onKeyguardFadedAway();
482                         }
483                         dispatchScrimsVisible();
484                         dispatchBackScrimState(mScrimBehind.getViewAlpha());
485                     }
486                 };
487 
488         // PRIMARY_BOUNCER->GONE
489         collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
490                 Edge.Companion.getINVALID(),
491                 Edge.Companion.create(PRIMARY_BOUNCER, GONE)),
492                 mBouncerToGoneTransition, mMainDispatcher);
493         collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
494                 mScrimAlphaConsumer, mMainDispatcher);
495 
496         // ALTERNATE_BOUNCER->GONE
497         collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
498                 Edge.Companion.create(ALTERNATE_BOUNCER, Scenes.Gone),
499                 Edge.Companion.create(ALTERNATE_BOUNCER, GONE)),
500                 mBouncerToGoneTransition, mMainDispatcher);
501         collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(),
502                 mScrimAlphaConsumer, mMainDispatcher);
503 
504         // LOCKSCREEN<->GLANCEABLE_HUB
505         collectFlow(
506                 behindScrim,
507                 mKeyguardTransitionInteractor.transition(
508                         Edge.Companion.create(LOCKSCREEN, Scenes.Communal),
509                         Edge.Companion.create(LOCKSCREEN, GLANCEABLE_HUB)),
510                 mGlanceableHubConsumer,
511                 mMainDispatcher);
512         collectFlow(behindScrim,
513                 mKeyguardTransitionInteractor.transition(
514                         Edge.Companion.create(Scenes.Communal, LOCKSCREEN),
515                         Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)),
516                 mGlanceableHubConsumer, mMainDispatcher);
517 
518         if (Flags.bouncerUiRevamp() || Flags.notificationShadeBlur()) {
519             collectFlow(behindScrim,
520                     mWindowRootViewBlurInteractor.get().isBlurCurrentlySupported(),
521                     this::handleBlurSupportedChanged);
522         }
523     }
524 
updateDefaultScrimAlphas()525     private void updateDefaultScrimAlphas() {
526         for (ScrimState state : ScrimState.values()) {
527             state.setDefaultScrimAlpha(getDefaultScrimAlpha(true));
528         }
529         applyAndDispatchState();
530     }
531 
isBlurCurrentlySupported()532     private boolean isBlurCurrentlySupported() {
533         return mWindowRootViewBlurInteractor.get()
534                 .isBlurCurrentlySupported()
535                 .getValue();
536     }
537 
handleBlurSupportedChanged(boolean isBlurSupported)538     private void handleBlurSupportedChanged(boolean isBlurSupported) {
539         this.mIsBlurSupported = isBlurSupported;
540         if (Flags.bouncerUiRevamp()) {
541             updateDefaultScrimAlphas();
542             if (isBlurSupported) {
543                 ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(mBlurConfig.getMaxBlurRadiusPx());
544             } else {
545                 ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(0f);
546             }
547         }
548         if (Flags.notificationShadeBlur()) {
549             mState.prepare(mState);
550             applyAndDispatchState();
551         }
552     }
553 
554     // TODO(b/270984686) recompute scrim height accurately, based on shade contents.
555     /** Set corner radius of the bottom edge of the Notification scrim. */
setNotificationBottomRadius(float radius)556     public void setNotificationBottomRadius(float radius) {
557         if (mNotificationsScrim == null) {
558             return;
559         }
560         mNotificationsScrim.setBottomEdgeRadius(radius);
561     }
562 
563     /** Sets corner radius of scrims. */
setScrimCornerRadius(int radius)564     public void setScrimCornerRadius(int radius) {
565         if (mScrimBehind == null || mNotificationsScrim == null) {
566             return;
567         }
568         mScrimBehind.setCornerRadius(radius);
569         mNotificationsScrim.setCornerRadius(radius);
570     }
571 
setScrimVisibleListener(Consumer<Integer> listener)572     void setScrimVisibleListener(Consumer<Integer> listener) {
573         mScrimVisibleListener = listener;
574     }
575 
transitionTo(ScrimState state)576     public void transitionTo(ScrimState state) {
577         if (SceneContainerFlag.isUnexpectedlyInLegacyMode() || !mViewsAttached) {
578             return;
579         }
580 
581         internalTransitionTo(state, null);
582     }
583 
584     /**
585      * Transitions to the given {@link ScrimState}.
586      *
587      * @deprecated Legacy codepath only. Do not call directly.
588      */
589     @Deprecated
legacyTransitionTo(ScrimState state)590     public void legacyTransitionTo(ScrimState state) {
591         SceneContainerFlag.assertInLegacyMode();
592         internalTransitionTo(state, null);
593     }
594 
595     /**
596      * Transitions to the given {@link ScrimState}.
597      *
598      * @deprecated Legacy codepath only. Do not call directly.
599      */
600     @Deprecated
legacyTransitionTo(ScrimState state, Callback callback)601     public void legacyTransitionTo(ScrimState state, Callback callback) {
602         SceneContainerFlag.assertInLegacyMode();
603         internalTransitionTo(state, callback);
604     }
605 
internalTransitionTo(ScrimState state, Callback callback)606     private void internalTransitionTo(ScrimState state, Callback callback) {
607         if (mIsBouncerToGoneTransitionRunning) {
608             Log.i(TAG, "Skipping transition to: " + state
609                     + " while mIsBouncerToGoneTransitionRunning");
610             return;
611         }
612         if (state == mState) {
613             // Call the callback anyway, unless it's already enqueued
614             if (callback != null && mCallback != callback) {
615                 callback.onFinished();
616             }
617             return;
618         } else if (DEBUG) {
619             Log.d(TAG, "State changed to: " + state);
620         }
621 
622         if (state == ScrimState.UNINITIALIZED) {
623             throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
624         }
625 
626         final ScrimState oldState = mState;
627         mState = state;
628         TrackTracer.instantForGroup("scrim", "state", mState.ordinal());
629 
630         if (mCallback != null) {
631             mCallback.onCancelled();
632         }
633         mCallback = callback;
634 
635         state.prepare(oldState);
636         mScreenBlankingCallbackCalled = false;
637         mAnimationDelay = 0;
638         mBlankScreen = state.getBlanksScreen();
639         mAnimateChange = state.getAnimateChange();
640         mAnimationDuration = state.getAnimationDuration();
641 
642         if (mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
643             // When the device is docked while on GLANCEABLE_HUB, the dream starts underneath the
644             // hub and the ScrimState transitions to GLANCEABLE_HUB_OVER_DREAM. To prevent the
645             // scrims from flickering in during this transition, we set the panel expansion
646             // fraction, which is 1 when idle on GLANCEABLE_HUB, to 0. This only occurs when the hub
647             // is open because the hub lives in the same window as the shade, which is not visible
648             // when transitioning from KEYGUARD to DREAMING.
649             mPanelExpansionFraction = 0f;
650         }
651 
652         applyState();
653 
654         mScrimInFront.setBlendWithMainColor(state.shouldBlendWithMainColor());
655 
656         // Cancel blanking transitions that were pending before we requested a new state
657         if (mPendingFrameCallback != null) {
658             mScrimBehind.removeCallbacks(mPendingFrameCallback);
659             mPendingFrameCallback = null;
660         }
661         if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
662             mHandler.removeCallbacks(mBlankingTransitionRunnable);
663             mBlankingTransitionRunnable = null;
664         }
665 
666         // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
667         // to do the same when you're just showing the brightness mirror.
668         mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
669 
670         // The device might sleep if it's entering AOD, we need to make sure that
671         // the animation plays properly until the last frame.
672         // It's important to avoid holding the wakelock unless necessary because
673         // WakeLock#aqcuire will trigger an IPC and will cause jank.
674         if (mState.isLowPowerState()) {
675             holdWakeLock();
676         }
677 
678         if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
679             mAnimationDelay = CentralSurfaces.FADE_KEYGUARD_START_DELAY;
680             scheduleUpdate();
681         } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING)  // leaving doze
682                 && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED))
683                 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
684             // Scheduling a frame isn't enough when:
685             //  • Leaving doze and we need to modify scrim color immediately
686             //  • ColorFade will not kick-in and scrim cannot wait for pre-draw.
687             onPreDraw();
688         } else {
689             // Schedule a frame
690             scheduleUpdate();
691         }
692 
693         dispatchBackScrimState(mScrimBehind.getViewAlpha());
694     }
695 
getState()696     public ScrimState getState() {
697         return mState;
698     }
699 
700     /**
701      * Sets the additional scrim behind alpha keyguard that would be blended with the default scrim
702      * by applying alpha composition on both values.
703      *
704      * @param additionalScrimAlpha alpha value of additional scrim behind alpha keyguard.
705      */
setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha)706     protected void setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha) {
707         mAdditionalScrimBehindAlphaKeyguard = additionalScrimAlpha;
708     }
709 
710     /**
711      * Applies alpha composition to the default scrim behind alpha keyguard and the additional
712      * scrim alpha, and sets this value to the scrim behind alpha keyguard.
713      * This is used to apply additional keyguard dimming on top of the default scrim alpha value.
714      */
applyCompositeAlphaOnScrimBehindKeyguard()715     protected void applyCompositeAlphaOnScrimBehindKeyguard() {
716         int compositeAlpha = ColorUtils.compositeAlpha(
717                 (int) (255 * mAdditionalScrimBehindAlphaKeyguard),
718                 (int) (255 * KEYGUARD_SCRIM_ALPHA));
719         float keyguardScrimAlpha = (float) compositeAlpha / 255;
720         setScrimBehindValues(keyguardScrimAlpha);
721     }
722 
723     /**
724      * Sets the scrim behind alpha keyguard values. This is how much the keyguard will be dimmed.
725      *
726      * @param scrimBehindAlphaKeyguard alpha value of the scrim behind
727      */
setScrimBehindValues(float scrimBehindAlphaKeyguard)728     private void setScrimBehindValues(float scrimBehindAlphaKeyguard) {
729         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
730         ScrimState[] states = ScrimState.values();
731         for (int i = 0; i < states.length; i++) {
732             states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
733         }
734         scheduleUpdate();
735     }
736 
737     /** This is used by the predictive back gesture animation to scale the Shade. */
applyBackScaling(float scale)738     public void applyBackScaling(float scale) {
739         mNotificationsScrim.setScaleX(scale);
740         mNotificationsScrim.setScaleY(scale);
741     }
742 
getBackScaling()743     public float getBackScaling() {
744         return mNotificationsScrim.getScaleY();
745     }
746 
onTrackingStarted()747     public void onTrackingStarted() {
748         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
749         if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
750             mAnimatingPanelExpansionOnUnlock = false;
751         }
752     }
753 
holdWakeLock()754     private void holdWakeLock() {
755         if (!mWakeLockHeld) {
756             if (mWakeLock != null) {
757                 mWakeLockHeld = true;
758                 mWakeLock.acquire(TAG);
759             } else {
760                 Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
761             }
762         }
763     }
764 
765     /**
766      * Current state of the shade expansion when pulling it from the top.
767      * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
768      *
769      * The expansion fraction is tied to the scrim opacity.
770      *
771      * See {@link ScrimShadeTransitionController#onPanelExpansionChanged}.
772      *
773      * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
774      */
setRawPanelExpansionFraction( @loatRangefrom = 0.0, to = 1.0) float rawPanelExpansionFraction)775     public void setRawPanelExpansionFraction(
776              @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) {
777         if (isNaN(rawPanelExpansionFraction)) {
778             throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN");
779         }
780         mRawPanelExpansionFraction = rawPanelExpansionFraction;
781         calculateAndUpdatePanelExpansion();
782     }
783 
784     /** See {@link ShadeViewController#setPanelScrimMinFraction(float)}. */
setPanelScrimMinFraction(float minFraction)785     public void setPanelScrimMinFraction(float minFraction) {
786         if (isNaN(minFraction)) {
787             throw new IllegalArgumentException("minFraction should not be NaN");
788         }
789         mPanelScrimMinFraction = minFraction;
790         calculateAndUpdatePanelExpansion();
791     }
792 
calculateAndUpdatePanelExpansion()793     private void calculateAndUpdatePanelExpansion() {
794         float panelExpansionFraction = mRawPanelExpansionFraction;
795         if (mPanelScrimMinFraction < 1.0f) {
796             panelExpansionFraction = Math.max(
797                     (mRawPanelExpansionFraction - mPanelScrimMinFraction)
798                             / (1.0f - mPanelScrimMinFraction),
799                     0);
800         }
801 
802         if (mPanelExpansionFraction != panelExpansionFraction) {
803             if (panelExpansionFraction != 0f
804                     && mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()
805                     && mState != ScrimState.UNLOCKED) {
806                 mAnimatingPanelExpansionOnUnlock = true;
807             } else if (panelExpansionFraction == 0f) {
808                 mAnimatingPanelExpansionOnUnlock = false;
809             }
810 
811             mPanelExpansionFraction = panelExpansionFraction;
812 
813             boolean relevantState = (mState == ScrimState.UNLOCKED
814                     || mState == ScrimState.KEYGUARD
815                     || mState == ScrimState.DREAMING
816                     || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM
817                     || mState == ScrimState.SHADE_LOCKED
818                     || mState == ScrimState.PULSING);
819             if (!(relevantState && mExpansionAffectsAlpha) || mAnimatingPanelExpansionOnUnlock) {
820                 return;
821             }
822             applyAndDispatchState();
823         }
824     }
825 
onUnlockAnimationFinished()826     public void onUnlockAnimationFinished() {
827         mAnimatingPanelExpansionOnUnlock = false;
828         applyAndDispatchState();
829     }
830 
831     /**
832      * Set the amount of progress we are currently in if we're transitioning to the full shade.
833      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
834      * shade.
835      *
836      * @param progress the progress for all scrims.
837      * @param lockScreenNotificationsProgress the progress specifically for the notifications scrim.
838      */
setTransitionToFullShadeProgress(float progress, float lockScreenNotificationsProgress)839     public void setTransitionToFullShadeProgress(float progress,
840             float lockScreenNotificationsProgress) {
841         if (progress != mTransitionToFullShadeProgress || lockScreenNotificationsProgress
842                 != mTransitionToLockScreenFullShadeNotificationsProgress) {
843             mTransitionToFullShadeProgress = progress;
844             mTransitionToLockScreenFullShadeNotificationsProgress = lockScreenNotificationsProgress;
845             setTransitionToFullShade(progress > 0.0f || lockScreenNotificationsProgress > 0.0f);
846             applyAndDispatchState();
847         }
848     }
849 
850     /**
851      * Set if we're currently transitioning to the full shade
852      */
setTransitionToFullShade(boolean transitioning)853     private void setTransitionToFullShade(boolean transitioning) {
854         if (transitioning != mTransitioningToFullShade) {
855             mTransitioningToFullShade = transitioning;
856         }
857     }
858 
859 
860     /**
861      * Set bounds for notifications background, all coordinates are absolute
862      */
setNotificationsBounds(float left, float top, float right, float bottom)863     public void setNotificationsBounds(float left, float top, float right, float bottom) {
864         if (mClipsQsScrim) {
865             // notification scrim's rounded corners are anti-aliased, but clipping of the QS/behind
866             // scrim can't be and it's causing jagged corners. That's why notification scrim needs
867             // to overlap QS scrim by one pixel horizontally (left - 1 and right + 1)
868             // see: b/186644628
869             mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom);
870             mScrimBehind.setBottomEdgePosition((int) top);
871         } else {
872             mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
873         }
874 
875         // Only clip if the notif scrim is visible
876         if (mNotificationsAlpha > 0f) {
877             mKeyguardInteractor.setTopClippingBounds((int) top);
878         } else {
879             mKeyguardInteractor.setTopClippingBounds(null);
880         }
881     }
882 
883     /**
884      * Sets the amount of vertical over scroll that should be performed on the notifications scrim.
885      */
setNotificationsOverScrollAmount(int overScrollAmount)886     public void setNotificationsOverScrollAmount(int overScrollAmount) {
887         if (mNotificationsScrim != null) mNotificationsScrim.setTranslationY(overScrollAmount);
888     }
889 
890     /**
891      * Current state of the QuickSettings when pulling it from the top.
892      *
893      * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
894      * @param qsPanelBottomY Absolute Y position of qs panel bottom
895      */
setQsPosition(float expansionFraction, int qsPanelBottomY)896     public void setQsPosition(float expansionFraction, int qsPanelBottomY) {
897         if (isNaN(expansionFraction)) {
898             return;
899         }
900         expansionFraction = ShadeInterpolation.getNotificationScrimAlpha(expansionFraction);
901         boolean qsBottomVisible = qsPanelBottomY > 0;
902         if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
903             mQsExpansion = expansionFraction;
904             mQsBottomVisible = qsBottomVisible;
905             boolean relevantState = (mState == ScrimState.SHADE_LOCKED
906                     || mState == ScrimState.KEYGUARD
907                     || mState == ScrimState.PULSING);
908             if (!(relevantState && mExpansionAffectsAlpha)) {
909                 return;
910             }
911             applyAndDispatchState();
912         }
913     }
914 
915     /**
916      * Updates the percentage of the bouncer which is hidden.
917      */
setBouncerHiddenFraction(@loatRangefrom = 0, to = 1) float bouncerHiddenAmount)918     public void setBouncerHiddenFraction(@FloatRange(from = 0, to = 1) float bouncerHiddenAmount) {
919         if (mBouncerHiddenFraction == bouncerHiddenAmount) {
920             return;
921         }
922         mBouncerHiddenFraction = bouncerHiddenAmount;
923         if (mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB
924                 || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
925             // The dreaming and glanceable hub states requires this for the scrim calculation, so we
926             // should only trigger an update in those states.
927             applyAndDispatchState();
928         }
929     }
930 
931     /**
932      * If QS and notification scrims should not overlap, and should be clipped to each other's
933      * bounds instead.
934      */
setClipsQsScrim(boolean clipScrim)935     public void setClipsQsScrim(boolean clipScrim) {
936         if (Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) {
937             // Never clip scrims when blur is enabled, colors of UI elements are supposed to "add"
938             // up across the scrims.
939             mClipsQsScrim = false;
940             return;
941         }
942         if (clipScrim == mClipsQsScrim) {
943             return;
944         }
945         mClipsQsScrim = clipScrim;
946         for (ScrimState state : ScrimState.values()) {
947             state.setClipQsScrim(mClipsQsScrim);
948         }
949         if (mScrimBehind != null) {
950             mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim);
951         }
952         if (mState != ScrimState.UNINITIALIZED) {
953             // the clipScrimState has changed, let's reprepare ourselves
954             mState.prepare(mState);
955             applyAndDispatchState();
956         }
957     }
958 
959     @VisibleForTesting
getClipQsScrim()960     public boolean getClipQsScrim() {
961         return mClipsQsScrim;
962     }
963 
setOccludeAnimationPlaying(boolean occludeAnimationPlaying)964     public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
965         mOccludeAnimationPlaying = occludeAnimationPlaying;
966 
967         for (ScrimState state : ScrimState.values()) {
968             state.setOccludeAnimationPlaying(occludeAnimationPlaying);
969         }
970 
971         applyAndDispatchState();
972     }
973 
setOrAdaptCurrentAnimation(@ullable View scrim)974     private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
975         if (scrim == null) {
976             return;
977         }
978 
979         float alpha = getCurrentScrimAlpha(scrim);
980         boolean qsScrimPullingDown = scrim == mScrimBehind && mQsBottomVisible;
981         if (isAnimating(scrim) && !qsScrimPullingDown) {
982             // Adapt current animation.
983             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
984             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
985             float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
986             float relativeDiff = alpha - previousEndValue;
987             float newStartValue = previousStartValue + relativeDiff;
988             scrim.setTag(TAG_START_ALPHA, newStartValue);
989             scrim.setTag(TAG_END_ALPHA, alpha);
990             previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
991         } else {
992             // Set animation.
993             updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
994         }
995     }
996 
applyState()997     private void applyState() {
998         mInFrontTint = mState.getFrontTint();
999         mBehindTint = mState.getBehindTint();
1000         mNotificationsTint = mState.getNotifTint();
1001 
1002         mInFrontAlpha = mState.getFrontAlpha();
1003         mBehindAlpha = mState.getBehindAlpha();
1004         mNotificationsAlpha = mState.getNotifAlpha();
1005 
1006         assertAlphasValid();
1007 
1008         if (!mExpansionAffectsAlpha) {
1009             return;
1010         }
1011 
1012         if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING
1013                 || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
1014             final boolean occluding =
1015                     mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
1016             // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
1017             // screen off/occlusion animations, ignore expansion changes while those animations
1018             // play.
1019             if (!mScreenOffAnimationController.shouldExpandNotifications()
1020                     && !mAnimatingPanelExpansionOnUnlock
1021                     && !occluding) {
1022                 if (mTransparentScrimBackground) {
1023                     mBehindAlpha = 0;
1024                     mNotificationsAlpha = 0;
1025                 } else if (mClipsQsScrim) {
1026                     float behindFraction = getInterpolatedFraction();
1027                     behindFraction = (float) Math.pow(behindFraction, 0.8f);
1028                     mBehindAlpha = 1;
1029                     mNotificationsAlpha = behindFraction * getDefaultScrimAlpha();
1030                 } else {
1031                     if (Flags.notificationShadeBlur()) {
1032                         // TODO (b/390730594): match any spec for controlling alpha based on shade
1033                         //  expansion fraction.
1034                         mBehindAlpha = mState.getBehindAlpha() * mPanelExpansionFraction;
1035                         mBehindTint = mState.getBehindTint();
1036                         mNotificationsAlpha = mState.getNotifAlpha() * mPanelExpansionFraction;
1037                         mNotificationsTint = mState.getNotifTint();
1038                     } else {
1039                         mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
1040                                 mPanelExpansionFraction * getDefaultScrimAlpha());
1041                         mNotificationsAlpha =
1042                                 mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
1043                                         mPanelExpansionFraction);
1044                     }
1045                 }
1046                 mBehindTint = mState.getBehindTint();
1047                 mInFrontAlpha = 0;
1048             }
1049 
1050             if ((mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM)
1051                     && mBouncerHiddenFraction != KeyguardBouncerConstants.EXPANSION_HIDDEN) {
1052                 // Bouncer is opening over dream or glanceable hub over dream.
1053                 final float interpolatedFraction =
1054                         BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
1055                                 mBouncerHiddenFraction);
1056                 mBehindAlpha = MathUtils.lerp(getDefaultScrimAlpha(), mBehindAlpha,
1057                         interpolatedFraction);
1058                 mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
1059                         mBehindTint,
1060                         interpolatedFraction);
1061             }
1062         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
1063                 || mState == ScrimState.PULSING || mState == ScrimState.GLANCEABLE_HUB) {
1064             Pair<Integer, Float> result = calculateBackStateForState(mState);
1065             int behindTint = result.first;
1066             float behindAlpha = result.second;
1067             if (mTransitionToFullShadeProgress > 0.0f) {
1068                 Pair<Integer, Float> shadeResult = calculateBackStateForState(
1069                         ScrimState.SHADE_LOCKED);
1070                 behindAlpha = MathUtils.lerp(behindAlpha, shadeResult.second,
1071                         mTransitionToFullShadeProgress);
1072                 behindTint = ColorUtils.blendARGB(behindTint, shadeResult.first,
1073                         mTransitionToFullShadeProgress);
1074             } else if (mState == ScrimState.GLANCEABLE_HUB && mTransitionToFullShadeProgress == 0.0f
1075                     && mBouncerHiddenFraction == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
1076                 // Behind scrim should not be visible when idle on the glanceable hub and neither
1077                 // bouncer nor shade are showing.
1078                 behindAlpha = 0f;
1079             }
1080             mInFrontAlpha = mState.getFrontAlpha();
1081             if (mClipsQsScrim) {
1082                 mNotificationsAlpha = behindAlpha;
1083                 mNotificationsTint = behindTint;
1084                 mBehindAlpha = 1;
1085                 mBehindTint = Color.BLACK;
1086             } else {
1087                 mBehindAlpha = behindAlpha;
1088                 if (mState == ScrimState.KEYGUARD && mTransitionToFullShadeProgress > 0.0f) {
1089                     mNotificationsAlpha = MathUtils
1090                             .saturate(mTransitionToLockScreenFullShadeNotificationsProgress);
1091                 } else if (mState == ScrimState.SHADE_LOCKED) {
1092                     // going from KEYGUARD to SHADE_LOCKED state
1093                     if (Flags.notificationShadeBlur()) {
1094                         mNotificationsAlpha = mState.getNotifAlpha() * getInterpolatedFraction();
1095                     } else {
1096                         mNotificationsAlpha = getInterpolatedFraction();
1097                     }
1098                 } else if (mState == ScrimState.GLANCEABLE_HUB
1099                         && mTransitionToFullShadeProgress == 0.0f) {
1100                     // Notification scrim should not be visible on the glanceable hub unless the
1101                     // shade is showing or transitioning in. Otherwise the notification scrim will
1102                     // be visible as the bouncer transitions in or after the notification shade
1103                     // closes.
1104                     mNotificationsAlpha = 0;
1105                 } else {
1106                     mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion);
1107                 }
1108                 mNotificationsTint = mState.getNotifTint();
1109                 mBehindTint = behindTint;
1110             }
1111 
1112             // At the end of a launch animation over the lockscreen, the state is either KEYGUARD or
1113             // SHADE_LOCKED and this code is called. We have to set the notification alpha to 0
1114             // otherwise there is a flicker to its previous value.
1115             boolean hideNotificationScrim = (mState == ScrimState.KEYGUARD
1116                     && mTransitionToFullShadeProgress == 0
1117                     && mQsExpansion == 0
1118                     && !mClipsQsScrim);
1119             if (mKeyguardOccluded || hideNotificationScrim) {
1120                 mNotificationsAlpha = 0;
1121             }
1122         }
1123         if (mState != ScrimState.UNLOCKED) {
1124             mAnimatingPanelExpansionOnUnlock = false;
1125         }
1126 
1127         assertAlphasValid();
1128     }
1129 
assertAlphasValid()1130     private void assertAlphasValid() {
1131         if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
1132             throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
1133                     + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
1134                     + mNotificationsAlpha);
1135         }
1136     }
1137 
calculateBackStateForState(ScrimState state)1138     private Pair<Integer, Float> calculateBackStateForState(ScrimState state) {
1139         // Either darken or make the scrim transparent when pulling down the shade.
1140         float interpolatedFract = getInterpolatedFraction();
1141 
1142         float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha();
1143         float behindAlpha;
1144         int behindTint = state.getBehindTint();
1145         if (mDarkenWhileDragging) {
1146             behindAlpha = MathUtils.lerp(getDefaultScrimAlpha(), stateBehind,
1147                     interpolatedFract);
1148         } else {
1149             behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
1150                     interpolatedFract);
1151         }
1152         if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
1153             if (mClipsQsScrim) {
1154                 behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
1155                     state.getNotifTint(), interpolatedFract);
1156             } else {
1157                 behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
1158                     state.getBehindTint(), interpolatedFract);
1159             }
1160         }
1161         if (mQsExpansion > 0) {
1162             behindAlpha = MathUtils.lerp(behindAlpha, getDefaultScrimAlpha(), mQsExpansion);
1163             float tintProgress = mQsExpansion;
1164             if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
1165                 // this is case of - on lockscreen - going from expanded QS to bouncer.
1166                 // Because mQsExpansion is already interpolated and transition between tints
1167                 // is too slow, we want to speed it up and make it more aligned to bouncer
1168                 // showing up progress. This issue is visible on large screens, both portrait and
1169                 // split shade because then transition is between very different tints
1170                 tintProgress = BouncerPanelExpansionCalculator
1171                         .showBouncerProgress(mPanelExpansionFraction);
1172             }
1173             int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint()
1174                     : ScrimState.SHADE_LOCKED.getBehindTint();
1175             behindTint = ColorUtils.blendARGB(behindTint, stateTint, tintProgress);
1176         }
1177 
1178         // If the keyguard is going away, we should not be opaque.
1179         if (mKeyguardStateController.isKeyguardGoingAway()) {
1180             behindAlpha = 0f;
1181         }
1182 
1183         return new Pair<>(behindTint, behindAlpha);
1184     }
1185 
1186 
applyAndDispatchState()1187     private void applyAndDispatchState() {
1188         applyState();
1189         if (mUpdatePending) {
1190             return;
1191         }
1192         setOrAdaptCurrentAnimation(mScrimBehind);
1193         setOrAdaptCurrentAnimation(mNotificationsScrim);
1194         setOrAdaptCurrentAnimation(mScrimInFront);
1195         dispatchBackScrimState(mScrimBehind.getViewAlpha());
1196     }
1197 
1198     /**
1199      * Sets the front scrim opacity in AOD so it's not as bright.
1200      * <p>
1201      * Displays usually don't support multiple dimming settings when in low power mode.
1202      * The workaround is to modify the front scrim opacity when in AOD, so it's not as
1203      * bright when you're at the movies or lying down on bed.
1204      * <p>
1205      * This value will be lost during transitions and only updated again after the the
1206      * device is dozing when the light sensor is on.
1207      */
setAodFrontScrimAlpha(float alpha)1208     public void setAodFrontScrimAlpha(float alpha) {
1209         if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) {
1210             mInFrontAlpha = alpha;
1211             updateScrims();
1212         }
1213 
1214         mState.AOD.setAodFrontScrimAlpha(alpha);
1215         mState.PULSING.setAodFrontScrimAlpha(alpha);
1216     }
1217 
shouldUpdateFrontScrimAlpha()1218     private boolean shouldUpdateFrontScrimAlpha() {
1219         if (mState == ScrimState.AOD
1220                 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
1221             return true;
1222         }
1223 
1224         return mState == ScrimState.PULSING;
1225     }
1226 
1227     /**
1228      * If the lock screen sensor is active.
1229      */
setWakeLockScreenSensorActive(boolean active)1230     public void setWakeLockScreenSensorActive(boolean active) {
1231         for (ScrimState state : ScrimState.values()) {
1232             state.setWakeLockScreenSensorActive(active);
1233         }
1234 
1235         if (mState == ScrimState.PULSING) {
1236             float newBehindAlpha = mState.getBehindAlpha();
1237             if (mBehindAlpha != newBehindAlpha) {
1238                 mBehindAlpha = newBehindAlpha;
1239                 if (isNaN(mBehindAlpha)) {
1240                     throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
1241                             + ", back: " + mBehindAlpha);
1242                 }
1243                 updateScrims();
1244             }
1245         }
1246     }
1247 
scheduleUpdate()1248     protected void scheduleUpdate() {
1249         if (mUpdatePending || mScrimBehind == null) return;
1250 
1251         // Make sure that a frame gets scheduled.
1252         mScrimBehind.invalidate();
1253         mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
1254         mUpdatePending = true;
1255     }
1256 
updateScrims()1257     protected void updateScrims() {
1258         // Make sure we have the right gradients and their opacities will satisfy GAR.
1259         if (mNeedsDrawableColorUpdate) {
1260             mNeedsDrawableColorUpdate = false;
1261             // Only animate scrim color if the scrim view is actually visible
1262             boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
1263             boolean animateBehindScrim = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
1264             boolean animateScrimNotifications = mNotificationsScrim.getViewAlpha() != 0
1265                     && !mBlankScreen;
1266 
1267             mScrimInFront.setColors(mColors, animateScrimInFront);
1268             mScrimBehind.setColors(mColors, animateBehindScrim);
1269             mNotificationsScrim.setColors(mColors, animateScrimNotifications);
1270 
1271             dispatchBackScrimState(mScrimBehind.getViewAlpha());
1272         }
1273         if (Flags.bouncerUiRevamp()) {
1274             // Blur the notification scrim as needed. The blur is needed only when we show the
1275             // expanded shade behind the bouncer. Without it, the notification scrim outline is
1276             // visible behind the bouncer.
1277             mNotificationsScrim.setBlurRadius(mState.getNotifBlurRadius());
1278         }
1279 
1280         // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
1281         boolean hideFlagShowWhenLockedActivities =
1282                 (mState == ScrimState.PULSING || mState == ScrimState.AOD)
1283                 && mKeyguardOccluded;
1284         if (hideFlagShowWhenLockedActivities) {
1285             mBehindAlpha = 1;
1286         }
1287         // Prevent notification scrim flicker when transitioning away from keyguard.
1288         if (mKeyguardStateController.isKeyguardGoingAway()) {
1289             mNotificationsAlpha = 0;
1290         }
1291 
1292         // Prevent flickering for activities above keyguard and quick settings in keyguard.
1293         if (mKeyguardOccluded
1294                 && (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED)) {
1295             mBehindAlpha = 0;
1296             mNotificationsAlpha = 0;
1297         }
1298 
1299         setScrimAlpha(mScrimInFront, mInFrontAlpha);
1300         setScrimAlpha(mScrimBehind, mBehindAlpha);
1301         setScrimAlpha(mNotificationsScrim, mNotificationsAlpha);
1302 
1303         // The animation could have all already finished, let's call onFinished just in case
1304         onFinished(mState);
1305         dispatchScrimsVisible();
1306     }
1307 
dispatchBackScrimState(float alpha)1308     private void dispatchBackScrimState(float alpha) {
1309         // When clipping QS, the notification scrim is the one that feels behind.
1310         // mScrimBehind will be drawing black and its opacity will always be 1.
1311         if (mClipsQsScrim && mQsBottomVisible) {
1312             alpha = mNotificationsAlpha;
1313         }
1314         mScrimStateListener.accept(mState, alpha, mColors);
1315     }
1316 
dispatchScrimsVisible()1317     private void dispatchScrimsVisible() {
1318         final ScrimView backScrim = mClipsQsScrim ? mNotificationsScrim : mScrimBehind;
1319         final int currentScrimVisibility;
1320         if (mScrimInFront.getViewAlpha() == 1 || backScrim.getViewAlpha() == 1) {
1321             currentScrimVisibility = OPAQUE;
1322         } else if (mScrimInFront.getViewAlpha() == 0 && backScrim.getViewAlpha() == 0) {
1323             currentScrimVisibility = TRANSPARENT;
1324         } else {
1325             currentScrimVisibility = SEMI_TRANSPARENT;
1326         }
1327 
1328         if (mScrimsVisibility != currentScrimVisibility) {
1329             mScrimsVisibility = currentScrimVisibility;
1330             mScrimVisibleListener.accept(currentScrimVisibility);
1331         }
1332     }
1333 
getInterpolatedFraction()1334     private float getInterpolatedFraction() {
1335         if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
1336             return BouncerPanelExpansionCalculator
1337                     .aboutToShowBouncerProgress(mPanelExpansionFraction);
1338         }
1339         return ShadeInterpolation.getNotificationScrimAlpha(mPanelExpansionFraction);
1340     }
1341 
setScrimAlpha(ScrimView scrim, float alpha)1342     private void setScrimAlpha(ScrimView scrim, float alpha) {
1343         if (alpha == 0f) {
1344             scrim.setClickable(false);
1345         } else {
1346             // Eat touch events (unless dozing).
1347             scrim.setClickable(mState != ScrimState.AOD);
1348         }
1349         updateScrim(scrim, alpha);
1350     }
1351 
getScrimName(ScrimView scrim)1352     private String getScrimName(ScrimView scrim) {
1353         if (scrim == mScrimInFront) {
1354             return "front_scrim";
1355         } else if (scrim == mScrimBehind) {
1356             return "behind_scrim";
1357         } else if (scrim == mNotificationsScrim) {
1358             return "notifications_scrim";
1359         }
1360         return "unknown_scrim";
1361     }
1362 
updateScrimColor(View scrim, float alpha, int tint)1363     private void updateScrimColor(View scrim, float alpha, int tint) {
1364         alpha = Math.max(0, Math.min(1.0f, alpha));
1365         if (scrim instanceof ScrimView) {
1366             ScrimView scrimView = (ScrimView) scrim;
1367             if (DEBUG_MODE) {
1368                 tint = getDebugScrimTint(scrimView);
1369             }
1370 
1371             TrackTracer.instantForGroup("scrim", getScrimName(scrimView) + "_alpha",
1372                     (int) (alpha * 255));
1373             TrackTracer.instantForGroup("scrim", getScrimName(scrimView) + "_tint",
1374                     Color.alpha(tint));
1375             scrimView.setTint(tint);
1376             if (!mIsBouncerToGoneTransitionRunning) {
1377                 scrimView.setViewAlpha(alpha);
1378             }
1379         } else {
1380             scrim.setAlpha(alpha);
1381         }
1382         dispatchScrimsVisible();
1383     }
1384 
getDebugScrimTint(ScrimView scrim)1385     private int getDebugScrimTint(ScrimView scrim) {
1386         if (scrim == mScrimBehind) return DEBUG_BEHIND_TINT;
1387         if (scrim == mScrimInFront) return DEBUG_FRONT_TINT;
1388         if (scrim == mNotificationsScrim) return DEBUG_NOTIFICATIONS_TINT;
1389         throw new RuntimeException("scrim can't be matched with known scrims");
1390     }
1391 
startScrimAnimation(final View scrim, float current)1392     private void startScrimAnimation(final View scrim, float current) {
1393         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
1394         if (mAnimatorListener != null) {
1395             anim.addListener(mAnimatorListener);
1396         }
1397         final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
1398                 Color.TRANSPARENT;
1399         anim.addUpdateListener(animation -> {
1400             final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA);
1401             final float animAmount = (float) animation.getAnimatedValue();
1402             final int finalScrimTint = getCurrentScrimTint(scrim);
1403             final float finalScrimAlpha = getCurrentScrimAlpha(scrim);
1404             float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount);
1405             alpha = MathUtils.constrain(alpha, 0f, 1f);
1406             int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
1407             updateScrimColor(scrim, alpha, tint);
1408             dispatchScrimsVisible();
1409         });
1410         anim.setInterpolator(mInterpolator);
1411         anim.setStartDelay(mAnimationDelay);
1412         anim.setDuration(mAnimationDuration);
1413         anim.addListener(new AnimatorListenerAdapter() {
1414             private final ScrimState mLastState = mState;
1415             private final Callback mLastCallback = mCallback;
1416 
1417             @Override
1418             public void onAnimationEnd(Animator animation) {
1419                 scrim.setTag(TAG_KEY_ANIM, null);
1420                 onFinished(mLastCallback, mLastState);
1421 
1422                 dispatchScrimsVisible();
1423             }
1424         });
1425 
1426         // Cache alpha values because we might want to update this animator in the future if
1427         // the user expands the panel while the animation is still running.
1428         scrim.setTag(TAG_START_ALPHA, current);
1429         scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim));
1430 
1431         scrim.setTag(TAG_KEY_ANIM, anim);
1432         anim.start();
1433     }
1434 
getCurrentScrimAlpha(View scrim)1435     private float getCurrentScrimAlpha(View scrim) {
1436         if (scrim == mScrimInFront) {
1437             return mInFrontAlpha;
1438         } else if (scrim == mScrimBehind) {
1439             return mBehindAlpha;
1440         } else if (scrim == mNotificationsScrim) {
1441             return mNotificationsAlpha;
1442         } else {
1443             throw new IllegalArgumentException("Unknown scrim view");
1444         }
1445     }
1446 
getCurrentScrimTint(View scrim)1447     private int getCurrentScrimTint(View scrim) {
1448         if (scrim == mScrimInFront) {
1449             return mInFrontTint;
1450         } else if (scrim == mScrimBehind) {
1451             return mBehindTint;
1452         } else if (scrim == mNotificationsScrim) {
1453             return mNotificationsTint;
1454         } else {
1455             throw new IllegalArgumentException("Unknown scrim view");
1456         }
1457     }
1458 
1459     @Override
onPreDraw()1460     public boolean onPreDraw() {
1461         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
1462         mUpdatePending = false;
1463         if (mCallback != null) {
1464             mCallback.onStart();
1465         }
1466         updateScrims();
1467         return true;
1468     }
1469 
1470     /**
1471      * @param state that finished
1472      */
onFinished(ScrimState state)1473     private void onFinished(ScrimState state) {
1474         onFinished(mCallback, state);
1475     }
1476 
onFinished(Callback callback, ScrimState state)1477     private void onFinished(Callback callback, ScrimState state) {
1478         if (mPendingFrameCallback != null) {
1479             // No animations can finish while we're waiting on the blanking to finish
1480             return;
1481 
1482         }
1483         if (isAnimating(mScrimBehind)
1484                 || isAnimating(mNotificationsScrim)
1485                 || isAnimating(mScrimInFront)) {
1486             if (callback != null && callback != mCallback) {
1487                 // Since we only notify the callback that we're finished once everything has
1488                 // finished, we need to make sure that any changing callbacks are also invoked
1489                 callback.onFinished();
1490             }
1491             return;
1492         }
1493         if (mWakeLockHeld) {
1494             mWakeLock.release(TAG);
1495             mWakeLockHeld = false;
1496         }
1497 
1498         if (callback != null) {
1499             callback.onFinished();
1500 
1501             if (callback == mCallback) {
1502                 mCallback = null;
1503             }
1504         }
1505 
1506         // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
1507         // At the end of the animation we need to remove the tint.
1508         if (state == ScrimState.UNLOCKED) {
1509             mInFrontTint = Color.TRANSPARENT;
1510             mBehindTint = mState.getBehindTint();
1511             mNotificationsTint = mState.getNotifTint();
1512             updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
1513             updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
1514             updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint);
1515         }
1516     }
1517 
isAnimating(@ullable View scrim)1518     private boolean isAnimating(@Nullable View scrim) {
1519         return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null;
1520     }
1521 
1522     @VisibleForTesting
setAnimatorListener(Animator.AnimatorListener animatorListener)1523     void setAnimatorListener(Animator.AnimatorListener animatorListener) {
1524         mAnimatorListener = animatorListener;
1525     }
1526 
updateScrim(ScrimView scrim, float alpha)1527     private void updateScrim(ScrimView scrim, float alpha) {
1528         final float currentAlpha = scrim.getViewAlpha();
1529 
1530         ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
1531         if (previousAnimator != null) {
1532             // Previous animators should always be cancelled. Not doing so would cause
1533             // overlap, especially on states that don't animate, leading to flickering,
1534             // and in the worst case, an internal state that doesn't represent what
1535             // transitionTo requested.
1536             cancelAnimator(previousAnimator);
1537         }
1538 
1539         if (mPendingFrameCallback != null) {
1540             // Display is off and we're waiting.
1541             return;
1542         } else if (mBlankScreen) {
1543             // Need to blank the display before continuing.
1544             blankDisplay();
1545             return;
1546         } else if (!mScreenBlankingCallbackCalled) {
1547             // Not blanking the screen. Letting the callback know that we're ready
1548             // to replace what was on the screen before.
1549             if (mCallback != null) {
1550                 mCallback.onDisplayBlanked();
1551                 mScreenBlankingCallbackCalled = true;
1552             }
1553         }
1554 
1555         if (scrim == mScrimBehind) {
1556             dispatchBackScrimState(alpha);
1557         }
1558 
1559         final boolean wantsAlphaUpdate = alpha != currentAlpha;
1560         final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim);
1561 
1562         if (wantsAlphaUpdate || wantsTintUpdate) {
1563             if (mAnimateChange) {
1564                 startScrimAnimation(scrim, currentAlpha);
1565             } else {
1566                 // update the alpha directly
1567                 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
1568             }
1569         }
1570     }
1571 
cancelAnimator(ValueAnimator previousAnimator)1572     private void cancelAnimator(ValueAnimator previousAnimator) {
1573         if (previousAnimator != null) {
1574             previousAnimator.cancel();
1575         }
1576     }
1577 
blankDisplay()1578     private void blankDisplay() {
1579         updateScrimColor(mScrimInFront, 1, Color.BLACK);
1580 
1581         // Notify callback that the screen is completely black and we're
1582         // ready to change the display power mode
1583         mPendingFrameCallback = () -> {
1584             if (mCallback != null) {
1585                 mCallback.onDisplayBlanked();
1586                 mScreenBlankingCallbackCalled = true;
1587             }
1588 
1589             mBlankingTransitionRunnable = () -> {
1590                 mBlankingTransitionRunnable = null;
1591                 mPendingFrameCallback = null;
1592                 mBlankScreen = false;
1593                 // Try again.
1594                 updateScrims();
1595             };
1596 
1597             // Setting power states can happen after we push out the frame. Make sure we
1598             // stay fully opaque until the power state request reaches the lower levels.
1599             final int delay = mScreenOn ? 32 : 500;
1600             if (DEBUG) {
1601                 Log.d(TAG, "Fading out scrims with delay: " + delay);
1602             }
1603             mHandler.postDelayed(mBlankingTransitionRunnable, delay);
1604         };
1605         doOnTheNextFrame(mPendingFrameCallback);
1606     }
1607 
1608     /**
1609      * Executes a callback after the frame has hit the display.
1610      *
1611      * @param callback What to run.
1612      */
1613     @VisibleForTesting
doOnTheNextFrame(Runnable callback)1614     protected void doOnTheNextFrame(Runnable callback) {
1615         // Just calling View#postOnAnimation isn't enough because the frame might not have reached
1616         // the display yet. A timeout is the safest solution.
1617         mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
1618     }
1619 
updateThemeColors()1620     private void updateThemeColors() {
1621         if (mScrimBehind == null) return;
1622         int background = mContext.getColor(
1623                 com.android.internal.R.color.materialColorSurfaceDim);
1624         int accent = mContext.getColor(
1625                 com.android.internal.R.color.materialColorPrimary);
1626         mColors.setMainColor(background);
1627         mColors.setSecondaryColor(accent);
1628         final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
1629         mColors.setSupportsDarkText(isBackgroundLight);
1630 
1631         int surface = mContext.getColor(
1632                 com.android.internal.R.color.materialColorSurface);
1633         for (ScrimState state : ScrimState.values()) {
1634             state.setSurfaceColor(surface);
1635         }
1636 
1637         mNeedsDrawableColorUpdate = true;
1638     }
1639 
onThemeChanged()1640     private void onThemeChanged() {
1641         updateThemeColors();
1642         scheduleUpdate();
1643     }
1644 
1645     @Override
dump(PrintWriter pw, String[] args)1646     public void dump(PrintWriter pw, String[] args) {
1647         pw.println(" ScrimController: ");
1648         pw.print("  state: ");
1649         pw.println(mState);
1650         pw.println("    mClipQsScrim = " + mState.mClipQsScrim);
1651 
1652         pw.print("  frontScrim:");
1653         pw.print(" viewAlpha=");
1654         pw.print(mScrimInFront.getViewAlpha());
1655         pw.print(" alpha=");
1656         pw.print(mInFrontAlpha);
1657         pw.print(" tint=0x");
1658         pw.println(Integer.toHexString(mScrimInFront.getTint()));
1659 
1660         pw.print("  behindScrim:");
1661         pw.print(" viewAlpha=");
1662         pw.print(mScrimBehind.getViewAlpha());
1663         pw.print(" alpha=");
1664         pw.print(mBehindAlpha);
1665         pw.print(" tint=0x");
1666         pw.println(Integer.toHexString(mScrimBehind.getTint()));
1667 
1668         pw.print("  notificationsScrim:");
1669         pw.print(" viewAlpha=");
1670         pw.print(mNotificationsScrim.getViewAlpha());
1671         pw.print(" alpha=");
1672         pw.print(mNotificationsAlpha);
1673         pw.print(" tint=0x");
1674         pw.println(Integer.toHexString(mNotificationsScrim.getTint()));
1675         pw.print(" expansionProgress=");
1676         pw.println(mTransitionToLockScreenFullShadeNotificationsProgress);
1677 
1678         pw.print("  mDefaultScrimAlpha=");
1679         pw.println(getDefaultScrimAlpha());
1680         pw.print("  mPanelExpansionFraction=");
1681         pw.println(mPanelExpansionFraction);
1682         pw.print("  mExpansionAffectsAlpha=");
1683         pw.println(mExpansionAffectsAlpha);
1684     }
1685 
1686     /**
1687      * Interrupts blanking transitions once the display notifies that it's already on.
1688      */
onScreenTurnedOn()1689     public void onScreenTurnedOn() {
1690         mScreenOn = true;
1691         if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
1692             if (DEBUG) {
1693                 Log.d(TAG, "Shorter blanking because screen turned on. All good.");
1694             }
1695             mHandler.removeCallbacks(mBlankingTransitionRunnable);
1696             mBlankingTransitionRunnable.run();
1697         }
1698     }
1699 
onScreenTurnedOff()1700     public void onScreenTurnedOff() {
1701         mScreenOn = false;
1702     }
1703 
isScreenOn()1704     public boolean isScreenOn() {
1705         return mScreenOn;
1706     }
1707 
setExpansionAffectsAlpha(boolean expansionAffectsAlpha)1708     public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
1709         mExpansionAffectsAlpha = expansionAffectsAlpha;
1710     }
1711 
setKeyguardOccluded(boolean keyguardOccluded)1712     public void setKeyguardOccluded(boolean keyguardOccluded) {
1713         if (mKeyguardOccluded == keyguardOccluded) {
1714             return;
1715         }
1716         mKeyguardOccluded = keyguardOccluded;
1717         updateScrims();
1718     }
1719 
setKeyguardFadingAway(boolean fadingAway, long duration)1720     private void setKeyguardFadingAway(boolean fadingAway, long duration) {
1721         for (ScrimState state : ScrimState.values()) {
1722             state.setKeyguardFadingAway(fadingAway, duration);
1723         }
1724     }
1725 
setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)1726     public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
1727         for (ScrimState state : ScrimState.values()) {
1728             state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
1729         }
1730     }
1731 
1732     public interface Callback {
onStart()1733         default void onStart() {
1734         }
1735 
onDisplayBlanked()1736         default void onDisplayBlanked() {
1737         }
1738 
onFinished()1739         default void onFinished() {
1740         }
1741 
onCancelled()1742         default void onCancelled() {
1743         }
1744     }
1745 
1746     /**
1747      * Simple keyguard callback that updates scrims when keyguard visibility changes.
1748      */
1749     private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
1750 
1751         @Override
onKeyguardVisibilityChanged(boolean visible)1752         public void onKeyguardVisibilityChanged(boolean visible) {
1753             mNeedsDrawableColorUpdate = true;
1754             scheduleUpdate();
1755         }
1756     }
1757 }
1758