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