blob: 0cf9a530afcb7ff8376d09ffdea26c91735110eb [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.statusbar.phone;
import static java.lang.Float.isNaN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.app.AlarmManager;
import android.graphics.Color;
import android.os.Handler;
import android.os.Trace;
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.function.TriConsumer;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
@SysUISingleton
public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
static final String TAG = "ScrimController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// debug mode colors scrims with below debug colors, irrespectively of which state they're in
public static final boolean DEBUG_MODE = false;
public static final int DEBUG_NOTIFICATIONS_TINT = Color.RED;
public static final int DEBUG_FRONT_TINT = Color.GREEN;
public static final int DEBUG_BEHIND_TINT = Color.BLUE;
/**
* General scrim animation duration.
*/
public static final long ANIMATION_DURATION = 220;
/**
* Longer duration, currently only used when going to AOD.
*/
public static final long ANIMATION_DURATION_LONG = 1000;
/**
* When both scrims have 0 alpha.
*/
public static final int TRANSPARENT = 0;
/**
* When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
*/
public static final int SEMI_TRANSPARENT = 1;
/**
* When at least 1 scrim is fully opaque (alpha set to 1.)
*/
public static final int OPAQUE = 2;
private boolean mClipsQsScrim;
/**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade.
*/
private float mTransitionToFullShadeProgress;
/**
* Same as {@link #mTransitionToFullShadeProgress}, but specifically for the notifications scrim
* on the lock screen.
*
* On split shade lock screen we want the different scrims to fade in at different times and
* rates.
*/
private float mTransitionToLockScreenFullShadeNotificationsProgress;
/**
* If we're currently transitioning to the full shade.
*/
private boolean mTransitioningToFullShade;
/**
* Is there currently an unocclusion animation running. Used to avoid bright flickers
* of the notification scrim.
*/
private boolean mUnOcclusionAnimationRunning;
/**
* The percentage of the bouncer which is hidden. If 1, the bouncer is completely hidden. If
* 0, the bouncer is visible.
*/
@FloatRange(from = 0, to = 1)
private float mBouncerHiddenFraction = KeyguardBouncer.EXPANSION_HIDDEN;
/**
* Set whether an unocclusion animation is currently running on the notification panel. Used
* to avoid bright flickers of the notification scrim.
*/
public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) {
mUnOcclusionAnimationRunning = unocclusionAnimationRunning;
}
@IntDef(prefix = {"VISIBILITY_"}, value = {
TRANSPARENT,
SEMI_TRANSPARENT,
OPAQUE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ScrimVisibility {
}
/**
* Default alpha value for most scrims.
*/
protected static final float KEYGUARD_SCRIM_ALPHA = 0.2f;
/**
* Scrim opacity when the phone is about to wake-up.
*/
public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f;
/**
* The default scrim under the shade and dialogs.
* This should not be lower than 0.54, otherwise we won't pass GAR.
*/
public static final float BUSY_SCRIM_ALPHA = 1f;
/**
* Scrim opacity that can have text on top.
*/
public static final float GAR_SCRIM_ALPHA = 0.6f;
static final int TAG_KEY_ANIM = R.id.scrim;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private static final float NOT_INITIALIZED = -1;
private ScrimState mState = ScrimState.UNINITIALIZED;
private ScrimView mScrimInFront;
private ScrimView mNotificationsScrim;
private ScrimView mScrimBehind;
private Runnable mScrimBehindChangeRunnable;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
private final DockManager mDockManager;
private final AlarmTimeout mTimeTicker;
private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
private final Handler mHandler;
private final Executor mMainExecutor;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
private float mAdditionalScrimBehindAlphaKeyguard = 0f;
// Combined scrim behind keyguard alpha of default scrim + additional scrim
// (if wallpaper dimming is applied).
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
private final float mDefaultScrimAlpha;
private float mRawPanelExpansionFraction;
private float mPanelScrimMinFraction;
// Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction
private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization
private float mQsExpansion;
private boolean mQsBottomVisible;
private boolean mAnimatingPanelExpansionOnUnlock; // don't animate scrim
private boolean mDarkenWhileDragging;
private boolean mExpansionAffectsAlpha = true;
private boolean mAnimateChange;
private boolean mUpdatePending;
private boolean mTracking;
private long mAnimationDuration = -1;
private long mAnimationDelay;
private Animator.AnimatorListener mAnimatorListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
private float mInFrontAlpha = NOT_INITIALIZED;
private float mBehindAlpha = NOT_INITIALIZED;
private float mNotificationsAlpha = NOT_INITIALIZED;
private int mInFrontTint;
private int mBehindTint;
private int mNotificationsTint;
private boolean mWallpaperVisibilityTimedOut;
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
private Callback mCallback;
private boolean mWallpaperSupportsAmbientMode;
private boolean mScreenOn;
// Scrim blanking callbacks
private Runnable mPendingFrameCallback;
private Runnable mBlankingTransitionRunnable;
private final WakeLock mWakeLock;
private boolean mWakeLockHeld;
private boolean mKeyguardOccluded;
@Inject
public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters,
AlarmManager alarmManager, KeyguardStateController keyguardStateController,
DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager,
ConfigurationController configurationController, @Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
mHandler = handler;
mMainExecutor = mainExecutor;
mScreenOffAnimationController = screenOffAnimationController;
mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
"hide_aod_wallpaper", mHandler);
mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build();
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
mDozeParameters = dozeParameters;
mDockManager = dockManager;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
setKeyguardFadingAway(keyguardStateController.isKeyguardFadingAway(),
keyguardStateController.getKeyguardFadingAwayDuration());
}
});
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
public void onThemeChanged() {
ScrimController.this.onThemeChanged();
}
@Override
public void onUiModeChanged() {
ScrimController.this.onThemeChanged();
}
});
mColors = new GradientColors();
}
/**
* Attach the controller to the supplied views.
*/
public void attachViews(ScrimView behindScrim, ScrimView notificationsScrim,
ScrimView scrimInFront) {
mNotificationsScrim = notificationsScrim;
mScrimBehind = behindScrim;
mScrimInFront = scrimInFront;
updateThemeColors();
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
mNotificationsScrim.enableRoundedCorners(true);
if (mScrimBehindChangeRunnable != null) {
mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
mScrimBehindChangeRunnable = null;
}
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
}
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mNotificationsScrim.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
}
/**
* Sets corner radius of scrims.
*/
public void setScrimCornerRadius(int radius) {
if (mScrimBehind == null || mNotificationsScrim == null) {
return;
}
mScrimBehind.setCornerRadius(radius);
mNotificationsScrim.setCornerRadius(radius);
}
void setScrimVisibleListener(Consumer<Integer> listener) {
mScrimVisibleListener = listener;
}
public void transitionTo(ScrimState state) {
transitionTo(state, null);
}
public void transitionTo(ScrimState state, Callback callback) {
if (state == mState) {
// Call the callback anyway, unless it's already enqueued
if (callback != null && mCallback != callback) {
callback.onFinished();
}
return;
} else if (DEBUG) {
Log.d(TAG, "State changed to: " + state);
}
if (state == ScrimState.UNINITIALIZED) {
throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
}
final ScrimState oldState = mState;
mState = state;
Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal());
if (mCallback != null) {
mCallback.onCancelled();
}
mCallback = callback;
state.prepare(oldState);
mScreenBlankingCallbackCalled = false;
mAnimationDelay = 0;
mBlankScreen = state.getBlanksScreen();
mAnimateChange = state.getAnimateChange();
mAnimationDuration = state.getAnimationDuration();
applyState();
// Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
// We need to disable focus otherwise AOD would end up with a gray overlay.
mScrimInFront.setFocusable(!state.isLowPowerState());
mScrimBehind.setFocusable(!state.isLowPowerState());
mNotificationsScrim.setFocusable(!state.isLowPowerState());
mScrimInFront.setBlendWithMainColor(state.shouldBlendWithMainColor());
// Cancel blanking transitions that were pending before we requested a new state
if (mPendingFrameCallback != null) {
mScrimBehind.removeCallbacks(mPendingFrameCallback);
mPendingFrameCallback = null;
}
if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
mHandler.removeCallbacks(mBlankingTransitionRunnable);
mBlankingTransitionRunnable = null;
}
// Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
// to do the same when you're just showing the brightness mirror.
mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
// The device might sleep if it's entering AOD, we need to make sure that
// the animation plays properly until the last frame.
// It's important to avoid holding the wakelock unless necessary because
// WakeLock#aqcuire will trigger an IPC and will cause jank.
if (mState.isLowPowerState()) {
holdWakeLock();
}
// AOD wallpapers should fade away after a while.
// Docking pulses may take a long time, wallpapers should also fade away after a while.
mWallpaperVisibilityTimedOut = false;
if (shouldFadeAwayWallpaper()) {
DejankUtils.postAfterTraversal(() -> {
mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
});
} else {
DejankUtils.postAfterTraversal(mTimeTicker::cancel);
}
if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
mAnimationDelay = CentralSurfaces.FADE_KEYGUARD_START_DELAY;
scheduleUpdate();
} else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze
&& (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED))
|| (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
// Scheduling a frame isn't enough when:
// • Leaving doze and we need to modify scrim color immediately
// • ColorFade will not kick-in and scrim cannot wait for pre-draw.
onPreDraw();
} else {
// Schedule a frame
scheduleUpdate();
}
dispatchBackScrimState(mScrimBehind.getViewAlpha());
}
private boolean shouldFadeAwayWallpaper() {
if (!mWallpaperSupportsAmbientMode) {
return false;
}
if (mState == ScrimState.AOD
&& (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
return true;
}
return false;
}
public ScrimState getState() {
return mState;
}
/**
* Sets the additional scrim behind alpha keyguard that would be blended with the default scrim
* by applying alpha composition on both values.
*
* @param additionalScrimAlpha alpha value of additional scrim behind alpha keyguard.
*/
protected void setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha) {
mAdditionalScrimBehindAlphaKeyguard = additionalScrimAlpha;
}
/**
* Applies alpha composition to the default scrim behind alpha keyguard and the additional
* scrim alpha, and sets this value to the scrim behind alpha keyguard.
* This is used to apply additional keyguard dimming on top of the default scrim alpha value.
*/
protected void applyCompositeAlphaOnScrimBehindKeyguard() {
int compositeAlpha = ColorUtils.compositeAlpha(
(int) (255 * mAdditionalScrimBehindAlphaKeyguard),
(int) (255 * KEYGUARD_SCRIM_ALPHA));
float keyguardScrimAlpha = (float) compositeAlpha / 255;
setScrimBehindValues(keyguardScrimAlpha);
}
/**
* Sets the scrim behind alpha keyguard values. This is how much the keyguard will be dimmed.
*
* @param scrimBehindAlphaKeyguard alpha value of the scrim behind
*/
private void setScrimBehindValues(float scrimBehindAlphaKeyguard) {
mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
}
scheduleUpdate();
}
public void onTrackingStarted() {
mTracking = true;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
mAnimatingPanelExpansionOnUnlock = false;
}
}
public void onExpandingFinished() {
mTracking = false;
setUnocclusionAnimationRunning(false);
}
@VisibleForTesting
protected void onHideWallpaperTimeout() {
if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
return;
}
holdWakeLock();
mWallpaperVisibilityTimedOut = true;
mAnimateChange = true;
mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
scheduleUpdate();
}
private void holdWakeLock() {
if (!mWakeLockHeld) {
if (mWakeLock != null) {
mWakeLockHeld = true;
mWakeLock.acquire(TAG);
} else {
Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
}
}
}
/**
* Current state of the shade expansion when pulling it from the top.
* This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
*
* The expansion fraction is tied to the scrim opacity.
*
* See {@link ScrimShadeTransitionController#onPanelExpansionChanged}.
*
* @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
*/
public void setRawPanelExpansionFraction(
@FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) {
if (isNaN(rawPanelExpansionFraction)) {
throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN");
}
mRawPanelExpansionFraction = rawPanelExpansionFraction;
calculateAndUpdatePanelExpansion();
}
/** See {@link NotificationPanelViewController#setPanelScrimMinFraction(float)}. */
public void setPanelScrimMinFraction(float minFraction) {
if (isNaN(minFraction)) {
throw new IllegalArgumentException("minFraction should not be NaN");
}
mPanelScrimMinFraction = minFraction;
calculateAndUpdatePanelExpansion();
}
private void calculateAndUpdatePanelExpansion() {
float panelExpansionFraction = mRawPanelExpansionFraction;
if (mPanelScrimMinFraction < 1.0f) {
panelExpansionFraction = Math.max(
(mRawPanelExpansionFraction - mPanelScrimMinFraction)
/ (1.0f - mPanelScrimMinFraction),
0);
}
if (mPanelExpansionFraction != panelExpansionFraction) {
if (panelExpansionFraction != 0f
&& mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
mAnimatingPanelExpansionOnUnlock = true;
} else if (panelExpansionFraction == 0f) {
mAnimatingPanelExpansionOnUnlock = false;
}
mPanelExpansionFraction = panelExpansionFraction;
boolean relevantState = (mState == ScrimState.UNLOCKED
|| mState == ScrimState.KEYGUARD
|| mState == ScrimState.DREAMING
|| mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING);
if (!(relevantState && mExpansionAffectsAlpha) || mAnimatingPanelExpansionOnUnlock) {
return;
}
applyAndDispatchState();
}
}
/**
* Set the amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade.
*
* @param progress the progress for all scrims.
* @param lockScreenNotificationsProgress the progress specifically for the notifications scrim.
*/
public void setTransitionToFullShadeProgress(float progress,
float lockScreenNotificationsProgress) {
if (progress != mTransitionToFullShadeProgress || lockScreenNotificationsProgress
!= mTransitionToLockScreenFullShadeNotificationsProgress) {
mTransitionToFullShadeProgress = progress;
mTransitionToLockScreenFullShadeNotificationsProgress = lockScreenNotificationsProgress;
setTransitionToFullShade(progress > 0.0f || lockScreenNotificationsProgress > 0.0f);
applyAndDispatchState();
}
}
/**
* Set if we're currently transitioning to the full shade
*/
private void setTransitionToFullShade(boolean transitioning) {
if (transitioning != mTransitioningToFullShade) {
mTransitioningToFullShade = transitioning;
if (transitioning) {
// Let's make sure the shade locked is ready
ScrimState.SHADE_LOCKED.prepare(mState);
}
}
}
/**
* Set bounds for notifications background, all coordinates are absolute
*/
public void setNotificationsBounds(float left, float top, float right, float bottom) {
if (mClipsQsScrim) {
// notification scrim's rounded corners are anti-aliased, but clipping of the QS/behind
// scrim can't be and it's causing jagged corners. That's why notification scrim needs
// to overlap QS scrim by one pixel horizontally (left - 1 and right + 1)
// see: b/186644628
mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom);
mScrimBehind.setBottomEdgePosition((int) top);
} else {
mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
}
}
/**
* Sets the amount of vertical over scroll that should be performed on the notifications scrim.
*/
public void setNotificationsOverScrollAmount(int overScrollAmount) {
mNotificationsScrim.setTranslationY(overScrollAmount);
}
/**
* Current state of the QuickSettings when pulling it from the top.
*
* @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
* @param qsPanelBottomY Absolute Y position of qs panel bottom
*/
public void setQsPosition(float expansionFraction, int qsPanelBottomY) {
if (isNaN(expansionFraction)) {
return;
}
expansionFraction = ShadeInterpolation.getNotificationScrimAlpha(expansionFraction);
boolean qsBottomVisible = qsPanelBottomY > 0;
if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
mQsExpansion = expansionFraction;
mQsBottomVisible = qsBottomVisible;
boolean relevantState = (mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.KEYGUARD
|| mState == ScrimState.PULSING);
if (!(relevantState && mExpansionAffectsAlpha)) {
return;
}
applyAndDispatchState();
}
}
/**
* Updates the percentage of the bouncer which is hidden.
*/
public void setBouncerHiddenFraction(@FloatRange(from = 0, to = 1) float bouncerHiddenAmount) {
if (mBouncerHiddenFraction == bouncerHiddenAmount) {
return;
}
mBouncerHiddenFraction = bouncerHiddenAmount;
if (mState == ScrimState.DREAMING) {
// Only the dreaming state requires this for the scrim calculation, so we should
// only trigger an update if dreaming.
applyAndDispatchState();
}
}
/**
* If QS and notification scrims should not overlap, and should be clipped to each other's
* bounds instead.
*/
public void setClipsQsScrim(boolean clipScrim) {
if (clipScrim == mClipsQsScrim) {
return;
}
mClipsQsScrim = clipScrim;
for (ScrimState state : ScrimState.values()) {
state.setClipQsScrim(mClipsQsScrim);
}
if (mScrimBehind != null) {
mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim);
}
if (mState != ScrimState.UNINITIALIZED) {
// the clipScrimState has changed, let's reprepare ourselves
mState.prepare(mState);
applyAndDispatchState();
}
}
@VisibleForTesting
public boolean getClipQsScrim() {
return mClipsQsScrim;
}
private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
if (scrim == null) {
return;
}
float alpha = getCurrentScrimAlpha(scrim);
boolean qsScrimPullingDown = scrim == mScrimBehind && mQsBottomVisible;
if (isAnimating(scrim) && !qsScrimPullingDown) {
// Adapt current animation.
ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
float relativeDiff = alpha - previousEndValue;
float newStartValue = previousStartValue + relativeDiff;
scrim.setTag(TAG_START_ALPHA, newStartValue);
scrim.setTag(TAG_END_ALPHA, alpha);
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
} else {
// Set animation.
updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
}
}
private void applyState() {
mInFrontTint = mState.getFrontTint();
mBehindTint = mState.getBehindTint();
mNotificationsTint = mState.getNotifTint();
mInFrontAlpha = mState.getFrontAlpha();
mBehindAlpha = mState.getBehindAlpha();
mNotificationsAlpha = mState.getNotifAlpha();
assertAlphasValid();
if (!mExpansionAffectsAlpha) {
return;
}
if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
// Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
// because we're doing the screen off animation OR the shade is collapsing because
// we're playing the unlock animation
if (!mScreenOffAnimationController.shouldExpandNotifications()
&& !mAnimatingPanelExpansionOnUnlock) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
if (mClipsQsScrim) {
mBehindAlpha = 1;
mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
} else {
mBehindAlpha = behindFraction * mDefaultScrimAlpha;
// Delay fade-in of notification scrim a bit further, to coincide with the
// view fade in. Otherwise the empty panel can be quite jarring.
mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
mPanelExpansionFraction);
}
mBehindTint = mState.getBehindTint();
mInFrontAlpha = 0;
}
if (mBouncerHiddenFraction != KeyguardBouncer.EXPANSION_HIDDEN) {
final float interpolatedFraction =
BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
mBouncerHiddenFraction);
mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, mBehindAlpha,
interpolatedFraction);
mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
mBehindTint,
interpolatedFraction);
}
} else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
mBehindAlpha = behindFraction * mDefaultScrimAlpha;
mNotificationsAlpha = mBehindAlpha;
if (mClipsQsScrim) {
mBehindAlpha = 1;
mBehindTint = Color.BLACK;
}
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING) {
Pair<Integer, Float> result = calculateBackStateForState(mState);
int behindTint = result.first;
float behindAlpha = result.second;
if (mTransitionToFullShadeProgress > 0.0f) {
Pair<Integer, Float> shadeResult = calculateBackStateForState(
ScrimState.SHADE_LOCKED);
behindAlpha = MathUtils.lerp(behindAlpha, shadeResult.second,
mTransitionToFullShadeProgress);
behindTint = ColorUtils.blendARGB(behindTint, shadeResult.first,
mTransitionToFullShadeProgress);
}
mInFrontAlpha = mState.getFrontAlpha();
if (mClipsQsScrim) {
mNotificationsAlpha = behindAlpha;
mNotificationsTint = behindTint;
mBehindAlpha = 1;
mBehindTint = Color.BLACK;
} else {
mBehindAlpha = behindAlpha;
if (mState == ScrimState.SHADE_LOCKED) {
// going from KEYGUARD to SHADE_LOCKED state
mNotificationsAlpha = getInterpolatedFraction();
} else {
mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion);
}
if (mState == ScrimState.KEYGUARD
&& mTransitionToLockScreenFullShadeNotificationsProgress > 0.0f) {
// Interpolate the notification alpha when transitioning!
mNotificationsAlpha = MathUtils.lerp(
mNotificationsAlpha,
getInterpolatedFraction(),
mTransitionToLockScreenFullShadeNotificationsProgress);
}
mNotificationsTint = mState.getNotifTint();
mBehindTint = behindTint;
}
// At the end of a launch animation over the lockscreen, the state is either KEYGUARD or
// SHADE_LOCKED and this code is called. We have to set the notification alpha to 0
// otherwise there is a flicker to its previous value.
boolean hideNotificationScrim = (mState == ScrimState.KEYGUARD
&& mTransitionToFullShadeProgress == 0
&& mQsExpansion == 0
&& !mClipsQsScrim);
if (mKeyguardOccluded || hideNotificationScrim) {
mNotificationsAlpha = 0;
}
if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) {
// We're unoccluding the keyguard and don't want to have a bright flash.
mNotificationsAlpha = ScrimState.KEYGUARD.getNotifAlpha();
mNotificationsTint = ScrimState.KEYGUARD.getNotifTint();
mBehindAlpha = ScrimState.KEYGUARD.getBehindAlpha();
mBehindTint = ScrimState.KEYGUARD.getBehindTint();
}
}
if (mState != ScrimState.UNLOCKED) {
mAnimatingPanelExpansionOnUnlock = false;
}
assertAlphasValid();
}
private void assertAlphasValid() {
if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
+ mNotificationsAlpha);
}
}
private Pair<Integer, Float> calculateBackStateForState(ScrimState state) {
// Either darken of make the scrim transparent when you
// pull down the shade
float interpolatedFract = getInterpolatedFraction();
float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha();
float behindAlpha;
int behindTint;
if (mDarkenWhileDragging) {
behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
interpolatedFract);
} else {
behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
interpolatedFract);
}
if (mClipsQsScrim) {
behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
state.getNotifTint(), interpolatedFract);
} else {
behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
state.getBehindTint(), interpolatedFract);
}
if (mQsExpansion > 0) {
behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
float tintProgress = mQsExpansion;
if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
// this is case of - on lockscreen - going from expanded QS to bouncer.
// Because mQsExpansion is already interpolated and transition between tints
// is too slow, we want to speed it up and make it more aligned to bouncer
// showing up progress. This issue is visible on large screens, both portrait and
// split shade because then transition is between very different tints
tintProgress = BouncerPanelExpansionCalculator
.showBouncerProgress(mPanelExpansionFraction);
}
int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint()
: ScrimState.SHADE_LOCKED.getBehindTint();
behindTint = ColorUtils.blendARGB(behindTint, stateTint, tintProgress);
}
// If the keyguard is going away, we should not be opaque.
if (mKeyguardStateController.isKeyguardGoingAway()) {
behindAlpha = 0f;
}
return new Pair<>(behindTint, behindAlpha);
}
private void applyAndDispatchState() {
applyState();
if (mUpdatePending) {
return;
}
setOrAdaptCurrentAnimation(mScrimBehind);
setOrAdaptCurrentAnimation(mNotificationsScrim);
setOrAdaptCurrentAnimation(mScrimInFront);
dispatchBackScrimState(mScrimBehind.getViewAlpha());
// Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
// and docking.
if (mWallpaperVisibilityTimedOut) {
mWallpaperVisibilityTimedOut = false;
DejankUtils.postAfterTraversal(() -> {
mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
});
}
}
/**
* Sets the front scrim opacity in AOD so it's not as bright.
* <p>
* Displays usually don't support multiple dimming settings when in low power mode.
* The workaround is to modify the front scrim opacity when in AOD, so it's not as
* bright when you're at the movies or lying down on bed.
* <p>
* This value will be lost during transitions and only updated again after the the
* device is dozing when the light sensor is on.
*/
public void setAodFrontScrimAlpha(float alpha) {
if (mInFrontAlpha != alpha && shouldUpdateFrontScrimAlpha()) {
mInFrontAlpha = alpha;
updateScrims();
}
mState.AOD.setAodFrontScrimAlpha(alpha);
mState.PULSING.setAodFrontScrimAlpha(alpha);
}
private boolean shouldUpdateFrontScrimAlpha() {
if (mState == ScrimState.AOD
&& (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
return true;
}
if (mState == ScrimState.PULSING) {
return true;
}
return false;
}
/**
* If the lock screen sensor is active.
*/
public void setWakeLockScreenSensorActive(boolean active) {
for (ScrimState state : ScrimState.values()) {
state.setWakeLockScreenSensorActive(active);
}
if (mState == ScrimState.PULSING) {
float newBehindAlpha = mState.getBehindAlpha();
if (mBehindAlpha != newBehindAlpha) {
mBehindAlpha = newBehindAlpha;
if (isNaN(mBehindAlpha)) {
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ ", back: " + mBehindAlpha);
}
updateScrims();
}
}
}
protected void scheduleUpdate() {
if (mUpdatePending || mScrimBehind == null) return;
// Make sure that a frame gets scheduled.
mScrimBehind.invalidate();
mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
mUpdatePending = true;
}
protected void updateScrims() {
// Make sure we have the right gradients and their opacities will satisfy GAR.
if (mNeedsDrawableColorUpdate) {
mNeedsDrawableColorUpdate = false;
// Only animate scrim color if the scrim view is actually visible
boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
boolean animateBehindScrim = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
boolean animateScrimNotifications = mNotificationsScrim.getViewAlpha() != 0
&& !mBlankScreen;
mScrimInFront.setColors(mColors, animateScrimInFront);
mScrimBehind.setColors(mColors, animateBehindScrim);
mNotificationsScrim.setColors(mColors, animateScrimNotifications);
dispatchBackScrimState(mScrimBehind.getViewAlpha());
}
// We want to override the back scrim opacity for the AOD state
// when it's time to fade the wallpaper away.
boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
&& mWallpaperVisibilityTimedOut;
// We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
boolean hideFlagShowWhenLockedActivities =
(mState == ScrimState.PULSING || mState == ScrimState.AOD)
&& mKeyguardOccluded;
if (aodWallpaperTimeout || hideFlagShowWhenLockedActivities) {
mBehindAlpha = 1;
}
// Prevent notification scrim flicker when transitioning away from keyguard.
if (mKeyguardStateController.isKeyguardGoingAway()) {
mNotificationsAlpha = 0;
}
// Prevent flickering for activities above keyguard and quick settings in keyguard.
if (mKeyguardOccluded
&& (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED)) {
mBehindAlpha = 0;
mNotificationsAlpha = 0;
}
setScrimAlpha(mScrimInFront, mInFrontAlpha);
setScrimAlpha(mScrimBehind, mBehindAlpha);
setScrimAlpha(mNotificationsScrim, mNotificationsAlpha);
// The animation could have all already finished, let's call onFinished just in case
onFinished(mState);
dispatchScrimsVisible();
}
private void dispatchBackScrimState(float alpha) {
// When clipping QS, the notification scrim is the one that feels behind.
// mScrimBehind will be drawing black and its opacity will always be 1.
if (mClipsQsScrim && mQsBottomVisible) {
alpha = mNotificationsAlpha;
}
mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
}
private void dispatchScrimsVisible() {
final ScrimView backScrim = mClipsQsScrim ? mNotificationsScrim : mScrimBehind;
final int currentScrimVisibility;
if (mScrimInFront.getViewAlpha() == 1 || backScrim.getViewAlpha() == 1) {
currentScrimVisibility = OPAQUE;
} else if (mScrimInFront.getViewAlpha() == 0 && backScrim.getViewAlpha() == 0) {
currentScrimVisibility = TRANSPARENT;
} else {
currentScrimVisibility = SEMI_TRANSPARENT;
}
if (mScrimsVisibility != currentScrimVisibility) {
mScrimsVisibility = currentScrimVisibility;
mScrimVisibleListener.accept(currentScrimVisibility);
}
}
private float getInterpolatedFraction() {
if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
return BouncerPanelExpansionCalculator
.aboutToShowBouncerProgress(mPanelExpansionFraction);
}
return ShadeInterpolation.getNotificationScrimAlpha(mPanelExpansionFraction);
}
private void setScrimAlpha(ScrimView scrim, float alpha) {
if (alpha == 0f) {
scrim.setClickable(false);
} else {
// Eat touch events (unless dozing).
scrim.setClickable(mState != ScrimState.AOD);
}
updateScrim(scrim, alpha);
}
private String getScrimName(ScrimView scrim) {
if (scrim == mScrimInFront) {
return "front_scrim";
} else if (scrim == mScrimBehind) {
return "behind_scrim";
} else if (scrim == mNotificationsScrim) {
return "notifications_scrim";
}
return "unknown_scrim";
}
private void updateScrimColor(View scrim, float alpha, int tint) {
alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
if (DEBUG_MODE) {
tint = getDebugScrimTint(scrimView);
}
Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
(int) (alpha * 255));
Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
Color.alpha(tint));
scrimView.setTint(tint);
scrimView.setViewAlpha(alpha);
} else {
scrim.setAlpha(alpha);
}
dispatchScrimsVisible();
}
private int getDebugScrimTint(ScrimView scrim) {
if (scrim == mScrimBehind) return DEBUG_BEHIND_TINT;
if (scrim == mScrimInFront) return DEBUG_FRONT_TINT;
if (scrim == mNotificationsScrim) return DEBUG_NOTIFICATIONS_TINT;
throw new RuntimeException("scrim can't be matched with known scrims");
}
private void startScrimAnimation(final View scrim, float current) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
if (mAnimatorListener != null) {
anim.addListener(mAnimatorListener);
}
final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
Color.TRANSPARENT;
anim.addUpdateListener(animation -> {
final float startAlpha = (Float) scrim.getTag(TAG_START_ALPHA);
final float animAmount = (float) animation.getAnimatedValue();
final int finalScrimTint = getCurrentScrimTint(scrim);
final float finalScrimAlpha = getCurrentScrimAlpha(scrim);
float alpha = MathUtils.lerp(startAlpha, finalScrimAlpha, animAmount);
alpha = MathUtils.constrain(alpha, 0f, 1f);
int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
updateScrimColor(scrim, alpha, tint);
dispatchScrimsVisible();
});
anim.setInterpolator(mInterpolator);
anim.setStartDelay(mAnimationDelay);
anim.setDuration(mAnimationDuration);
anim.addListener(new AnimatorListenerAdapter() {
private final ScrimState mLastState = mState;
private final Callback mLastCallback = mCallback;
@Override
public void onAnimationEnd(Animator animation) {
scrim.setTag(TAG_KEY_ANIM, null);
onFinished(mLastCallback, mLastState);
dispatchScrimsVisible();
}
});
// Cache alpha values because we might want to update this animator in the future if
// the user expands the panel while the animation is still running.
scrim.setTag(TAG_START_ALPHA, current);
scrim.setTag(TAG_END_ALPHA, getCurrentScrimAlpha(scrim));
scrim.setTag(TAG_KEY_ANIM, anim);
anim.start();
}
private float getCurrentScrimAlpha(View scrim) {
if (scrim == mScrimInFront) {
return mInFrontAlpha;
} else if (scrim == mScrimBehind) {
return mBehindAlpha;
} else if (scrim == mNotificationsScrim) {
return mNotificationsAlpha;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
}
private int getCurrentScrimTint(View scrim) {
if (scrim == mScrimInFront) {
return mInFrontTint;
} else if (scrim == mScrimBehind) {
return mBehindTint;
} else if (scrim == mNotificationsScrim) {
return mNotificationsTint;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
}
@Override
public boolean onPreDraw() {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
if (mCallback != null) {
mCallback.onStart();
}
updateScrims();
return true;
}
/**
* @param state that finished
*/
private void onFinished(ScrimState state) {
onFinished(mCallback, state);
}
private void onFinished(Callback callback, ScrimState state) {
if (mPendingFrameCallback != null) {
// No animations can finish while we're waiting on the blanking to finish
return;
}
if (isAnimating(mScrimBehind)
|| isAnimating(mNotificationsScrim)
|| isAnimating(mScrimInFront)) {
if (callback != null && callback != mCallback) {
// Since we only notify the callback that we're finished once everything has
// finished, we need to make sure that any changing callbacks are also invoked
callback.onFinished();
}
return;
}
if (mWakeLockHeld) {
mWakeLock.release(TAG);
mWakeLockHeld = false;
}
if (callback != null) {
callback.onFinished();
if (callback == mCallback) {
mCallback = null;
}
}
// When unlocking with fingerprint, we'll fade the scrims from black to transparent.
// At the end of the animation we need to remove the tint.
if (state == ScrimState.UNLOCKED) {
mInFrontTint = Color.TRANSPARENT;
mBehindTint = mState.getBehindTint();
mNotificationsTint = mState.getNotifTint();
updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint);
}
}
private boolean isAnimating(@Nullable View scrim) {
return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null;
}
@VisibleForTesting
void setAnimatorListener(Animator.AnimatorListener animatorListener) {
mAnimatorListener = animatorListener;
}
private void updateScrim(ScrimView scrim, float alpha) {
final float currentAlpha = scrim.getViewAlpha();
ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
if (previousAnimator != null) {
// Previous animators should always be cancelled. Not doing so would cause
// overlap, especially on states that don't animate, leading to flickering,
// and in the worst case, an internal state that doesn't represent what
// transitionTo requested.
cancelAnimator(previousAnimator);
}
if (mPendingFrameCallback != null) {
// Display is off and we're waiting.
return;
} else if (mBlankScreen) {
// Need to blank the display before continuing.
blankDisplay();
return;
} else if (!mScreenBlankingCallbackCalled) {
// Not blanking the screen. Letting the callback know that we're ready
// to replace what was on the screen before.
if (mCallback != null) {
mCallback.onDisplayBlanked();
mScreenBlankingCallbackCalled = true;
}
}
if (scrim == mScrimBehind) {
dispatchBackScrimState(alpha);
}
final boolean wantsAlphaUpdate = alpha != currentAlpha;
final boolean wantsTintUpdate = scrim.getTint() != getCurrentScrimTint(scrim);
if (wantsAlphaUpdate || wantsTintUpdate) {
if (mAnimateChange) {
startScrimAnimation(scrim, currentAlpha);
} else {
// update the alpha directly
updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
}
}
}
private void cancelAnimator(ValueAnimator previousAnimator) {
if (previousAnimator != null) {
previousAnimator.cancel();
}
}
private void blankDisplay() {
updateScrimColor(mScrimInFront, 1, Color.BLACK);
// Notify callback that the screen is completely black and we're
// ready to change the display power mode
mPendingFrameCallback = () -> {
if (mCallback != null) {
mCallback.onDisplayBlanked();
mScreenBlankingCallbackCalled = true;
}
mBlankingTransitionRunnable = () -> {
mBlankingTransitionRunnable = null;
mPendingFrameCallback = null;
mBlankScreen = false;
// Try again.
updateScrims();
};
// Setting power states can happen after we push out the frame. Make sure we
// stay fully opaque until the power state request reaches the lower levels.
final int delay = mScreenOn ? 32 : 500;
if (DEBUG) {
Log.d(TAG, "Fading out scrims with delay: " + delay);
}
mHandler.postDelayed(mBlankingTransitionRunnable, delay);
};
doOnTheNextFrame(mPendingFrameCallback);
}
/**
* Executes a callback after the frame has hit the display.
*
* @param callback What to run.
*/
@VisibleForTesting
protected void doOnTheNextFrame(Runnable callback) {
// Just calling View#postOnAnimation isn't enough because the frame might not have reached
// the display yet. A timeout is the safest solution.
mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
}
public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
// TODO: remove this. This is necessary because of an order-of-operations limitation.
// The fix is to move more of these class into @CentralSurfacesScope
if (mScrimBehind == null) {
mScrimBehindChangeRunnable = changeRunnable;
} else {
mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
}
}
public void setCurrentUser(int currentUser) {
// Don't care in the base class.
}
private void updateThemeColors() {
if (mScrimBehind == null) return;
int background = Utils.getColorAttr(mScrimBehind.getContext(),
android.R.attr.colorBackgroundFloating).getDefaultColor();
int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
mColors.setMainColor(background);
mColors.setSecondaryColor(accent);
mColors.setSupportsDarkText(
ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
mNeedsDrawableColorUpdate = true;
}
private void onThemeChanged() {
updateThemeColors();
scheduleUpdate();
}
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println(" ScrimController: ");
pw.print(" state: ");
pw.println(mState);
pw.println(" mClipQsScrim = " + mState.mClipQsScrim);
pw.print(" frontScrim:");
pw.print(" viewAlpha=");
pw.print(mScrimInFront.getViewAlpha());
pw.print(" alpha=");
pw.print(mInFrontAlpha);
pw.print(" tint=0x");
pw.println(Integer.toHexString(mScrimInFront.getTint()));
pw.print(" behindScrim:");
pw.print(" viewAlpha=");
pw.print(mScrimBehind.getViewAlpha());
pw.print(" alpha=");
pw.print(mBehindAlpha);
pw.print(" tint=0x");
pw.println(Integer.toHexString(mScrimBehind.getTint()));
pw.print(" notificationsScrim:");
pw.print(" viewAlpha=");
pw.print(mNotificationsScrim.getViewAlpha());
pw.print(" alpha=");
pw.print(mNotificationsAlpha);
pw.print(" tint=0x");
pw.println(Integer.toHexString(mNotificationsScrim.getTint()));
pw.print(" mTracking=");
pw.println(mTracking);
pw.print(" mDefaultScrimAlpha=");
pw.println(mDefaultScrimAlpha);
pw.print(" mPanelExpansionFraction=");
pw.println(mPanelExpansionFraction);
pw.print(" mExpansionAffectsAlpha=");
pw.println(mExpansionAffectsAlpha);
pw.print(" mState.getMaxLightRevealScrimAlpha=");
pw.println(mState.getMaxLightRevealScrimAlpha());
}
public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
}
}
/**
* Interrupts blanking transitions once the display notifies that it's already on.
*/
public void onScreenTurnedOn() {
mScreenOn = true;
if (mHandler.hasCallbacks(mBlankingTransitionRunnable)) {
if (DEBUG) {
Log.d(TAG, "Shorter blanking because screen turned on. All good.");
}
mHandler.removeCallbacks(mBlankingTransitionRunnable);
mBlankingTransitionRunnable.run();
}
}
public void onScreenTurnedOff() {
mScreenOn = false;
}
public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
mExpansionAffectsAlpha = expansionAffectsAlpha;
}
public void setKeyguardOccluded(boolean keyguardOccluded) {
mKeyguardOccluded = keyguardOccluded;
updateScrims();
}
public void setHasBackdrop(boolean hasBackdrop) {
for (ScrimState state : ScrimState.values()) {
state.setHasBackdrop(hasBackdrop);
}
// Backdrop event may arrive after state was already applied,
// in this case, back-scrim needs to be re-evaluated
if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
float newBehindAlpha = mState.getBehindAlpha();
if (isNaN(newBehindAlpha)) {
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ ", back: " + mBehindAlpha);
}
if (mBehindAlpha != newBehindAlpha) {
mBehindAlpha = newBehindAlpha;
updateScrims();
}
}
}
private void setKeyguardFadingAway(boolean fadingAway, long duration) {
for (ScrimState state : ScrimState.values()) {
state.setKeyguardFadingAway(fadingAway, duration);
}
}
public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
for (ScrimState state : ScrimState.values()) {
state.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
}
}
public interface Callback {
default void onStart() {
}
default void onDisplayBlanked() {
}
default void onFinished() {
}
default void onCancelled() {
}
}
/**
* Simple keyguard callback that updates scrims when keyguard visibility changes.
*/
private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
}
}