Home | History | Annotate | Download | only in phone
      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 
    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 
    207     public void transitionTo(ScrimState state) {
    208         transitionTo(state, null);
    209     }
    210 
    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 
    305     public ScrimState getState() {
    306         return mState;
    307     }
    308 
    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 
    318     public void onTrackingStarted() {
    319         mTracking = true;
    320         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
    321     }
    322 
    323     public void onExpandingFinished() {
    324         mTracking = false;
    325     }
    326 
    327     @VisibleForTesting
    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 
    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      */
    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 
    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 
    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      */
    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      */
    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      */
    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 
    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 
    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 
    526     private void dispatchScrimState(float alpha) {
    527         mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
    528     }
    529 
    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 
    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 
    558     private void setScrimBehindAlpha(float alpha) {
    559         setScrimAlpha(mScrimBehind, alpha);
    560     }
    561 
    562     private void setScrimInFrontAlpha(float alpha) {
    563         setScrimAlpha(mScrimInFront, alpha);
    564     }
    565 
    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 
    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 
    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 
    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 
    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
    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 
    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 
    692     private boolean isAnimating(View scrim) {
    693         return scrim.getTag(TAG_KEY_ANIM) != null;
    694     }
    695 
    696     public void setDrawBehindAsSrc(boolean asSrc) {
    697         mScrimBehind.setDrawAsSrc(asSrc);
    698     }
    699 
    700     @VisibleForTesting
    701     void setOnAnimationFinished(Runnable onAnimationFinished) {
    702         mOnAnimationFinished = onAnimationFinished;
    703     }
    704 
    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
    759     protected void cancelAnimator(ValueAnimator previousAnimator) {
    760         if (previousAnimator != null) {
    761             previousAnimator.cancel();
    762         }
    763     }
    764 
    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
    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
    807     protected Handler getHandler() {
    808         return Handler.getMain();
    809     }
    810 
    811     public void setExcludedBackgroundArea(Rect area) {
    812         mScrimBehind.setExcludedArea(area);
    813     }
    814 
    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 
    821     public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
    822         mScrimBehind.setChangeRunnable(changeRunnable);
    823     }
    824 
    825     public void setCurrentUser(int currentUser) {
    826         // Don't care in the base class.
    827     }
    828 
    829     @Override
    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
    846     protected WakeLock createWakeLock() {
    847          return new DelayedWakeLock(getHandler(),
    848                 WakeLock.createPartial(mContext, "Scrims"));
    849     }
    850 
    851     @Override
    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 
    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      */
    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 
    889     public void onScreenTurnedOff() {
    890         mScreenOn = false;
    891     }
    892 
    893     public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
    894         mExpansionAffectsAlpha = expansionAffectsAlpha;
    895     }
    896 
    897     public void setKeyguardOccluded(boolean keyguardOccluded) {
    898         mKeyguardOccluded = keyguardOccluded;
    899         updateScrims();
    900     }
    901 
    902     public interface Callback {
    903         default void onStart() {
    904         }
    905         default void onDisplayBlanked() {
    906         }
    907         default void onFinished() {
    908         }
    909         default void onCancelled() {
    910         }
    911     }
    912 }
    913