Merge "Refactor doze suppressors into its own class" into tm-dev am: 95db721b82

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17002931

Change-Id: I79e6dc22471fe36407550b0ada493eafd88a8a8f
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 2beed4c..d89c0be 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -31,7 +31,6 @@
     boolean isPowerSaveActive();
     boolean isPulsingBlocked();
     boolean isProvisioned();
-    boolean isBlockingDoze();
 
     /**
      * Makes a current pulse last for twice as long.
@@ -80,8 +79,9 @@
      */
     void stopPulsing();
 
-    /** Returns whether doze is suppressed. */
-    boolean isDozeSuppressed();
+    /** Returns whether always-on-display is suppressed. This does not include suppressing
+     * wake-up gestures. */
+    boolean isAlwaysOnSuppressed();
 
     interface Callback {
         /**
@@ -97,8 +97,10 @@
          */
         default void onPowerSaveChanged(boolean active) {}
 
-        /** Called when the doze suppression state changes. */
-        default void onDozeSuppressedChanged(boolean suppressed) {}
+        /**
+         * Called when the always on suppression state changes. See {@link #isAlwaysOnSuppressed()}.
+         */
+        default void onAlwaysOnSuppressedChanged(boolean suppressed) {}
     }
 
     interface PulseCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 2511520..0a2e69f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -132,14 +132,6 @@
     }
 
     /**
-     * Appends dozing event to the logs
-     * @param suppressed true if dozing is suppressed
-     */
-    public void traceDozingSuppressed(boolean suppressed) {
-        mLogger.logDozingSuppressed(suppressed);
-    }
-
-    /**
      * Appends fling event to the logs
      */
     public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
@@ -325,15 +317,40 @@
     }
 
     /**
-     * Appends doze suppressed event to the logs
+     * Appends the doze state that was suppressed to the doze event log
      * @param suppressedState The {@link DozeMachine.State} that was suppressed
      */
-    public void traceDozeSuppressed(DozeMachine.State suppressedState) {
-        mLogger.logDozeSuppressed(suppressedState);
+    public void traceAlwaysOnSuppressed(DozeMachine.State suppressedState) {
+        mLogger.logAlwaysOnSuppressed(suppressedState);
     }
 
     /**
-     * Appends new AOD sreen brightness to logs
+     * Appends reason why doze immediately ended.
+     */
+    public void traceImmediatelyEndDoze(String reason) {
+        mLogger.logImmediatelyEndDoze(reason);
+    }
+
+    /**
+     * Appends power save changes that may cause a new doze state
+     * @param powerSaveActive true if power saving is active
+     * @param nextState the state that we'll transition to
+     */
+    public void tracePowerSaveChanged(boolean powerSaveActive, DozeMachine.State nextState) {
+        mLogger.logPowerSaveChanged(powerSaveActive, nextState);
+    }
+
+    /**
+     * Appends an event on AOD suppression change
+     * @param suppressed true if AOD is being suppressed
+     * @param nextState the state that we'll transition to
+     */
+    public void traceAlwaysOnSuppressedChange(boolean suppressed, DozeMachine.State nextState) {
+        mLogger.logAlwaysOnSuppressedChange(suppressed, nextState);
+    }
+
+    /**
+     * Appends new AOD screen brightness to logs
      * @param brightness display brightness setting
      */
     public void traceDozeScreenBrightness(int brightness) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 4ba6b51..f3f6be2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -74,11 +74,21 @@
         })
     }
 
-    fun logDozingSuppressed(isDozingSuppressed: Boolean) {
+    fun logPowerSaveChanged(powerSaveActive: Boolean, nextState: DozeMachine.State) {
         buffer.log(TAG, INFO, {
-            bool1 = isDozingSuppressed
+            bool1 = powerSaveActive
+            str1 = nextState.name
         }, {
-            "DozingSuppressed=$bool1"
+            "Power save active=$bool1 nextState=$str1"
+        })
+    }
+
+    fun logAlwaysOnSuppressedChange(isAodSuppressed: Boolean, nextState: DozeMachine.State) {
+        buffer.log(TAG, INFO, {
+            bool1 = isAodSuppressed
+            str1 = nextState.name
+        }, {
+            "Always on (AOD) suppressed changed, suppressed=$bool1 nextState=$str1"
         })
     }
 
@@ -257,11 +267,19 @@
         })
     }
 
-    fun logDozeSuppressed(state: DozeMachine.State) {
+    fun logAlwaysOnSuppressed(state: DozeMachine.State) {
         buffer.log(TAG, INFO, {
             str1 = state.name
         }, {
-            "Doze state suppressed, state=$str1"
+            "Always-on state suppressed, suppressed state=$str1"
+        })
+    }
+
+    fun logImmediatelyEndDoze(reason: String) {
+        buffer.log(TAG, INFO, {
+            str1 = reason
+        }, {
+            "Doze immediately ended due to $str1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 789ad62..ae01f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -357,9 +357,9 @@
         if (mState == State.FINISH) {
             return State.FINISH;
         }
-        if (mDozeHost.isDozeSuppressed() && requestedState.isAlwaysOn()) {
+        if (mDozeHost.isAlwaysOnSuppressed() && requestedState.isAlwaysOn()) {
             Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState);
-            mDozeLog.traceDozeSuppressed(requestedState);
+            mDozeLog.traceAlwaysOnSuppressed(requestedState);
             return State.DOZE;
         }
         if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
@@ -415,7 +415,6 @@
         pw.print(" state="); pw.println(mState);
         pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
         pw.print(" wakeLock="); pw.println(mWakeLock);
-        pw.print(" isDozeSuppressed="); pw.println(mDozeHost.isDozeSuppressed());
         pw.println("Parts:");
         for (Part p : mParts) {
             p.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
new file mode 100644
index 0000000..31d43b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 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.doze;
+
+import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
+
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
+/**
+ * Handles suppressing doze on:
+ * 1. INITIALIZED, don't allow dozing at all when:
+ *      - in CAR_MODE
+ *      - device is NOT provisioned
+ *      - there's a pending authentication
+ * 2. PowerSaveMode active
+ *      - no always-on-display (DOZE_AOD)
+ *      - continues to allow doze triggers (DOZE, DOZE_REQUEST_PULSE)
+ * 3. Suppression changes from the PowerManager API. See {@link PowerManager#suppressAmbientDisplay}
+ *      and {@link DozeHost#isAlwaysOnSuppressed()}.
+ *      - no always-on-display (DOZE_AOD)
+ *      - allow doze triggers (DOZE), but disallow notifications (handled by {@link DozeTriggers})
+ *      - See extra check in {@link DozeMachine} to guarantee device never enters always-on states
+ */
+@DozeScope
+public class DozeSuppressor implements DozeMachine.Part {
+    private static final String TAG = "DozeSuppressor";
+
+    private DozeMachine mMachine;
+    private final DozeHost mDozeHost;
+    private final AmbientDisplayConfiguration mConfig;
+    private final DozeLog mDozeLog;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UiModeManager mUiModeManager;
+    private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+
+    private boolean mBroadcastReceiverRegistered;
+
+    @Inject
+    public DozeSuppressor(
+            DozeHost dozeHost,
+            AmbientDisplayConfiguration config,
+            DozeLog dozeLog,
+            BroadcastDispatcher broadcastDispatcher,
+            UiModeManager uiModeManager,
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
+        mDozeHost = dozeHost;
+        mConfig = config;
+        mDozeLog = dozeLog;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mUiModeManager = uiModeManager;
+        mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+    }
+
+    @Override
+    public void setDozeMachine(DozeMachine dozeMachine) {
+        mMachine = dozeMachine;
+    }
+
+    @Override
+    public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
+        switch (newState) {
+            case INITIALIZED:
+                registerBroadcastReceiver();
+                mDozeHost.addCallback(mHostCallback);
+                checkShouldImmediatelyEndDoze();
+                break;
+            case FINISH:
+                destroy();
+                break;
+            default:
+        }
+    }
+
+    @Override
+    public void destroy() {
+        unregisterBroadcastReceiver();
+        mDozeHost.removeCallback(mHostCallback);
+    }
+
+    private void checkShouldImmediatelyEndDoze() {
+        String reason = null;
+        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
+            reason = "car_mode";
+        } else if (!mDozeHost.isProvisioned()) {
+            reason = "device_unprovisioned";
+        } else if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) {
+            reason = "has_pending_auth";
+        }
+
+        if (!TextUtils.isEmpty(reason)) {
+            mDozeLog.traceImmediatelyEndDoze(reason);
+            mMachine.requestState(DozeMachine.State.FINISH);
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println(" uiMode=" + mUiModeManager.getCurrentModeType());
+        pw.println(" hasPendingAuth="
+                + mBiometricUnlockControllerLazy.get().hasPendingAuthentication());
+        pw.println(" isProvisioned=" + mDozeHost.isProvisioned());
+        pw.println(" isAlwaysOnSuppressed=" + mDozeHost.isAlwaysOnSuppressed());
+        pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive());
+    }
+
+    private void registerBroadcastReceiver() {
+        if (mBroadcastReceiverRegistered) {
+            return;
+        }
+        IntentFilter filter = new IntentFilter(ACTION_ENTER_CAR_MODE);
+        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
+        mBroadcastReceiverRegistered = true;
+    }
+
+    private void unregisterBroadcastReceiver() {
+        if (!mBroadcastReceiverRegistered) {
+            return;
+        }
+        mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
+        mBroadcastReceiverRegistered = false;
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
+                mDozeLog.traceImmediatelyEndDoze("car_mode");
+                mMachine.requestState(DozeMachine.State.FINISH);
+            }
+        }
+    };
+
+    private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
+        @Override
+        public void onPowerSaveChanged(boolean active) {
+            DozeMachine.State nextState = null;
+            if (mDozeHost.isPowerSaveActive()) {
+                nextState = DozeMachine.State.DOZE;
+            } else if (mMachine.getState() == DozeMachine.State.DOZE
+                    && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+                nextState = DozeMachine.State.DOZE_AOD;
+            }
+
+            if (nextState != null) {
+                mDozeLog.tracePowerSaveChanged(mDozeHost.isPowerSaveActive(), nextState);
+                mMachine.requestState(nextState);
+            }
+        }
+
+        @Override
+        public void onAlwaysOnSuppressedChanged(boolean suppressed) {
+            final DozeMachine.State nextState;
+            if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !suppressed) {
+                nextState = DozeMachine.State.DOZE_AOD;
+            } else {
+                nextState = DozeMachine.State.DOZE;
+            }
+            mDozeLog.traceAlwaysOnSuppressedChange(suppressed, nextState);
+            mMachine.requestState(nextState);
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 8bff3ba..74044e2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -17,12 +17,10 @@
 package com.android.systemui.doze;
 
 import android.annotation.Nullable;
-import android.app.UiModeManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -88,7 +86,6 @@
     private final AsyncSensorManager mSensorManager;
     private final WakeLock mWakeLock;
     private final boolean mAllowPulseTriggers;
-    private final UiModeManager mUiModeManager;
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
     private final DockEventListener mDockEventListener = new DockEventListener();
     private final DockManager mDockManager;
@@ -203,8 +200,6 @@
         mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
                 secureSettings, authController, devicePostureController);
-
-        mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mDockManager = dockManager;
         mProxCheck = proxCheck;
         mDozeLog = dozeLog;
@@ -247,7 +242,7 @@
             mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
             return;
         }
-        if (mDozeHost.isDozeSuppressed()) {
+        if (mDozeHost.isAlwaysOnSuppressed()) {
             runIfNotNull(onPulseSuppressedListener);
             mDozeLog.tracePulseDropped("dozeSuppressed");
             return;
@@ -456,10 +451,9 @@
                 mAodInterruptRunnable = null;
                 sWakeDisplaySensorState = true;
                 mBroadcastReceiver.register(mBroadcastDispatcher);
-                mDozeHost.addCallback(mHostCallback);
                 mDockManager.addListener(mDockEventListener);
                 mDozeSensors.requestTemporaryDisable();
-                checkTriggersAtInit();
+                mDozeHost.addCallback(mHostCallback);
                 break;
             case DOZE:
             case DOZE_AOD:
@@ -516,15 +510,6 @@
         }
     }
 
-
-    private void checkTriggersAtInit() {
-        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
-                || mDozeHost.isBlockingDoze()
-                || !mDozeHost.isProvisioned()) {
-            mMachine.requestState(DozeMachine.State.FINISH);
-        }
-    }
-
     private void requestPulse(final int reason, boolean performedProxCheck,
             Runnable onPulseSuppressedListener) {
         Assert.isMainThread();
@@ -608,9 +593,6 @@
                 requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */
                         null /* onPulseSuppressedListener */);
             }
-            if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
-                mMachine.requestState(DozeMachine.State.FINISH);
-            }
             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
                 mDozeSensors.onUserSwitched();
             }
@@ -621,7 +603,6 @@
                 return;
             }
             IntentFilter filter = new IntentFilter(PULSE_ACTION);
-            filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
             filter.addAction(Intent.ACTION_USER_SWITCHED);
             broadcastDispatcher.registerReceiver(this, filter);
             mRegistered = true;
@@ -659,26 +640,5 @@
         public void onNotificationAlerted(Runnable onPulseSuppressedListener) {
             onNotification(onPulseSuppressedListener);
         }
-
-        @Override
-        public void onPowerSaveChanged(boolean active) {
-            if (mDozeHost.isPowerSaveActive()) {
-                mMachine.requestState(DozeMachine.State.DOZE);
-            } else if (mMachine.getState() == DozeMachine.State.DOZE
-                    && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
-                mMachine.requestState(DozeMachine.State.DOZE_AOD);
-            }
-        }
-
-        @Override
-        public void onDozeSuppressedChanged(boolean suppressed) {
-            final DozeMachine.State nextState;
-            if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !suppressed) {
-                nextState = DozeMachine.State.DOZE_AOD;
-            } else {
-                nextState = DozeMachine.State.DOZE;
-            }
-            mMachine.requestState(nextState);
-        }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 05fba54..696c5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -98,7 +98,7 @@
     private NotificationPanelViewController mNotificationPanel;
     private View mAmbientIndicationContainer;
     private StatusBar mStatusBar;
-    private boolean mSuppressed;
+    private boolean mAlwaysOnSuppressed;
 
     @Inject
     public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
@@ -332,15 +332,6 @@
     }
 
     @Override
-    public boolean isBlockingDoze() {
-        if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) {
-            Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated");
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     public void extendPulse(int reason) {
         if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
             mScrimController.setWakeLockScreenSensorActive(true);
@@ -452,18 +443,25 @@
         return mIgnoreTouchWhilePulsing;
     }
 
-    void setDozeSuppressed(boolean suppressed) {
-        if (suppressed == mSuppressed) {
+    /**
+     * Suppresses always-on-display and waking up the display for notifications.
+     * Does not disable wakeup gestures like pickup and tap.
+     */
+    void setAlwaysOnSuppressed(boolean suppressed) {
+        if (suppressed == mAlwaysOnSuppressed) {
             return;
         }
-        mSuppressed = suppressed;
-        mDozeLog.traceDozingSuppressed(mSuppressed);
+        mAlwaysOnSuppressed = suppressed;
         for (Callback callback : mCallbacks) {
-            callback.onDozeSuppressedChanged(suppressed);
+            callback.onAlwaysOnSuppressedChanged(suppressed);
         }
     }
 
-    public boolean isDozeSuppressed() {
-        return mSuppressed;
+    /**
+     * Whether always-on-display is being suppressed. This does not affect wakeup gestures like
+     * pickup and tap.
+     */
+    public boolean isAlwaysOnSuppressed() {
+        return mAlwaysOnSuppressed;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index aeb7826..883445e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -534,7 +534,7 @@
 
     @Override
     public void suppressAmbientDisplay(boolean suppressed) {
-        mDozeServiceHost.setDozeSuppressed(suppressed);
+        mDozeServiceHost.setAlwaysOnSuppressed(suppressed);
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 5684429..3340f2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -143,8 +143,8 @@
     }
 
     @Test
-    public void testInitialize_dozeSuppressed_alwaysOnDisabled_goesToDoze() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_goesToDoze() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
 
         mMachine.requestState(INITIALIZED);
@@ -154,8 +154,8 @@
     }
 
     @Test
-    public void testInitialize_dozeSuppressed_alwaysOnEnabled_goesToDoze() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_goesToDoze() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
 
         mMachine.requestState(INITIALIZED);
@@ -165,8 +165,8 @@
     }
 
     @Test
-    public void testInitialize_dozeSuppressed_afterDocked_goesToDoze() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testInitialize_alwaysOnSuppressed_afterDocked_goesToDoze() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
 
         mMachine.requestState(INITIALIZED);
@@ -176,8 +176,8 @@
     }
 
     @Test
-    public void testInitialize_dozeSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
@@ -189,8 +189,8 @@
     }
 
     @Test
-    public void testInitialize_dozeSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
@@ -228,8 +228,8 @@
     }
 
     @Test
-    public void testPulseDone_dozeSuppressed_goesToSuppressed() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testPulseDone_alwaysOnSuppressed_goesToSuppressed() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         mMachine.requestState(INITIALIZED);
         mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
@@ -266,8 +266,8 @@
     }
 
     @Test
-    public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testPulseDone_alwaysOnSuppressed_afterDocked_goesToDoze() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
         mMachine.requestState(INITIALIZED);
         mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
@@ -295,8 +295,8 @@
     }
 
     @Test
-    public void testPulseDone_dozeSuppressed_afterDockPaused_goesToDoze() {
-        when(mHost.isDozeSuppressed()).thenReturn(true);
+    public void testPulseDone_alwaysOnSuppressed_afterDockPaused_goesToDoze() {
+        when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
         when(mDockManager.isHidden()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
new file mode 100644
index 0000000..aa0a909
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 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 andatest
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze;
+
+import static com.android.systemui.doze.DozeMachine.State.DOZE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.FINISH;
+import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
+import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
+
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.res.Configuration;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.testing.AndroidTestingRunner;
+import android.testing.UiThreadTest;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import dagger.Lazy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@UiThreadTest
+public class DozeSuppressorTest extends SysuiTestCase {
+
+    DozeSuppressor mDozeSuppressor;
+    @Mock
+    private DozeLog mDozeLog;
+    @Mock
+    private DozeHost mDozeHost;
+    @Mock
+    private AmbientDisplayConfiguration mConfig;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private UiModeManager mUiModeManager;
+    @Mock
+    private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+    @Mock
+    private BiometricUnlockController mBiometricUnlockController;
+
+    @Mock
+    private DozeMachine mDozeMachine;
+
+    @Captor
+    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    private BroadcastReceiver mBroadcastReceiver;
+
+    @Captor
+    private ArgumentCaptor<DozeHost.Callback> mDozeHostCaptor;
+    private DozeHost.Callback mDozeHostCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // setup state for NOT ending doze immediately
+        when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+        when(mBiometricUnlockController.hasPendingAuthentication()).thenReturn(false);
+        when(mDozeHost.isProvisioned()).thenReturn(true);
+
+        mDozeSuppressor = new DozeSuppressor(
+                mDozeHost,
+                mConfig,
+                mDozeLog,
+                mBroadcastDispatcher,
+                mUiModeManager,
+                mBiometricUnlockControllerLazy);
+
+        mDozeSuppressor.setDozeMachine(mDozeMachine);
+    }
+
+    @After
+    public void tearDown() {
+        mDozeSuppressor.destroy();
+    }
+
+    @Test
+    public void testRegistersListenersOnInitialized_unregisteredOnFinish() {
+        // check that receivers and callbacks registered
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+        captureBroadcastReceiver();
+        captureDozeHostCallback();
+
+        // check that receivers and callbacks are unregistered
+        mDozeSuppressor.transitionTo(INITIALIZED, FINISH);
+        verify(mBroadcastDispatcher).unregisterReceiver(mBroadcastReceiver);
+        verify(mDozeHost).removeCallback(mDozeHostCallback);
+    }
+
+    @Test
+    public void testEndDoze_carMode() {
+        // GIVEN car mode
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+
+        // WHEN dozing begins
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+
+        // THEN doze immediately ends
+        verify(mDozeMachine).requestState(FINISH);
+    }
+
+    @Test
+    public void testEndDoze_unprovisioned() {
+        // GIVEN device unprovisioned
+        when(mDozeHost.isProvisioned()).thenReturn(false);
+
+        // WHEN dozing begins
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+
+        // THEN doze immediately ends
+        verify(mDozeMachine).requestState(FINISH);
+    }
+
+    @Test
+    public void testEndDoze_hasPendingUnlock() {
+        // GIVEN device unprovisioned
+        when(mBiometricUnlockController.hasPendingAuthentication()).thenReturn(true);
+
+        // WHEN dozing begins
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+
+        // THEN doze immediately ends
+        verify(mDozeMachine).requestState(FINISH);
+    }
+
+    @Test
+    public void testPowerSaveChanged_active() {
+        // GIVEN AOD power save is active and doze is initialized
+        when(mDozeHost.isPowerSaveActive()).thenReturn(true);
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+        captureDozeHostCallback();
+
+        // WHEN power save change gets triggered (even if active = false, since it
+        // may differ from the aodPowerSaveActive state reported by DostHost)
+        mDozeHostCallback.onPowerSaveChanged(false);
+
+        // THEN the state changes to DOZE
+        verify(mDozeMachine).requestState(DOZE);
+    }
+
+    @Test
+    public void testPowerSaveChanged_notActive() {
+        // GIVEN DOZE (not showing aod content)
+        when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+        when(mDozeMachine.getState()).thenReturn(DOZE);
+        captureDozeHostCallback();
+
+        // WHEN power save mode is no longer active
+        when(mDozeHost.isPowerSaveActive()).thenReturn(false);
+        mDozeHostCallback.onPowerSaveChanged(false);
+
+        // THEN the state changes to DOZE_AOD
+        verify(mDozeMachine).requestState(DOZE_AOD);
+    }
+
+    @Test
+    public void testAlwaysOnSuppressedChanged_nowSuppressed() {
+        // GIVEN DOZE_AOD
+        when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+        when(mDozeMachine.getState()).thenReturn(DOZE_AOD);
+        captureDozeHostCallback();
+
+        // WHEN alwaysOnSuppressedChanged to suppressed=true
+        mDozeHostCallback.onAlwaysOnSuppressedChanged(true);
+
+        // THEN DOZE requested
+        verify(mDozeMachine).requestState(DOZE);
+    }
+
+    @Test
+    public void testAlwaysOnSuppressedChanged_notSuppressed() {
+        // GIVEN DOZE
+        when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+        when(mDozeMachine.getState()).thenReturn(DOZE);
+        captureDozeHostCallback();
+
+        // WHEN alwaysOnSuppressedChanged to suppressed=false
+        mDozeHostCallback.onAlwaysOnSuppressedChanged(false);
+
+        // THEN DOZE_AOD requested
+        verify(mDozeMachine).requestState(DOZE_AOD);
+    }
+
+    private void captureDozeHostCallback() {
+        verify(mDozeHost).addCallback(mDozeHostCaptor.capture());
+        mDozeHostCallback = mDozeHostCaptor.getValue();
+    }
+
+    private void captureBroadcastReceiver() {
+        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                anyObject());
+        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
index 25950f2..54d9cec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
@@ -158,13 +158,13 @@
     @Test
     public void testSuppressAmbientDisplay_suppress() {
         mSbcqCallbacks.suppressAmbientDisplay(true);
-        verify(mDozeServiceHost).setDozeSuppressed(true);
+        verify(mDozeServiceHost).setAlwaysOnSuppressed(true);
     }
 
     @Test
     public void testSuppressAmbientDisplay_unsuppress() {
         mSbcqCallbacks.suppressAmbientDisplay(false);
-        verify(mDozeServiceHost).setDozeSuppressed(false);
+        verify(mDozeServiceHost).setAlwaysOnSuppressed(false);
     }