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             // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
352             // with too many things at this case, in order to not skip the initial frames.
353             mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
354             mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
355         } else if ((!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD)
356                 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
357             // Scheduling a frame isn't enough when:
358             //  • Leaving doze and we need to modify scrim color immediately
359             //  • ColorFade will not kick-in and scrim cannot wait for pre-draw.
360             onPreDraw();
361         } else {
362             scheduleUpdate();
363         }
364 
365         dispatchScrimState(mScrimBehind.getViewAlpha());
366     }
367 
shouldFadeAwayWallpaper()368     private boolean shouldFadeAwayWallpaper() {
369         if (!mWallpaperSupportsAmbientMode) {
370             return false;
371         }
372 
373         if (mState == ScrimState.AOD
374                 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
375             return true;
376         }
377 
378         return false;
379     }
380 
getState()381     public ScrimState getState() {
382         return mState;
383     }
384 
setScrimBehindValues(float scrimBehindAlphaKeyguard)385     protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) {
386         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
387         ScrimState[] states = ScrimState.values();
388         for (int i = 0; i < states.length; i++) {
389             states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
390         }
391         scheduleUpdate();
392     }
393 
onTrackingStarted()394     public void onTrackingStarted() {
395         mTracking = true;
396         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
397     }
398 
onExpandingFinished()399     public void onExpandingFinished() {
400         mTracking = false;
401     }
402 
403     @VisibleForTesting
onHideWallpaperTimeout()404     protected void onHideWallpaperTimeout() {
405         if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
406             return;
407         }
408 
409         holdWakeLock();
410         mWallpaperVisibilityTimedOut = true;
411         mAnimateChange = true;
412         mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
413         scheduleUpdate();
414     }
415 
holdWakeLock()416     private void holdWakeLock() {
417         if (!mWakeLockHeld) {
418             if (mWakeLock != null) {
419                 mWakeLockHeld = true;
420                 mWakeLock.acquire(TAG);
421             } else {
422                 Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
423             }
424         }
425     }
426 
427     /**
428      * Current state of the shade expansion when pulling it from the top.
429      * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
430      *
431      * The expansion fraction is tied to the scrim opacity.
432      *
433      * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
434      */
setPanelExpansion(float fraction)435     public void setPanelExpansion(float fraction) {
436         if (isNaN(fraction)) {
437             throw new IllegalArgumentException("Fraction should not be NaN");
438         }
439         if (mExpansionFraction != fraction) {
440             mExpansionFraction = fraction;
441 
442             boolean relevantState = (mState == ScrimState.UNLOCKED
443                     || mState == ScrimState.KEYGUARD
444                     || mState == ScrimState.PULSING
445                     || mState == ScrimState.BUBBLE_EXPANDED);
446             if (!(relevantState && mExpansionAffectsAlpha)) {
447                 return;
448             }
449             applyAndDispatchExpansion();
450         }
451     }
452 
setOrAdaptCurrentAnimation(View scrim)453     private void setOrAdaptCurrentAnimation(View scrim) {
454         float alpha = getCurrentScrimAlpha(scrim);
455         if (isAnimating(scrim)) {
456             // Adapt current animation.
457             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
458             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
459             float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
460             float relativeDiff = alpha - previousEndValue;
461             float newStartValue = previousStartValue + relativeDiff;
462             scrim.setTag(TAG_START_ALPHA, newStartValue);
463             scrim.setTag(TAG_END_ALPHA, alpha);
464             previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
465         } else {
466             // Set animation.
467             updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
468         }
469     }
470 
applyExpansionToAlpha()471     private void applyExpansionToAlpha() {
472         if (!mExpansionAffectsAlpha) {
473             return;
474         }
475 
476         if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
477             // Darken scrim as you pull down the shade when unlocked
478             float behindFraction = getInterpolatedFraction();
479             behindFraction = (float) Math.pow(behindFraction, 0.8f);
480             mBehindAlpha = behindFraction * mDefaultScrimAlpha;
481             mInFrontAlpha = 0;
482         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
483             // Either darken of make the scrim transparent when you
484             // pull down the shade
485             float interpolatedFract = getInterpolatedFraction();
486             float alphaBehind = mState.getBehindAlpha();
487             if (mDarkenWhileDragging) {
488                 mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, alphaBehind,
489                         interpolatedFract);
490                 mInFrontAlpha = mState.getFrontAlpha();
491             } else {
492                 mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
493                         interpolatedFract);
494                 mInFrontAlpha = mState.getFrontAlpha();
495             }
496             mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
497                     mState.getBehindTint(), interpolatedFract);
498         }
499         if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) {
500             throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
501                     + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha);
502         }
503     }
504 
applyAndDispatchExpansion()505     private void applyAndDispatchExpansion() {
506         applyExpansionToAlpha();
507         if (mUpdatePending) {
508             return;
509         }
510         setOrAdaptCurrentAnimation(mScrimBehind);
511         setOrAdaptCurrentAnimation(mScrimInFront);
512         setOrAdaptCurrentAnimation(mScrimForBubble);
513         dispatchScrimState(mScrimBehind.getViewAlpha());
514 
515         // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
516         // and docking.
517         if (mWallpaperVisibilityTimedOut) {
518             mWallpaperVisibilityTimedOut = false;
519             DejankUtils.postAfterTraversal(() -> {
520                 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
521                         AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
522             });
523         }
524     }
525 
526     /**
527      * Sets the given drawable as the background of the scrim that shows up behind the
528      * notifications.
529      */
setScrimBehindDrawable(Drawable drawable)530     public void setScrimBehindDrawable(Drawable drawable) {
531         mScrimBehind.setDrawable(drawable);
532     }
533 
534     /**
535      * Sets the front scrim opacity in AOD so it's not as bright.
536      * <p>
537      * Displays usually don't support multiple dimming settings when in low power mode.
538      * The workaround is to modify the front scrim opacity when in AOD, so it's not as
539      * bright when you're at the movies or lying down on bed.
540      * <p>
541      * This value will be lost during transitions and only updated again after the the
542      * device is dozing when the light sensor is on.
543      */
setAodFrontScrimAlpha(float alpha)544     public void setAodFrontScrimAlpha(float alpha) {
545         if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) {
546             mInFrontAlpha = alpha;
547             updateScrims();
548         }
549 
550         mState.AOD.setAodFrontScrimAlpha(alpha);
551         mState.PULSING.setAodFrontScrimAlpha(alpha);
552     }
553 
shouldUpdateFrontScrimAlpha()554     private boolean shouldUpdateFrontScrimAlpha() {
555         if (mState == ScrimState.AOD
556                 && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
557             return true;
558         }
559 
560         if (mState == ScrimState.PULSING) {
561             return true;
562         }
563 
564         return false;
565     }
566 
567     /**
568      * If the lock screen sensor is active.
569      */
setWakeLockScreenSensorActive(boolean active)570     public void setWakeLockScreenSensorActive(boolean active) {
571         for (ScrimState state : ScrimState.values()) {
572             state.setWakeLockScreenSensorActive(active);
573         }
574 
575         if (mState == ScrimState.PULSING) {
576             float newBehindAlpha = mState.getBehindAlpha();
577             if (mBehindAlpha != newBehindAlpha) {
578                 mBehindAlpha = newBehindAlpha;
579                 if (isNaN(mBehindAlpha)) {
580                     throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
581                             + ", back: " + mBehindAlpha);
582                 }
583                 updateScrims();
584             }
585         }
586     }
587 
scheduleUpdate()588     protected void scheduleUpdate() {
589         if (mUpdatePending || mScrimBehind == null) return;
590 
591         // Make sure that a frame gets scheduled.
592         mScrimBehind.invalidate();
593         mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
594         mUpdatePending = true;
595     }
596 
updateScrims()597     protected void updateScrims() {
598         // Make sure we have the right gradients and their opacities will satisfy GAR.
599         if (mNeedsDrawableColorUpdate) {
600             mNeedsDrawableColorUpdate = false;
601             // Only animate scrim color if the scrim view is actually visible
602             boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
603             boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
604             boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
605 
606             mScrimInFront.setColors(mColors, animateScrimInFront);
607             mScrimBehind.setColors(mColors, animateScrimBehind);
608             mScrimForBubble.setColors(mColors, animateScrimForBubble);
609 
610             // Calculate minimum scrim opacity for white or black text.
611             int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
612             int mainColor = mColors.getMainColor();
613             float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor,
614                     4.5f /* minimumContrast */) / 255f;
615             dispatchScrimState(mScrimBehind.getViewAlpha());
616         }
617 
618         // We want to override the back scrim opacity for the AOD state
619         // when it's time to fade the wallpaper away.
620         boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
621                 && mWallpaperVisibilityTimedOut;
622         // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
623         boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
624                 && mKeyguardOccluded;
625         if (aodWallpaperTimeout || occludedKeyguard) {
626             mBehindAlpha = 1;
627         }
628         setScrimAlpha(mScrimInFront, mInFrontAlpha);
629         setScrimAlpha(mScrimBehind, mBehindAlpha);
630         setScrimAlpha(mScrimForBubble, mBubbleAlpha);
631         // The animation could have all already finished, let's call onFinished just in case
632         onFinished();
633         dispatchScrimsVisible();
634     }
635 
dispatchScrimState(float alpha)636     private void dispatchScrimState(float alpha) {
637         mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
638     }
639 
dispatchScrimsVisible()640     private void dispatchScrimsVisible() {
641         final int currentScrimVisibility;
642         if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
643             currentScrimVisibility = OPAQUE;
644         } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
645             currentScrimVisibility = TRANSPARENT;
646         } else {
647             currentScrimVisibility = SEMI_TRANSPARENT;
648         }
649 
650         if (mScrimsVisibility != currentScrimVisibility) {
651             mScrimsVisibility = currentScrimVisibility;
652             mScrimVisibleListener.accept(currentScrimVisibility);
653         }
654     }
655 
getInterpolatedFraction()656     private float getInterpolatedFraction() {
657         float frac = mExpansionFraction;
658         // let's start this 20% of the way down the screen
659         frac = frac * 1.2f - 0.2f;
660         if (frac <= 0) {
661             return 0;
662         } else {
663             // woo, special effects
664             return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f))));
665         }
666     }
667 
setScrimAlpha(ScrimView scrim, float alpha)668     private void setScrimAlpha(ScrimView scrim, float alpha) {
669         if (alpha == 0f) {
670             scrim.setClickable(false);
671         } else {
672             // Eat touch events (unless dozing).
673             scrim.setClickable(mState != ScrimState.AOD);
674         }
675         updateScrim(scrim, alpha);
676     }
677 
getScrimName(ScrimView scrim)678     private String getScrimName(ScrimView scrim) {
679         if (scrim == mScrimInFront) {
680             return "front_scrim";
681         } else if (scrim == mScrimBehind) {
682             return "back_scrim";
683         } else if (scrim == mScrimForBubble) {
684             return "bubble_scrim";
685         }
686         return "unknown_scrim";
687     }
688 
updateScrimColor(View scrim, float alpha, int tint)689     private void updateScrimColor(View scrim, float alpha, int tint) {
690         alpha = Math.max(0, Math.min(1.0f, alpha));
691         if (scrim instanceof ScrimView) {
692             ScrimView scrimView = (ScrimView) scrim;
693 
694             Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
695                     (int) (alpha * 255));
696 
697             Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
698                     Color.alpha(tint));
699 
700             scrimView.setTint(tint);
701             scrimView.setViewAlpha(alpha);
702         } else {
703             scrim.setAlpha(alpha);
704         }
705         dispatchScrimsVisible();
706     }
707 
startScrimAnimation(final View scrim, float current)708     private void startScrimAnimation(final View scrim, float current) {
709         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
710         if (mAnimatorListener != null) {
711             anim.addListener(mAnimatorListener);
712         }
713         final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
714                 Color.TRANSPARENT;
715         anim.addUpdateListener(animation -> {
716             final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA);
717             final float animAmount = (float) animation.getAnimatedValue();
718             final int finalScrimTint = getCurrentScrimTint(scrim);
719             final float finalScrimAlpha = getCurrentScrimAlpha(scrim);
720             float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount);
721             alpha = MathUtils.constrain(alpha, 0f, 1f);
722             int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
723             updateScrimColor(scrim, alpha, tint);
724             dispatchScrimsVisible();
725         });
726         anim.setInterpolator(mInterpolator);
727         anim.setStartDelay(mAnimationDelay);
728         anim.setDuration(mAnimationDuration);
729         anim.addListener(new AnimatorListenerAdapter() {
730             private Callback lastCallback = mCallback;
731 
732             @Override
733             public void onAnimationEnd(Animator animation) {
734                 scrim.setTag(TAG_KEY_ANIM, null);
735                 onFinished(lastCallback);
736 
737                 dispatchScrimsVisible();
738             }
739         });
740 
741         // Cache alpha values because we might want to update this animator in the future if
742         // the user expands the panel while the animation is still running.
743         scrim.setTag(TAG_START_ALPHA, current);
744         scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim));
745 
746         scrim.setTag(TAG_KEY_ANIM, anim);
747         anim.start();
748     }
749 
getCurrentScrimAlpha(View scrim)750     private float getCurrentScrimAlpha(View scrim) {
751         if (scrim == mScrimInFront) {
752             return mInFrontAlpha;
753         } else if (scrim == mScrimBehind) {
754             return mBehindAlpha;
755         } else if (scrim == mScrimForBubble) {
756             return mBubbleAlpha;
757         } else {
758             throw new IllegalArgumentException("Unknown scrim view");
759         }
760     }
761 
getCurrentScrimTint(View scrim)762     private int getCurrentScrimTint(View scrim) {
763         if (scrim == mScrimInFront) {
764             return mInFrontTint;
765         } else if (scrim == mScrimBehind) {
766             return mBehindTint;
767         } else if (scrim == mScrimForBubble) {
768             return mBubbleTint;
769         } else {
770             throw new IllegalArgumentException("Unknown scrim view");
771         }
772     }
773 
774     @Override
onPreDraw()775     public boolean onPreDraw() {
776         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
777         mUpdatePending = false;
778         if (mCallback != null) {
779             mCallback.onStart();
780         }
781         updateScrims();
782         return true;
783     }
784 
onFinished()785     private void onFinished() {
786         onFinished(mCallback);
787     }
788 
onFinished(Callback callback)789     private void onFinished(Callback callback) {
790         if (mPendingFrameCallback != null) {
791             // No animations can finish while we're waiting on the blanking to finish
792             return;
793 
794         }
795         if (isAnimating(mScrimBehind)
796             || isAnimating(mScrimInFront)
797             || isAnimating(mScrimForBubble)) {
798             if (callback != null && callback != mCallback) {
799                 // Since we only notify the callback that we're finished once everything has
800                 // finished, we need to make sure that any changing callbacks are also invoked
801                 callback.onFinished();
802             }
803             return;
804         }
805         if (mWakeLockHeld) {
806             mWakeLock.release(TAG);
807             mWakeLockHeld = false;
808         }
809 
810         if (callback != null) {
811             callback.onFinished();
812 
813             if (callback == mCallback) {
814                 mCallback = null;
815             }
816         }
817 
818         // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
819         // At the end of the animation we need to remove the tint.
820         if (mState == ScrimState.UNLOCKED) {
821             mInFrontTint = Color.TRANSPARENT;
822             mBehindTint = Color.TRANSPARENT;
823             mBubbleTint = Color.TRANSPARENT;
824             updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
825             updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
826             updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
827         }
828     }
829 
isAnimating(View scrim)830     private boolean isAnimating(View scrim) {
831         return scrim.getTag(TAG_KEY_ANIM) != null;
832     }
833 
834     @VisibleForTesting
setAnimatorListener(Animator.AnimatorListener animatorListener)835     void setAnimatorListener(Animator.AnimatorListener animatorListener) {
836         mAnimatorListener = animatorListener;
837     }
838 
updateScrim(ScrimView scrim, float alpha)839     private void updateScrim(ScrimView scrim, float alpha) {
840         final float currentAlpha = scrim.getViewAlpha();
841 
842         ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
843         if (previousAnimator != null) {
844             // Previous animators should always be cancelled. Not doing so would cause
845             // overlap, especially on states that don't animate, leading to flickering,
846             // and in the worst case, an internal state that doesn't represent what
847             // transitionTo requested.
848             cancelAnimator(previousAnimator);
849         }
850 
851         if (mPendingFrameCallback != null) {
852             // Display is off and we're waiting.
853             return;
854         } else if (mBlankScreen) {
855             // Need to blank the display before continuing.
856             blankDisplay();
857             return;
858         } else if (!mScreenBlankingCallbackCalled) {
859             // Not blanking the screen. Letting the callback know that we're ready
860             // to replace what was on the screen before.
861             if (mCallback != null) {
862                 mCallback.onDisplayBlanked();
863                 mScreenBlankingCallbackCalled = true;
864             }
865         }
866 
867         if (scrim == mScrimBehind) {
868             dispatchScrimState(alpha);
869         }
870 
871         final boolean wantsAlphaUpdate = alpha != currentAlpha;
872         final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim);
873 
874         if (wantsAlphaUpdate || wantsTintUpdate) {
875             if (mAnimateChange) {
876                 startScrimAnimation(scrim, currentAlpha);
877             } else {
878                 // update the alpha directly
879                 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
880             }
881         }
882     }
883 
cancelAnimator(ValueAnimator previousAnimator)884     private void cancelAnimator(ValueAnimator previousAnimator) {
885         if (previousAnimator != null) {
886             previousAnimator.cancel();
887         }
888     }
889 
blankDisplay()890     private void blankDisplay() {
891         updateScrimColor(mScrimInFront, 1, Color.BLACK);
892 
893         // Notify callback that the screen is completely black and we're
894         // ready to change the display power mode
895         mPendingFrameCallback = () -> {
896             if (mCallback != null) {
897                 mCallback.onDisplayBlanked();
898                 mScreenBlankingCallbackCalled = true;
899             }
900 
901             mBlankingTransitionRunnable = () -> {
902                 mBlankingTransitionRunnable = null;
903                 mPendingFrameCallback = null;
904                 mBlankScreen = false;
905                 // Try again.
906                 updateScrims();
907             };
908 
909             // Setting power states can happen after we push out the frame. Make sure we
910             // stay fully opaque until the power state request reaches the lower levels.
911             final int delay = mScreenOn ? 32 : 500;
912             if (DEBUG) {
913                 Log.d(TAG, "Fading out scrims with delay: " + delay);
914             }
915             mHandler.postDelayed(mBlankingTransitionRunnable, delay);
916         };
917         doOnTheNextFrame(mPendingFrameCallback);
918     }
919 
920     /**
921      * Executes a callback after the frame has hit the display.
922      *
923      * @param callback What to run.
924      */
925     @VisibleForTesting
doOnTheNextFrame(Runnable callback)926     protected void doOnTheNextFrame(Runnable callback) {
927         // Just calling View#postOnAnimation isn't enough because the frame might not have reached
928         // the display yet. A timeout is the safest solution.
929         mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
930     }
931 
getBackgroundColor()932     public int getBackgroundColor() {
933         int color = mColors.getMainColor();
934         return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
935                 Color.red(color), Color.green(color), Color.blue(color));
936     }
937 
setScrimBehindChangeRunnable(Runnable changeRunnable)938     public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
939         mScrimBehind.setChangeRunnable(changeRunnable);
940     }
941 
setCurrentUser(int currentUser)942     public void setCurrentUser(int currentUser) {
943         // Don't care in the base class.
944     }
945 
946     @Override
onColorsChanged(ColorExtractor colorExtractor, int which)947     public void onColorsChanged(ColorExtractor colorExtractor, int which) {
948         mColors = mColorExtractor.getNeutralColors();
949         mNeedsDrawableColorUpdate = true;
950         scheduleUpdate();
951     }
952 
953     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)954     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
955         pw.println(" ScrimController: ");
956         pw.print("  state: ");
957         pw.println(mState);
958 
959         pw.print("  frontScrim:");
960         pw.print(" viewAlpha=");
961         pw.print(mScrimInFront.getViewAlpha());
962         pw.print(" alpha=");
963         pw.print(mInFrontAlpha);
964         pw.print(" tint=0x");
965         pw.println(Integer.toHexString(mScrimInFront.getTint()));
966 
967         pw.print("  backScrim:");
968         pw.print(" viewAlpha=");
969         pw.print(mScrimBehind.getViewAlpha());
970         pw.print(" alpha=");
971         pw.print(mBehindAlpha);
972         pw.print(" tint=0x");
973         pw.println(Integer.toHexString(mScrimBehind.getTint()));
974 
975         pw.print("  bubbleScrim:");
976         pw.print(" viewAlpha=");
977         pw.print(mScrimForBubble.getViewAlpha());
978         pw.print(" alpha=");
979         pw.print(mBubbleAlpha);
980         pw.print(" tint=0x");
981         pw.println(Integer.toHexString(mScrimForBubble.getTint()));
982 
983         pw.print("  mTracking=");
984         pw.println(mTracking);
985         pw.print("  mDefaultScrimAlpha=");
986         pw.println(mDefaultScrimAlpha);
987         pw.print("  mExpansionFraction=");
988         pw.println(mExpansionFraction);
989     }
990 
setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode)991     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
992         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
993         ScrimState[] states = ScrimState.values();
994         for (int i = 0; i < states.length; i++) {
995             states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
996         }
997     }
998 
999     /**
1000      * Interrupts blanking transitions once the display notifies that it's already on.
1001      */
onScreenTurnedOn()1002     public void onScreenTurnedOn() {
1003         mScreenOn = true;
1004         if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
1005             if (DEBUG) {
1006                 Log.d(TAG, "Shorter blanking because screen turned on. All good.");
1007             }
1008             mHandler.removeCallbacks(mBlankingTransitionRunnable);
1009             mBlankingTransitionRunnable.run();
1010         }
1011     }
1012 
onScreenTurnedOff()1013     public void onScreenTurnedOff() {
1014         mScreenOn = false;
1015     }
1016 
setExpansionAffectsAlpha(boolean expansionAffectsAlpha)1017     public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
1018         mExpansionAffectsAlpha = expansionAffectsAlpha;
1019         if (expansionAffectsAlpha) {
1020             applyAndDispatchExpansion();
1021         }
1022     }
1023 
setKeyguardOccluded(boolean keyguardOccluded)1024     public void setKeyguardOccluded(boolean keyguardOccluded) {
1025         mKeyguardOccluded = keyguardOccluded;
1026         updateScrims();
1027     }
1028 
setHasBackdrop(boolean hasBackdrop)1029     public void setHasBackdrop(boolean hasBackdrop) {
1030         for (ScrimState state : ScrimState.values()) {
1031             state.setHasBackdrop(hasBackdrop);
1032         }
1033 
1034         // Backdrop event may arrive after state was already applied,
1035         // in this case, back-scrim needs to be re-evaluated
1036         if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
1037             float newBehindAlpha = mState.getBehindAlpha();
1038             if (isNaN(newBehindAlpha)) {
1039                 throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
1040                         + ", back: " + mBehindAlpha);
1041             }
1042             if (mBehindAlpha != newBehindAlpha) {
1043                 mBehindAlpha = newBehindAlpha;
1044                 updateScrims();
1045             }
1046         }
1047     }
1048 
setKeyguardFadingAway(boolean fadingAway, long duration)1049     private void setKeyguardFadingAway(boolean fadingAway, long duration) {
1050         for (ScrimState state : ScrimState.values()) {
1051             state.setKeyguardFadingAway(fadingAway, duration);
1052         }
1053     }
1054 
setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview)1055     public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
1056         for (ScrimState state : ScrimState.values()) {
1057             state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
1058         }
1059     }
1060 
1061     public interface Callback {
onStart()1062         default void onStart() {
1063         }
1064 
onDisplayBlanked()1065         default void onDisplayBlanked() {
1066         }
1067 
onFinished()1068         default void onFinished() {
1069         }
1070 
onCancelled()1071         default void onCancelled() {
1072         }
1073     }
1074 
1075     /**
1076      * Simple keyguard callback that updates scrims when keyguard visibility changes.
1077      */
1078     private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
1079 
1080         @Override
onKeyguardVisibilityChanged(boolean showing)1081         public void onKeyguardVisibilityChanged(boolean showing) {
1082             mNeedsDrawableColorUpdate = true;
1083             scheduleUpdate();
1084         }
1085     }
1086 }
1087