Hide AoD wallpaper after 1min

Hide wallpaper by cross fading back scrim. This increases
battery life since black pixels won't drain power.

Bug: 64155983
Test: atest packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
Test: set AoD wallpaper, wait for 1 min.
Test: set AoD wallpaper, wait for 50 sec, go to pulsing, wait 10 sec.
Change-Id: Ie830c2fd20f9e60efbfd9e78f248603df07ae93c
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index e80d6d3..3177c03 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.AlarmManager;
 import android.content.Context;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -92,10 +93,10 @@
 
     public ScrimController createScrimController(LightBarController lightBarController,
             ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
-            LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
-            DozeParameters dozeParameters) {
+            LockscreenWallpaper lockscreenWallpaper, Consumer<Integer> scrimVisibleListener,
+            DozeParameters dozeParameters, AlarmManager alarmManager) {
         return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
-                scrimVisibleListener, dozeParameters);
+                scrimVisibleListener, dozeParameters, alarmManager);
     }
 
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index cc2244a..8515bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -40,12 +40,16 @@
     private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
     private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
     private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_WALLPAPER_VISIBILITY_MS = 60 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_WALLPAPER_FADE_OUT_MS = 400;
 
     static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
     static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
     static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
     static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger";
     static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period";
+    static final String KEY_WALLPAPER_VISIBILITY_MS = "wallpaper_visibility_timeout";
+    static final String KEY_WALLPAPER_FADE_OUT_MS = "wallpaper_fade_out_duration";
 
     /**
      * Integer array to map ambient brightness type to real screen brightness.
@@ -89,6 +93,24 @@
      */
     public long proxCooldownPeriodMs;
 
+    /**
+     * For how long(ms) the wallpaper should still be visible
+     * after entering AoD.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_WALLPAPER_VISIBILITY_MS
+     */
+    public long wallpaperVisibilityDuration;
+
+    /**
+     * Duration(ms) of the fade out animation after
+     * {@link #KEY_WALLPAPER_VISIBILITY_MS} elapses.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_WALLPAPER_FADE_OUT_MS
+     */
+    public long wallpaperFadeOutDuration;
+
     private final KeyValueListParser mParser;
     private final Context mContext;
     private SettingsObserver mSettingsObserver;
@@ -138,6 +160,10 @@
                         DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
                 proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
                         DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+                wallpaperFadeOutDuration = mParser.getLong(KEY_WALLPAPER_FADE_OUT_MS,
+                        DEFAULT_WALLPAPER_FADE_OUT_MS);
+                wallpaperVisibilityDuration = mParser.getLong(KEY_WALLPAPER_VISIBILITY_MS,
+                        DEFAULT_WALLPAPER_VISIBILITY_MS);
                 screenBrightnessArray = mParser.getIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
                         resources.getIntArray(
                                 R.array.config_doze_brightness_sensor_to_brightness));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 69809a2..6d85fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.systemui.R;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 
 import java.io.PrintWriter;
 
@@ -37,10 +38,12 @@
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
+    private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
 
     public DozeParameters(Context context) {
         mContext = context;
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+        mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context);
     }
 
     public void dump(PrintWriter pw) {
@@ -121,6 +124,22 @@
     }
 
     /**
+     * For how long a wallpaper can be visible in AoD before it fades aways.
+     * @return duration in millis.
+     */
+    public long getWallpaperAodDuration() {
+        return mAlwaysOnPolicy.wallpaperVisibilityDuration;
+    }
+
+    /**
+     * How long it takes for the wallpaper fade away (Animation duration.)
+     * @return duration in millis.
+     */
+    public long getWallpaperFadeOutDuration() {
+        return mAlwaysOnPolicy.wallpaperFadeOutDuration;
+    }
+
+    /**
      * Checks if always on is available and enabled for the current user.
      * @return {@code true} if enabled and available.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a809c9e..8abc3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.app.AlarmManager;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Color;
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -73,6 +75,19 @@
             = new PathInterpolator(0f, 0, 0.7f, 1f);
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
             = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
+
+    /**
+     * When both scrims have 0 alpha.
+     */
+    public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+    /**
+     * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
+     */
+    public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+    /**
+     * When at least 1 scrim is fully opaque (alpha set to 1.)
+     */
+    public static final int VISIBILITY_FULLY_OPAQUE = 2;
     /**
      * Default alpha value for most scrims.
      */
@@ -111,6 +126,7 @@
     private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
+    private final AlarmTimeout mTimeTicker;
 
     private final SysuiColorExtractor mColorExtractor;
     private GradientColors mLockColors;
@@ -138,23 +154,25 @@
     private float mCurrentBehindAlpha = NOT_INITIALIZED;
     private int mCurrentInFrontTint;
     private int mCurrentBehindTint;
+    private boolean mWallpaperVisibilityTimedOut;
     private int mPinnedHeadsUpCount;
     private float mTopHeadsUpDragAmount;
     private View mDraggedHeadsUpView;
     private boolean mKeyguardFadingOutInProgress;
     private ValueAnimator mKeyguardFadeoutAnimation;
-    private boolean mScrimsVisible;
-    private final Consumer<Boolean> mScrimVisibleListener;
+    private int mScrimsVisibility;
+    private final Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
     private Callback mCallback;
+    private boolean mWallpaperSupportsAmbientMode;
 
     private final WakeLock mWakeLock;
     private boolean mWakeLockHeld;
 
     public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
-            ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
-            DozeParameters dozeParameters) {
+            ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener,
+            DozeParameters dozeParameters, AlarmManager alarmManager) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
         mHeadsUpScrim = headsUpScrim;
@@ -164,6 +182,8 @@
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mLightBarController = lightBarController;
         mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
+                "hide_aod_wallpaper", new Handler());
         mWakeLock = createWakeLock();
         // 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.
@@ -236,13 +256,18 @@
         }
 
         // Do not let the device sleep until we're done with all animations
-        if (!mWakeLockHeld) {
-            if (mWakeLock != null) {
-                mWakeLockHeld = true;
-                mWakeLock.acquire();
-            } else {
-                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+        holdWakeLock();
+
+        // AOD wallpapers should fade away after a while
+        if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
+                && (mState == ScrimState.AOD || mState == ScrimState.PULSING)) {
+            if (!mWallpaperVisibilityTimedOut) {
+                mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
             }
+        } else {
+            mTimeTicker.cancel();
+            mWallpaperVisibilityTimedOut = false;
         }
 
         if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
@@ -279,6 +304,30 @@
         mTracking = 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();
+            } 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.
@@ -391,6 +440,14 @@
             mLightBarController.setScrimColor(mScrimInFront.getColors());
         }
 
+        // We want to override the back scrim opacity for AOD and PULSING
+        // when it's time to fade the wallpaper away.
+        boolean overrideBackScrimAlpha = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
+                && mWallpaperVisibilityTimedOut;
+        if (overrideBackScrimAlpha) {
+            mCurrentBehindAlpha = 1;
+        }
+
         setScrimInFrontAlpha(mCurrentInFrontAlpha);
         setScrimBehindAlpha(mCurrentBehindAlpha);
 
@@ -398,12 +455,18 @@
     }
 
     private void dispatchScrimsVisible() {
-        boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
+        final int currentScrimVisibility;
+        if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
+            currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+        } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
+            currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+        } else {
+            currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+        }
 
-        if (mScrimsVisible != scrimsVisible) {
-            mScrimsVisible = scrimsVisible;
-
-            mScrimVisibleListener.accept(scrimsVisible);
+        if (mScrimsVisibility != currentScrimVisibility) {
+            mScrimsVisibility = currentScrimVisibility;
+            mScrimVisibleListener.accept(currentScrimVisibility);
         }
     }
 
@@ -812,7 +875,11 @@
     }
 
     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
-        ScrimState.AOD.setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
+        mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+        ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
+        }
     }
 
     public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index f6b5e6e..fa2c1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,9 +19,7 @@
 import android.graphics.Color;
 import android.os.Trace;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
@@ -87,19 +85,13 @@
      */
     AOD {
         @Override
-        public void init(ScrimView scrimInFront, ScrimView scrimBehind,
-                DozeParameters dozeParameters) {
-            super.init(scrimInFront, scrimBehind, dozeParameters);
-            mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext());
-        }
-
-        @Override
         public void prepare(ScrimState previousState) {
             if (previousState == ScrimState.PULSING && !mCanControlScreenOff) {
                 updateScrimColor(mScrimInFront, 1, Color.BLACK);
             }
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
-            mBlankScreen = previousState == ScrimState.PULSING;
+            final boolean wasPulsing = previousState == ScrimState.PULSING;
+            mBlankScreen = wasPulsing && !mCanControlScreenOff;
             mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
                     && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
             mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
@@ -117,9 +109,10 @@
     PULSING {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 1;
             mCurrentInFrontAlpha = 0;
             mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+                    && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
             mCurrentBehindTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
             if (mDisplayRequiresBlanking) {
@@ -178,6 +171,7 @@
         mDozeParameters = dozeParameters;
         mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
         mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation();
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext());
     }
 
     public void prepare(ScrimState previousState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 87815a3..c5349d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -46,6 +46,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.AlarmManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -250,7 +251,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
-import java.util.function.Function;
 
 public class StatusBar extends SystemUI implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
@@ -943,9 +943,9 @@
                 scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
                 scrimsVisible -> {
                     if (mStatusBarWindowManager != null) {
-                        mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
+                        mStatusBarWindowManager.setScrimsVisibility(scrimsVisible);
                     }
-                }, new DozeParameters(mContext));
+                }, new DozeParameters(mContext), mContext.getSystemService(AlarmManager.class));
         if (mScrimSrcModeEnabled) {
             Runnable runnable = () -> {
                 boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 47be4d0..c30f633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -138,7 +138,8 @@
         }
 
         final boolean showWallpaperOnAod = mDozeParameters.getAlwaysOn() &&
-                state.wallpaperSupportsAmbientMode;
+                state.wallpaperSupportsAmbientMode &&
+                state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_OPAQUE;
         if (state.keyguardShowing && !state.backdropShowing &&
                 (!state.dozing || showWallpaperOnAod)) {
             mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -192,7 +193,8 @@
     private boolean isExpanded(State state) {
         return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
-                || state.headsUpShowing || state.scrimsVisible);
+                || state.headsUpShowing
+                || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
     }
 
     private void applyFitsSystemWindows(State state) {
@@ -340,8 +342,8 @@
         apply(mCurrentState);
     }
 
-    public void setScrimsVisible(boolean scrimsVisible) {
-        mCurrentState.scrimsVisible = scrimsVisible;
+    public void setScrimsVisibility(int scrimsVisibility) {
+        mCurrentState.scrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
     }
 
@@ -452,7 +454,7 @@
         boolean remoteInputActive;
         boolean forcePluginOpen;
         boolean dozing;
-        boolean scrimsVisible;
+        int scrimsVisibility;
 
         private boolean isKeyguardShowingAndNotOccluded() {
             return keyguardShowing && !keyguardOccluded;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 691d871..9b7efe9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,13 +16,21 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
+import android.app.AlarmManager;
 import android.graphics.Color;
 import android.os.Handler;
 import android.os.Looper;
@@ -53,12 +61,13 @@
     private ScrimView mScrimBehind;
     private ScrimView mScrimInFront;
     private View mHeadsUpScrim;
-    private Consumer<Boolean> mScrimVisibilityCallback;
-    private Boolean mScrimVisibile;
+    private Consumer<Integer> mScrimVisibilityCallback;
+    private int mScrimVisibility;
     private LightBarController mLightBarController;
     private DozeParameters mDozeParamenters;
     private WakeLock mWakeLock;
     private boolean mAlwaysOnEnabled;
+    private AlarmManager mAlarmManager;
 
     @Before
     public void setup() {
@@ -67,13 +76,15 @@
         mScrimInFront = new ScrimView(getContext());
         mHeadsUpScrim = mock(View.class);
         mWakeLock = mock(WakeLock.class);
+        mAlarmManager = mock(AlarmManager.class);
         mAlwaysOnEnabled = true;
-        mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible;
+        mScrimVisibilityCallback = (Integer visible) -> mScrimVisibility = visible;
         mDozeParamenters = mock(DozeParameters.class);
         when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
         when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
         mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind,
-                mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters);
+                mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters,
+                mAlarmManager);
     }
 
     @Test
@@ -88,7 +99,7 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
         assertScrimTint(mScrimBehind, false /* tinted */);
     }
 
@@ -98,7 +109,7 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible with tint
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
         assertScrimTint(mScrimBehind, true /* tinted */);
         assertScrimTint(mScrimInFront, true /* tinted */);
     }
@@ -110,7 +121,12 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be transparent
-        assertScrimVisibility(false /* front */, false /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        // Move on to PULSING and check if the back scrim is still transparent
+        mScrimController.transitionTo(ScrimState.PULSING);
+        mScrimController.finishAnimationsImmediately();
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
     }
 
     @Test
@@ -126,19 +142,27 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible with tint
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
         assertScrimTint(mScrimBehind, true /* tinted */);
         assertScrimTint(mScrimInFront, true /* tinted */);
     }
 
     @Test
     public void transitionToPulsing() {
+        // Pre-condition
+        // Need to go to AoD first because PULSING doesn't change
+        // the back scrim opacity - otherwise it would hide AoD wallpapers.
+        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible with tint
         // Pulse callback should have been invoked
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
         assertScrimTint(mScrimBehind, true /* tinted */);
     }
 
@@ -148,7 +172,7 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(true /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
         assertScrimTint(mScrimBehind, false /* tinted */);
     }
 
@@ -158,13 +182,13 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be transparent
-        assertScrimVisibility(false /* front */, false /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
         assertScrimTint(mScrimBehind, false /* tinted */);
         assertScrimTint(mScrimInFront, false /* tinted */);
 
         // Back scrim should be visible after start dragging
         mScrimController.setPanelExpansion(0.5f);
-        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
     }
 
     @Test
@@ -180,7 +204,7 @@
         // Front scrim should be transparent
         // Back scrim should be transparent
         // Neither scrims should be tinted anymore after the animation.
-        assertScrimVisibility(false /* front */, false /* behind */);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
         assertScrimTint(mScrimInFront, false /* tinted */);
         assertScrimTint(mScrimBehind, false /* tinted */);
     }
@@ -198,8 +222,8 @@
                         Assert.assertTrue("Scrim should be visible during transition. Alpha: "
                                 + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
                         assertScrimTint(mScrimInFront, true /* tinted */);
-                        Assert.assertTrue("Scrim should be visible during transition.",
-                                mScrimVisibile);
+                        Assert.assertSame("Scrim should be visible during transition.",
+                                mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
                     }
                 });
         mScrimController.finishAnimationsImmediately();
@@ -253,10 +277,10 @@
     @Test
     public void testHoldsWakeLock() {
         mScrimController.transitionTo(ScrimState.AOD);
-        verify(mWakeLock, times(1)).acquire();
+        verify(mWakeLock).acquire();
         verify(mWakeLock, never()).release();
         mScrimController.finishAnimationsImmediately();
-        verify(mWakeLock, times(1)).release();
+        verify(mWakeLock).release();
     }
 
     @Test
@@ -265,7 +289,30 @@
         mScrimController.finishAnimationsImmediately();
         ScrimController.Callback callback = mock(ScrimController.Callback.class);
         mScrimController.transitionTo(ScrimState.UNLOCKED, callback);
-        verify(callback, times(1)).onFinished();
+        verify(callback).onFinished();
+    }
+
+    @Test
+    public void testHoldsAodWallpaperAnimationLock() {
+        // Pre-conditions
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        reset(mWakeLock);
+
+        mScrimController.onHideWallpaperTimeout();
+        verify(mWakeLock).acquire();
+        verify(mWakeLock, never()).release();
+        mScrimController.finishAnimationsImmediately();
+        verify(mWakeLock).release();
+    }
+
+    @Test
+    public void testWillHideAoDWallpaper() {
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.AOD);
+        verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
     }
 
     private void assertScrimTint(ScrimView scrimView, boolean tinted) {
@@ -276,12 +323,23 @@
                 tinted, viewIsTinted);
     }
 
-    private void assertScrimVisibility(boolean inFront, boolean behind) {
+    private void assertScrimVisibility(int inFront, int behind) {
+        boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
+        boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
         Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
-                + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0);
+                + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
         Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
-                + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0);
-        Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile);
+                + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
+
+        final int visibility;
+        if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
+            visibility = VISIBILITY_FULLY_OPAQUE;
+        } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
+            visibility = VISIBILITY_SEMI_TRANSPARENT;
+        } else {
+            visibility = VISIBILITY_FULLY_TRANSPARENT;
+        }
+        Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
     }
 
     /**
@@ -293,9 +351,10 @@
 
         public SynchronousScrimController(LightBarController lightBarController,
                 ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
-                Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) {
+                Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
+                AlarmManager alarmManager) {
             super(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
-                    scrimVisibleListener, dozeParameters);
+                    scrimVisibleListener, dozeParameters, alarmManager);
             mHandler = new FakeHandler(Looper.myLooper());
         }