DeviceIdleController state exit conditions tests.

This tests exit conditions and transitions (going to ACTIVE) for
each state in DeviceIdleController.

Bug: 116512267
Test: atest com.android.server.DeviceIdleControllerTest
Change-Id: I3b46ebd4e683b865e05f0a481147a96a796fe5f2
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 376bc0d..26421a2 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -666,7 +666,8 @@
      * global Settings. Any access to this class or its fields should be done while
      * holding the DeviceIdleController lock.
      */
-    private final class Constants extends ContentObserver {
+    @VisibleForTesting
+    final class Constants extends ContentObserver {
         // Key names stored in the settings value.
         private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT
                 = "light_after_inactive_to";
@@ -1529,12 +1530,17 @@
             return (ConnectivityService) ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
         }
 
+        Constants getConstants(DeviceIdleController controller, Handler handler,
+                ContentResolver resolver) {
+            return controller.new Constants(handler, resolver);
+        }
+
         LocationManager getLocationManager() {
             return mContext.getSystemService(LocationManager.class);
         }
 
-        MyHandler getHandler(DeviceIdleController ctlr) {
-            return ctlr.new MyHandler(BackgroundThread.getHandler().getLooper());
+        MyHandler getHandler(DeviceIdleController controller) {
+            return controller.new MyHandler(BackgroundThread.getHandler().getLooper());
         }
 
         PowerManager getPowerManager() {
@@ -1623,7 +1629,7 @@
                 }
             }
 
-            mConstants = new Constants(mHandler, getContext().getContentResolver());
+            mConstants = mInjector.getConstants(this, mHandler, getContext().getContentResolver());
 
             readConfigFileLocked();
             updateWhitelistAppIdsLocked();
@@ -2325,6 +2331,16 @@
         }
     }
 
+    /**
+     * Must only be used in tests.
+     *
+     * This sets the state value directly and thus doesn't trigger any behavioral changes.
+     */
+    @VisibleForTesting
+    void setLightStateForTest(int lightState) {
+        mLightState = lightState;
+    }
+
     @VisibleForTesting
     int getLightState() {
         return mLightState;
@@ -2556,6 +2572,12 @@
         }
     }
 
+    /** Must only be used in tests. */
+    @VisibleForTesting
+    void setActiveIdleOpsForTest(int count) {
+        mActiveIdleOpCount = count;
+    }
+
     void setJobsActive(boolean active) {
         synchronized (this) {
             mJobsActive = active;
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 6665070..95ed00f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -51,6 +51,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.hardware.SensorManager;
 import android.location.LocationManager;
@@ -59,6 +60,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
+import android.os.SystemClock;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -84,15 +86,17 @@
 
     private MockitoSession mMockingSession;
     @Mock
-    private PowerManager mPowerManager;
-    @Mock
-    private PowerManager.WakeLock mWakeLock;
-    @Mock
     private AlarmManager mAlarmManager;
     @Mock
+    private DeviceIdleController.Constants mConstants;
+    @Mock
+    private IActivityManager mIActivityManager;
+    @Mock
     private LocationManager mLocationManager;
     @Mock
-    private IActivityManager mIActivityManager;
+    private PowerManager mPowerManager;
+    @Mock
+    private PowerManager.WakeLock mWakeLock;
 
     class InjectorForTest extends DeviceIdleController.Injector {
 
@@ -122,12 +126,18 @@
         }
 
         @Override
+        DeviceIdleController.Constants getConstants(DeviceIdleController controller, Handler handler,
+                ContentResolver resolver) {
+            return mConstants;
+        }
+
+        @Override
         LocationManager getLocationManager() {
             return mLocationManager;
         }
 
         @Override
-        DeviceIdleController.MyHandler getHandler(DeviceIdleController ctlr) {
+        DeviceIdleController.MyHandler getHandler(DeviceIdleController controller) {
             return mock(DeviceIdleController.MyHandler.class);
         }
 
@@ -452,6 +462,514 @@
         verifyStateConditions(STATE_IDLE_MAINTENANCE);
     }
 
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_deep_noActiveOps() {
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterDeepState(STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_IDLE_PENDING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        enterDeepState(STATE_SENSING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_SENSING);
+
+        enterDeepState(STATE_LOCATING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_LOCATING);
+
+        enterDeepState(STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE);
+
+        enterDeepState(STATE_IDLE_MAINTENANCE);
+        // Going into IDLE_MAINTENANCE increments the active idle op count.
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE);
+    }
+
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_deep_activeJobs() {
+        mDeviceIdleController.setJobsActive(true);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterDeepState(STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_IDLE_PENDING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        enterDeepState(STATE_SENSING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_SENSING);
+
+        enterDeepState(STATE_LOCATING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_LOCATING);
+
+        enterDeepState(STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE);
+
+        enterDeepState(STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_deep_activeAlarms() {
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(true);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterDeepState(STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_IDLE_PENDING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        enterDeepState(STATE_SENSING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_SENSING);
+
+        enterDeepState(STATE_LOCATING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_LOCATING);
+
+        enterDeepState(STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE);
+
+        enterDeepState(STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_deep_activeOps() {
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(1);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterDeepState(STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_IDLE_PENDING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        enterDeepState(STATE_SENSING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_SENSING);
+
+        enterDeepState(STATE_LOCATING);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_LOCATING);
+
+        enterDeepState(STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE);
+
+        enterDeepState(STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_light_noActiveOps() {
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterLightState(LIGHT_STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+
+        enterLightState(LIGHT_STATE_PRE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        enterLightState(LIGHT_STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        enterLightState(LIGHT_STATE_WAITING_FOR_NETWORK);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
+
+        enterLightState(LIGHT_STATE_IDLE_MAINTENANCE);
+        // Going into IDLE_MAINTENANCE increments the active idle op count.
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        enterLightState(LIGHT_STATE_OVERRIDE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_OVERRIDE);
+    }
+
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_light_activeJobs() {
+        mDeviceIdleController.setJobsActive(true);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterLightState(LIGHT_STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+
+        enterLightState(LIGHT_STATE_PRE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
+
+        enterLightState(LIGHT_STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        enterLightState(LIGHT_STATE_WAITING_FOR_NETWORK);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
+
+        enterLightState(LIGHT_STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+
+        enterLightState(LIGHT_STATE_OVERRIDE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_OVERRIDE);
+    }
+
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_light_activeAlarms() {
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(true);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterLightState(LIGHT_STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+
+        enterLightState(LIGHT_STATE_PRE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
+
+        enterLightState(LIGHT_STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        enterLightState(LIGHT_STATE_WAITING_FOR_NETWORK);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
+
+        enterLightState(LIGHT_STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+
+        enterLightState(LIGHT_STATE_OVERRIDE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_OVERRIDE);
+    }
+
+    @Test
+    public void testExitMaintenanceEarlyIfNeededLocked_light_activeOps() {
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(1);
+
+        // This method should only change things if in IDLE_MAINTENANCE or PRE_IDLE states.
+
+        enterLightState(LIGHT_STATE_ACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_INACTIVE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+
+        enterLightState(LIGHT_STATE_PRE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
+
+        enterLightState(LIGHT_STATE_IDLE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        enterLightState(LIGHT_STATE_WAITING_FOR_NETWORK);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
+
+        enterLightState(LIGHT_STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+
+        enterLightState(LIGHT_STATE_OVERRIDE);
+        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
+        verifyLightStateConditions(LIGHT_STATE_OVERRIDE);
+    }
+
+    @Test
+    public void testHandleMotionDetectedLocked_deep() {
+        enterDeepState(STATE_ACTIVE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyStateConditions(STATE_ACTIVE);
+
+        // Anything that wasn't ACTIVE before motion detection should end up in the INACTIVE state.
+
+        enterDeepState(STATE_INACTIVE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_IDLE_PENDING);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_SENSING);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_LOCATING);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_IDLE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyStateConditions(STATE_INACTIVE);
+
+        enterDeepState(STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyStateConditions(STATE_INACTIVE);
+    }
+
+    @Test
+    public void testHandleMotionDetectedLocked_light() {
+        enterLightState(LIGHT_STATE_ACTIVE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        // Motion shouldn't affect light idle, so LIGHT states should stay as they were except for
+        // OVERRIDE. OVERRIDE means deep was active, so if motion was detected,
+        // LIGHT_STATE_OVERRIDE should end up as LIGHT_STATE_INACTIVE.
+
+        enterLightState(LIGHT_STATE_INACTIVE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+
+        enterLightState(LIGHT_STATE_PRE_IDLE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
+
+        enterLightState(LIGHT_STATE_IDLE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        enterLightState(LIGHT_STATE_WAITING_FOR_NETWORK);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
+
+        enterLightState(LIGHT_STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+
+        enterLightState(LIGHT_STATE_OVERRIDE);
+        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+    }
+
+    @Test
+    public void testbecomeActiveLocked_deep() {
+        // becomeActiveLocked should put everything into ACTIVE.
+
+        enterDeepState(STATE_ACTIVE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_INACTIVE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_IDLE_PENDING);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_SENSING);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_LOCATING);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_IDLE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyStateConditions(STATE_ACTIVE);
+    }
+
+    @Test
+    public void testbecomeActiveLocked_light() {
+        // becomeActiveLocked should put everything into ACTIVE.
+
+        enterLightState(LIGHT_STATE_ACTIVE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_INACTIVE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_PRE_IDLE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_IDLE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_WAITING_FOR_NETWORK);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_IDLE_MAINTENANCE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_OVERRIDE);
+        mDeviceIdleController.becomeActiveLocked("test", 1000);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+    }
+
+    private void enterDeepState(int state) {
+        switch (state) {
+            case STATE_ACTIVE:
+                setScreenOn(true);
+                mDeviceIdleController.becomeActiveLocked("testing", 0);
+                break;
+            case STATE_LOCATING:
+                doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(
+                        anyString());
+                // Fallthrough to step loop.
+            case STATE_IDLE_PENDING:
+            case STATE_SENSING:
+            case STATE_IDLE:
+            case STATE_IDLE_MAINTENANCE:
+                // Make sure the controller doesn't think there's a wake-from-idle alarm coming
+                // soon.
+                doReturn(Long.MAX_VALUE).when(mAlarmManager).getNextWakeFromIdleTime();
+            case STATE_INACTIVE:
+                setScreenOn(false);
+                setChargingOn(false);
+                mDeviceIdleController.becomeInactiveIfAppropriateLocked();
+                //fail(stateToString(mDeviceIdleController.getState()));
+                int count = 0;
+                while (mDeviceIdleController.getState() != state) {
+                    // Stepping through each state ensures that the proper features are turned
+                    // on/off.
+                    mDeviceIdleController.stepIdleStateLocked("testing");
+                    count++;
+                    if (count > 10) {
+                        fail(stateToString(mDeviceIdleController.getState()));
+                    }
+                }
+                break;
+            default:
+                fail("Unknown deep state " + stateToString(state));
+        }
+    }
+
+    private void enterLightState(int lightState) {
+        switch (lightState) {
+            case LIGHT_STATE_ACTIVE:
+                setScreenOn(true);
+                mDeviceIdleController.becomeActiveLocked("testing", 0);
+                break;
+            case LIGHT_STATE_INACTIVE:
+            case LIGHT_STATE_IDLE:
+            case LIGHT_STATE_IDLE_MAINTENANCE:
+                setScreenOn(false);
+                setChargingOn(false);
+                int count = 0;
+                mDeviceIdleController.becomeInactiveIfAppropriateLocked();
+                while (mDeviceIdleController.getLightState() != lightState) {
+                    // Stepping through each state ensures that the proper features are turned
+                    // on/off.
+                    mDeviceIdleController.stepLightIdleStateLocked("testing");
+
+                    count++;
+                    if (count > 10) {
+                        fail(lightStateToString(mDeviceIdleController.getLightState()));
+                    }
+                }
+                break;
+            case LIGHT_STATE_PRE_IDLE:
+            case LIGHT_STATE_WAITING_FOR_NETWORK:
+            case LIGHT_STATE_OVERRIDE:
+                setScreenOn(false);
+                setChargingOn(false);
+                mDeviceIdleController.setLightStateForTest(lightState);
+                break;
+            default:
+                fail("Unknown light state " + lightStateToString(lightState));
+        }
+    }
+
     private void setChargingOn(boolean on) {
         mDeviceIdleController.updateChargingLocked(on);
     }
@@ -525,7 +1043,10 @@
         switch (expectedLightState) {
             case LIGHT_STATE_ACTIVE:
                 assertTrue(
-                        mDeviceIdleController.isCharging() || mDeviceIdleController.isScreenOn());
+                        mDeviceIdleController.isCharging() || mDeviceIdleController.isScreenOn()
+                                // Or there's an alarm coming up soon.
+                                || SystemClock.elapsedRealtime() + mConstants.MIN_TIME_TO_ALARM
+                                > mAlarmManager.getNextWakeFromIdleTime());
                 break;
             case LIGHT_STATE_INACTIVE:
             case LIGHT_STATE_PRE_IDLE: