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