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