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