Ensure AlarmManager is brought out of device idle.

In certain cases, DeviceIdleController would come out of STATE_IDLE but
not properly tell AlarmManager (by removing the IDLE_UNTIL alarm). This
meant that alarms wouldn't go off even though the user may be using the
device.

Manual testing steps:
1. Turn on WAIT_FOR_UNLOCK ('adb shell settings put global device_idle_constants "wait_for_unlock=true"')
2. Enable screen lock
3. Unplug device ('adb shell dumpsys battery unplug')
3. Turn screen off
4. Enter IDLE state (repeat 'adb shell dumpsys deviceidle step' until IDLE)
5. Turn screen on
6. Shake device (or run 'adb shell dumpsys deviceidle motion')
7. Check dumpsys output:
  - 'adb shell dumpsys deviceidle get deep' should say "INACTIVE"
  - 'adb shell dumpsys alarm' should not have "Idle state mode" in the output

Bug: 118321869
Test: atest com.android.server.DeviceIdleControllerTest
Change-Id: Ie41b5a4bd9f2b386fe709108c39a443bd40fd573
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index 2756610..5b0de5e 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -26,6 +26,8 @@
         void broadcastAlarmComplete(int recipientUid);
     }
 
+    /** Returns true if AlarmManager is delaying alarms due to device idle. */
+    boolean isIdling();
     public void removeAlarmsForUid(int uid);
     public void registerInFlightListener(InFlightListener callback);
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 10b5327..a400cc3 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1991,6 +1991,11 @@
      */
     private final class LocalService implements AlarmManagerInternal {
         @Override
+        public boolean isIdling() {
+            return isIdlingImpl();
+        }
+
+        @Override
         public void removeAlarmsForUid(int uid) {
             synchronized (mLock) {
                 removeLocked(uid);
@@ -2823,6 +2828,12 @@
         }
     }
 
+    private boolean isIdlingImpl() {
+        synchronized (mLock) {
+            return mPendingIdleUntil != null;
+        }
+    }
+
     AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
         synchronized (mLock) {
             return mNextAlarmClockForUser.get(userId);
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 39030aa..6b66394 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -271,6 +271,7 @@
     private static final int EVENT_BUFFER_SIZE = 100;
 
     private AlarmManager mAlarmManager;
+    private AlarmManagerInternal mLocalAlarmManager;
     private IBatteryStats mBatteryStats;
     private ActivityManagerInternal mLocalActivityManager;
     private ActivityTaskManagerInternal mLocalActivityTaskManager;
@@ -616,7 +617,8 @@
         }
     };
 
-    private final AlarmManager.OnAlarmListener mDeepAlarmListener
+    @VisibleForTesting
+    final AlarmManager.OnAlarmListener mDeepAlarmListener
             = new AlarmManager.OnAlarmListener() {
         @Override
         public void onAlarm() {
@@ -1874,6 +1876,7 @@
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             synchronized (this) {
                 mAlarmManager = mInjector.getAlarmManager();
+                mLocalAlarmManager = getLocalService(AlarmManagerInternal.class);
                 mBatteryStats = BatteryStatsService.getService();
                 mLocalActivityManager = getLocalService(ActivityManagerInternal.class);
                 mLocalActivityTaskManager = getLocalService(ActivityTaskManagerInternal.class);
@@ -2605,6 +2608,16 @@
         // next natural time to come out of it.
     }
 
+
+    /** Returns true if the screen is locked. */
+    @VisibleForTesting
+    boolean isKeyguardShowing() {
+        synchronized (this) {
+            return mScreenLocked;
+        }
+    }
+
+    @VisibleForTesting
     void keyguardShowingLocked(boolean showing) {
         if (DEBUG) Slog.i(TAG, "keyguardShowing=" + showing);
         if (mScreenLocked != showing) {
@@ -2616,25 +2629,38 @@
         }
     }
 
+    @VisibleForTesting
     void scheduleReportActiveLocked(String activeReason, int activeUid) {
         Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
         mHandler.sendMessage(msg);
     }
 
     void becomeActiveLocked(String activeReason, int activeUid) {
-        if (DEBUG) Slog.i(TAG, "becomeActiveLocked, reason = " + activeReason);
+        becomeActiveLocked(activeReason, activeUid, mConstants.INACTIVE_TIMEOUT, true);
+    }
+
+    private void becomeActiveLocked(String activeReason, int activeUid,
+            long newInactiveTimeout, boolean changeLightIdle) {
+        if (DEBUG) {
+            Slog.i(TAG, "becomeActiveLocked, reason=" + activeReason
+                    + ", changeLightIdle=" + changeLightIdle);
+        }
         if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
             EventLogTags.writeDeviceIdle(STATE_ACTIVE, activeReason);
-            EventLogTags.writeDeviceIdleLight(LIGHT_STATE_ACTIVE, activeReason);
-            scheduleReportActiveLocked(activeReason, activeUid);
             mState = STATE_ACTIVE;
-            mLightState = LIGHT_STATE_ACTIVE;
-            mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+            mInactiveTimeout = newInactiveTimeout;
             mCurIdleBudget = 0;
             mMaintenanceStartTime = 0;
             resetIdleManagementLocked();
-            resetLightIdleManagementLocked();
-            addEvent(EVENT_NORMAL, activeReason);
+
+            if (changeLightIdle) {
+                EventLogTags.writeDeviceIdleLight(LIGHT_STATE_ACTIVE, activeReason);
+                mLightState = LIGHT_STATE_ACTIVE;
+                resetLightIdleManagementLocked();
+                // Only report active if light is also ACTIVE.
+                scheduleReportActiveLocked(activeReason, activeUid);
+                addEvent(EVENT_NORMAL, activeReason);
+            }
         }
     }
 
@@ -2654,49 +2680,81 @@
         }
     }
 
+    /** Sanity check to make sure DeviceIdleController and AlarmManager are on the same page. */
+    private void verifyAlarmStateLocked() {
+        if (mState == STATE_ACTIVE && mNextAlarmTime != 0) {
+            Slog.wtf(TAG, "mState=ACTIVE but mNextAlarmTime=" + mNextAlarmTime);
+        }
+        if (mState != STATE_IDLE && mLocalAlarmManager.isIdling()) {
+            Slog.wtf(TAG, "mState=" + stateToString(mState) + " but AlarmManager is idling");
+        }
+        if (mState == STATE_IDLE && !mLocalAlarmManager.isIdling()) {
+            Slog.wtf(TAG, "mState=IDLE but AlarmManager is not idling");
+        }
+        if (mLightState == LIGHT_STATE_ACTIVE && mNextLightAlarmTime != 0) {
+            Slog.wtf(TAG, "mLightState=ACTIVE but mNextLightAlarmTime is "
+                    + TimeUtils.formatDuration(mNextLightAlarmTime - SystemClock.elapsedRealtime())
+                    + " from now");
+        }
+    }
+
     void becomeInactiveIfAppropriateLocked() {
-        if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
-        if ((!mScreenOn && !mCharging) || mForceIdle) {
-            // Become inactive and determine if we will ultimately go idle.
-            if (mDeepEnabled) {
-                if (mQuickDozeActivated) {
-                    if (mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE
-                            || mState == STATE_IDLE_MAINTENANCE) {
-                        // Already "idling". Don't want to restart the process.
-                        // mLightState can't be LIGHT_STATE_ACTIVE if mState is any of these 3
-                        // values, so returning here is safe.
-                        return;
-                    }
-                    if (DEBUG) {
-                        Slog.d(TAG, "Moved from "
-                                + stateToString(mState) + " to STATE_QUICK_DOZE_DELAY");
-                    }
-                    mState = STATE_QUICK_DOZE_DELAY;
-                    // Make sure any motion sensing or locating is stopped.
-                    resetIdleManagementLocked();
-                    // Wait a small amount of time in case something (eg: background service from
-                    // recently closed app) needs to finish running.
-                    scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
-                    EventLogTags.writeDeviceIdle(mState, "no activity");
-                } else if (mState == STATE_ACTIVE) {
-                    mState = STATE_INACTIVE;
-                    if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
-                    resetIdleManagementLocked();
-                    long delay = mInactiveTimeout;
-                    if (shouldUseIdleTimeoutFactorLocked()) {
-                        delay = (long) (mPreIdleFactor * delay);
-                    }
-                    scheduleAlarmLocked(delay, false);
-                    EventLogTags.writeDeviceIdle(mState, "no activity");
+        verifyAlarmStateLocked();
+
+        final boolean isScreenBlockingInactive =
+                mScreenOn && (!mConstants.WAIT_FOR_UNLOCK || !mScreenLocked);
+        if (DEBUG) {
+            Slog.d(TAG, "becomeInactiveIfAppropriateLocked():"
+                    + " isScreenBlockingInactive=" + isScreenBlockingInactive
+                    + " (mScreenOn=" + mScreenOn
+                    + ", WAIT_FOR_UNLOCK=" + mConstants.WAIT_FOR_UNLOCK
+                    + ", mScreenLocked=" + mScreenLocked + ")"
+                    + " mCharging=" + mCharging
+                    + " mForceIdle=" + mForceIdle
+            );
+        }
+        if (!mForceIdle && (mCharging || isScreenBlockingInactive)) {
+            return;
+        }
+        // Become inactive and determine if we will ultimately go idle.
+        if (mDeepEnabled) {
+            if (mQuickDozeActivated) {
+                if (mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE
+                        || mState == STATE_IDLE_MAINTENANCE) {
+                    // Already "idling". Don't want to restart the process.
+                    // mLightState can't be LIGHT_STATE_ACTIVE if mState is any of these 3
+                    // values, so returning here is safe.
+                    return;
                 }
+                if (DEBUG) {
+                    Slog.d(TAG, "Moved from "
+                            + stateToString(mState) + " to STATE_QUICK_DOZE_DELAY");
+                }
+                mState = STATE_QUICK_DOZE_DELAY;
+                // Make sure any motion sensing or locating is stopped.
+                resetIdleManagementLocked();
+                // Wait a small amount of time in case something (eg: background service from
+                // recently closed app) needs to finish running.
+                scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
+                EventLogTags.writeDeviceIdle(mState, "no activity");
+            } else if (mState == STATE_ACTIVE) {
+                mState = STATE_INACTIVE;
+                if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
+                resetIdleManagementLocked();
+                long delay = mInactiveTimeout;
+                if (shouldUseIdleTimeoutFactorLocked()) {
+                    delay = (long) (mPreIdleFactor * delay);
+                }
+                scheduleAlarmLocked(delay, false);
+                EventLogTags.writeDeviceIdle(mState, "no activity");
             }
-            if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
-                mLightState = LIGHT_STATE_INACTIVE;
-                if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
-                resetLightIdleManagementLocked();
-                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
-                EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
-            }
+        }
+        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
+            mLightState = LIGHT_STATE_INACTIVE;
+            if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
+            resetLightIdleManagementLocked();
+            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
+            EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
         }
     }
 
@@ -3216,33 +3274,10 @@
         // The device is not yet active, so we want to go back to the pending idle
         // state to wait again for no motion.  Note that we only monitor for motion
         // after moving out of the inactive state, so no need to worry about that.
-        boolean becomeInactive = false;
-        if (mState != STATE_ACTIVE) {
-            // Motion shouldn't affect light state, if it's already in doze-light or maintenance
-            boolean lightIdle = mLightState == LIGHT_STATE_IDLE
-                    || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK
-                    || mLightState == LIGHT_STATE_IDLE_MAINTENANCE;
-            if (!lightIdle) {
-                // Only switch to active state if we're not in either idle state
-                scheduleReportActiveLocked(type, Process.myUid());
-                addEvent(EVENT_NORMAL, type);
-            }
-            mActiveReason = ACTIVE_REASON_MOTION;
-            mState = STATE_ACTIVE;
-            mInactiveTimeout = timeout;
-            mCurIdleBudget = 0;
-            mMaintenanceStartTime = 0;
-            EventLogTags.writeDeviceIdle(mState, type);
-            becomeInactive = true;
-            updateActiveConstraintsLocked();
-        }
-        if (mLightState == LIGHT_STATE_OVERRIDE) {
-            // We went out of light idle mode because we had started deep idle mode...  let's
-            // now go back and reset things so we resume light idling if appropriate.
-            mLightState = LIGHT_STATE_ACTIVE;
-            EventLogTags.writeDeviceIdleLight(mLightState, type);
-            becomeInactive = true;
-        }
+        final boolean becomeInactive = mState != STATE_ACTIVE
+                || mLightState == LIGHT_STATE_OVERRIDE;
+        // We only want to change the IDLE state if it's OVERRIDE.
+        becomeActiveLocked(type, Process.myUid(), timeout, mLightState == LIGHT_STATE_OVERRIDE);
         if (becomeInactive) {
             becomeInactiveIfAppropriateLocked();
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 53d72bb..e51ee94 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -19,6 +19,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -51,6 +52,9 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
@@ -82,6 +86,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
@@ -105,6 +110,8 @@
     @Mock
     private ContentResolver mContentResolver;
     @Mock
+    private DeviceIdleController.MyHandler mHandler;
+    @Mock
     private IActivityManager mIActivityManager;
     @Mock
     private LocationManager mLocationManager;
@@ -154,7 +161,7 @@
 
         @Override
         DeviceIdleController.MyHandler getHandler(DeviceIdleController controller) {
-            return mock(DeviceIdleController.MyHandler.class, Answers.RETURNS_DEEP_STUBS);
+            return mHandler;
         }
 
         @Override
@@ -232,10 +239,12 @@
                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(ActivityTaskManagerInternal.class))
                 .when(() -> LocalServices.getService(ActivityTaskManagerInternal.class));
+        doReturn(mock(AlarmManagerInternal.class))
+                .when(() -> LocalServices.getService(AlarmManagerInternal.class));
         doReturn(mPowerManagerInternal)
                 .when(() -> LocalServices.getService(PowerManagerInternal.class));
-        when(mPowerManagerInternal.getLowPowerState(anyInt())).thenReturn(
-                mock(PowerSaveState.class));
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(mock(PowerSaveState.class));
         doReturn(mock(NetworkPolicyManagerInternal.class))
                 .when(() -> LocalServices.getService(NetworkPolicyManagerInternal.class));
         when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
@@ -246,8 +255,11 @@
         doReturn(true).when(mSensorManager).registerListener(any(), any(), anyInt());
         mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper());
         mAnyMotionDetector = new AnyMotionDetectorForTest();
+        mHandler = mock(DeviceIdleController.MyHandler.class, Answers.RETURNS_DEEP_STUBS);
+        doNothing().when(mHandler).handleMessage(any());
         mInjector = new InjectorForTest(getContext());
         doNothing().when(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
+
         mDeviceIdleController = new DeviceIdleController(getContext(), mInjector);
         spyOn(mDeviceIdleController);
         doNothing().when(mDeviceIdleController).publishBinderService(any(), any());
@@ -423,6 +435,29 @@
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_ACTIVE);
+
+        mConstants.WAIT_FOR_UNLOCK = false;
+        setScreenLocked(true);
+        setScreenOn(true);
+        setChargingOn(false);
+
+        mDeviceIdleController.becomeInactiveIfAppropriateLocked();
+        verifyStateConditions(STATE_ACTIVE);
+
+        setScreenLocked(false);
+        setScreenOn(true);
+        setChargingOn(false);
+
+        mDeviceIdleController.becomeInactiveIfAppropriateLocked();
+        verifyStateConditions(STATE_ACTIVE);
+
+        mConstants.WAIT_FOR_UNLOCK = true;
+        setScreenLocked(false);
+        setScreenOn(true);
+        setChargingOn(false);
+
+        mDeviceIdleController.becomeInactiveIfAppropriateLocked();
+        verifyStateConditions(STATE_ACTIVE);
     }
 
     @Test
@@ -1307,7 +1342,7 @@
     }
 
     @Test
-    public void testbecomeActiveLocked_deep() {
+    public void testBecomeActiveLocked_deep() {
         // becomeActiveLocked should put everything into ACTIVE.
 
         enterDeepState(STATE_ACTIVE);
@@ -1344,7 +1379,7 @@
     }
 
     @Test
-    public void testbecomeActiveLocked_light() {
+    public void testBecomeActiveLocked_light() {
         // becomeActiveLocked should put everything into ACTIVE.
 
         enterLightState(LIGHT_STATE_ACTIVE);
@@ -1376,6 +1411,163 @@
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
     }
 
+    /** Test based on b/119058625. */
+    @Test
+    public void testExitNotifiesDependencies_WaitForUnlockOn_KeyguardOn_ScreenThenMotion() {
+        mConstants.WAIT_FOR_UNLOCK = true;
+        enterDeepState(STATE_IDLE);
+        reset(mAlarmManager);
+        spyOn(mDeviceIdleController);
+
+        mDeviceIdleController.keyguardShowingLocked(true);
+        setScreenOn(true);
+        // With WAIT_FOR_UNLOCK = true and the screen locked, turning the screen on by itself
+        // shouldn't bring the device out of deep IDLE.
+        verifyStateConditions(STATE_IDLE);
+        mDeviceIdleController.handleMotionDetectedLocked(1000, "test");
+        // Motion should bring the device out of Doze. Since the screen is still locked (albeit
+        // on), the states should go back into INACTIVE.
+        verifyStateConditions(STATE_INACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+        verify(mAlarmManager).cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        verify(mDeviceIdleController).scheduleReportActiveLocked(anyString(), anyInt());
+    }
+
+    /** Test based on b/119058625. */
+    @Test
+    public void testExitNotifiesDependencies_WaitForUnlockOn_KeyguardOff_ScreenThenMotion() {
+        mConstants.WAIT_FOR_UNLOCK = true;
+        enterDeepState(STATE_IDLE);
+        reset(mAlarmManager);
+        spyOn(mDeviceIdleController);
+
+        mDeviceIdleController.keyguardShowingLocked(false);
+        setScreenOn(true);
+        // With WAIT_FOR_UNLOCK = true and the screen unlocked, turning the screen on by itself
+        // should bring the device out of deep IDLE.
+        verifyStateConditions(STATE_ACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+        verify(mAlarmManager).cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        verify(mDeviceIdleController).scheduleReportActiveLocked(anyString(), anyInt());
+    }
+
+    /** Test based on b/119058625. */
+    @Test
+    public void testExitNotifiesDependencies_WaitForUnlockOn_KeyguardOn_MotionThenScreen() {
+        mConstants.WAIT_FOR_UNLOCK = true;
+        enterDeepState(STATE_IDLE);
+        reset(mAlarmManager);
+        spyOn(mDeviceIdleController);
+
+        InOrder alarmManagerInOrder = inOrder(mAlarmManager);
+        InOrder controllerInOrder = inOrder(mDeviceIdleController);
+
+        mDeviceIdleController.keyguardShowingLocked(true);
+        mDeviceIdleController.handleMotionDetectedLocked(1000, "test");
+        // The screen is still off, so motion should result in the INACTIVE state.
+        verifyStateConditions(STATE_INACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+        alarmManagerInOrder.verify(mAlarmManager)
+                .cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        controllerInOrder.verify(mDeviceIdleController)
+                .scheduleReportActiveLocked(anyString(), anyInt());
+
+        setScreenOn(true);
+        // With WAIT_FOR_UNLOCK = true and the screen locked, turning the screen on by itself
+        // shouldn't bring the device all the way to ACTIVE.
+        verifyStateConditions(STATE_INACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+        alarmManagerInOrder.verify(mAlarmManager, never()).cancel(
+                eq(mDeviceIdleController.mDeepAlarmListener));
+
+        // User finally unlocks the device. Device should be fully active.
+        mDeviceIdleController.keyguardShowingLocked(false);
+        verifyStateConditions(STATE_ACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+        alarmManagerInOrder.verify(mAlarmManager)
+                .cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        controllerInOrder.verify(mDeviceIdleController)
+                .scheduleReportActiveLocked(anyString(), anyInt());
+    }
+
+    /** Test based on b/119058625. */
+    @Test
+    public void testExitNotifiesDependencies_WaitForUnlockOn_KeyguardOff_MotionThenScreen() {
+        mConstants.WAIT_FOR_UNLOCK = true;
+        enterDeepState(STATE_IDLE);
+        reset(mAlarmManager);
+        spyOn(mDeviceIdleController);
+
+        InOrder alarmManagerInOrder = inOrder(mAlarmManager);
+        InOrder controllerInOrder = inOrder(mDeviceIdleController);
+
+        mDeviceIdleController.keyguardShowingLocked(false);
+        mDeviceIdleController.handleMotionDetectedLocked(1000, "test");
+        // The screen is still off, so motion should result in the INACTIVE state.
+        verifyStateConditions(STATE_INACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+        alarmManagerInOrder.verify(mAlarmManager)
+                .cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        controllerInOrder.verify(mDeviceIdleController)
+                .scheduleReportActiveLocked(anyString(), anyInt());
+
+        setScreenOn(true);
+        // With WAIT_FOR_UNLOCK = true and the screen unlocked, turning the screen on by itself
+        // should bring the device out of deep IDLE.
+        verifyStateConditions(STATE_ACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+        alarmManagerInOrder.verify(mAlarmManager)
+                .cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        controllerInOrder.verify(mDeviceIdleController)
+                .scheduleReportActiveLocked(anyString(), anyInt());
+    }
+
+    @Test
+    public void testExitNotifiesDependencies_WaitForUnlockOff_Screen() {
+        mConstants.WAIT_FOR_UNLOCK = false;
+        enterDeepState(STATE_IDLE);
+        reset(mAlarmManager);
+        spyOn(mDeviceIdleController);
+
+        setScreenOn(true);
+        // With WAIT_FOR_UNLOCK = false and the screen locked, turning the screen on by itself
+        // should bring the device out of deep IDLE.
+        verifyStateConditions(STATE_ACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+        verify(mAlarmManager).cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        verify(mDeviceIdleController).scheduleReportActiveLocked(anyString(), anyInt());
+    }
+
+    @Test
+    public void testExitNotifiesDependencies_WaitForUnlockOff_MotionThenScreen() {
+        mConstants.WAIT_FOR_UNLOCK = false;
+        enterDeepState(STATE_IDLE);
+        reset(mAlarmManager);
+        spyOn(mDeviceIdleController);
+
+        InOrder alarmManagerInOrder = inOrder(mAlarmManager);
+        InOrder controllerInOrder = inOrder(mDeviceIdleController);
+
+        mDeviceIdleController.handleMotionDetectedLocked(1000, "test");
+        // The screen is still off, so motion should result in the INACTIVE state.
+        verifyStateConditions(STATE_INACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+        alarmManagerInOrder.verify(mAlarmManager)
+                .cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        controllerInOrder.verify(mDeviceIdleController)
+                .scheduleReportActiveLocked(anyString(), anyInt());
+
+        setScreenOn(true);
+        // With WAIT_FOR_UNLOCK = false and the screen locked, turning the screen on by itself
+        // should bring the device out of deep IDLE.
+        verifyStateConditions(STATE_ACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+        alarmManagerInOrder.verify(mAlarmManager)
+                .cancel(eq(mDeviceIdleController.mDeepAlarmListener));
+        controllerInOrder.verify(mDeviceIdleController)
+                .scheduleReportActiveLocked(anyString(), anyInt());
+    }
+
     @Test
     public void testStepToIdleMode() {
         float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE;
@@ -1508,6 +1700,10 @@
         mDeviceIdleController.updateChargingLocked(on);
     }
 
+    private void setScreenLocked(boolean locked) {
+        mDeviceIdleController.keyguardShowingLocked(locked);
+    }
+
     private void setScreenOn(boolean on) {
         doReturn(on).when(mPowerManager).isInteractive();
         mDeviceIdleController.updateInteractivityLocked();
@@ -1549,7 +1745,8 @@
                 assertFalse(mDeviceIdleController.mMotionListener.isActive());
                 assertFalse(mAnyMotionDetector.isMonitoring);
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 break;
             case STATE_IDLE_PENDING:
                 assertEquals(
@@ -1557,7 +1754,8 @@
                         mDeviceIdleController.mMotionListener.isActive());
                 assertFalse(mAnyMotionDetector.isMonitoring);
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 break;
             case STATE_SENSING:
                 assertEquals(
@@ -1567,14 +1765,16 @@
                         mDeviceIdleController.hasMotionSensor(),
                         mAnyMotionDetector.isMonitoring);
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 break;
             case STATE_LOCATING:
                 assertEquals(
                         mDeviceIdleController.hasMotionSensor(),
                         mDeviceIdleController.mMotionListener.isActive());
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 break;
             case STATE_IDLE:
                 if (mDeviceIdleController.hasMotionSensor()) {
@@ -1584,7 +1784,8 @@
                 }
                 assertFalse(mAnyMotionDetector.isMonitoring);
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 // Light state should be OVERRIDE at this point.
                 verifyLightStateConditions(LIGHT_STATE_OVERRIDE);
                 break;
@@ -1596,14 +1797,16 @@
                 }
                 assertFalse(mAnyMotionDetector.isMonitoring);
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 break;
             case STATE_QUICK_DOZE_DELAY:
                 // If quick doze is enabled, the motion listener should NOT be active.
                 assertFalse(mDeviceIdleController.mMotionListener.isActive());
                 assertFalse(mAnyMotionDetector.isMonitoring);
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 break;
             default:
                 fail("Conditions for " + stateToString(expectedState) + " unknown.");
@@ -1632,7 +1835,8 @@
             case LIGHT_STATE_IDLE_MAINTENANCE:
             case LIGHT_STATE_OVERRIDE:
                 assertFalse(mDeviceIdleController.isCharging());
-                assertFalse(mDeviceIdleController.isScreenOn());
+                assertFalse(mDeviceIdleController.isScreenOn()
+                        && !mDeviceIdleController.isKeyguardShowing());
                 break;
             default:
                 fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");