diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 9e4321db6fb08..1d93eb3a211a9 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -2402,7 +2402,6 @@ public class DeviceIdleController extends SystemService return mConstants; } - /** Returns the current elapsed realtime in milliseconds. */ long getElapsedRealtime() { return SystemClock.elapsedRealtime(); @@ -3819,6 +3818,7 @@ public class DeviceIdleController extends SystemService // Everything is in place to go into IDLE state. case STATE_IDLE_MAINTENANCE: + moveToStateLocked(STATE_IDLE, reason); scheduleAlarmLocked(mNextIdleDelay, true); if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay + " ms."); @@ -3829,7 +3829,6 @@ public class DeviceIdleController extends SystemService if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) { mNextIdleDelay = mConstants.IDLE_TIMEOUT; } - moveToStateLocked(STATE_IDLE, reason); if (mLightState != LIGHT_STATE_OVERRIDE) { moveToLightStateLocked(LIGHT_STATE_OVERRIDE, "deep"); cancelLightAlarmLocked(); @@ -3842,6 +3841,7 @@ public class DeviceIdleController extends SystemService // We have been idling long enough, now it is time to do some work. mActiveIdleOpCount = 1; mActiveIdleWakeLock.acquire(); + moveToStateLocked(STATE_IDLE_MAINTENANCE, reason); scheduleAlarmLocked(mNextIdlePendingDelay, false); if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " + "Next alarm in " + mNextIdlePendingDelay + " ms."); @@ -3851,7 +3851,6 @@ public class DeviceIdleController extends SystemService if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) { mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT; } - moveToStateLocked(STATE_IDLE_MAINTENANCE, reason); addEvent(EVENT_DEEP_MAINTENANCE, null); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); break; diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 85820127d36c8..5377ee71f4808 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -154,6 +154,7 @@ public class DeviceIdleControllerTest { ConstraintController constraintController; // Freeze time for testing. long nowElapsed; + boolean useMotionSensor = true; InjectorForTest(Context ctx) { super(ctx); @@ -245,7 +246,7 @@ public class DeviceIdleControllerTest { @Override boolean useMotionSensor() { - return true; + return useMotionSensor; } } @@ -345,6 +346,12 @@ public class DeviceIdleControllerTest { mAnyMotionDetector = new AnyMotionDetectorForTest(); mInjector = new InjectorForTest(getContext()); + setupDeviceIdleController(); + } + + private void setupDeviceIdleController() { + reset(mTelephonyManager); + mDeviceIdleController = new DeviceIdleController(getContext(), mInjector); spyOn(mDeviceIdleController); doNothing().when(mDeviceIdleController).publishBinderService(any(), any()); @@ -371,6 +378,10 @@ public class DeviceIdleControllerTest { if (mMockingSession != null) { mMockingSession.finishMocking(); } + } + + @After + public void cleanupDeviceIdleController() { // DeviceIdleController adds these to LocalServices in the constructor, so we have to remove // them after each test, otherwise, subsequent tests will fail. LocalServices.removeServiceForTest(AppStateTracker.class); @@ -617,6 +628,60 @@ public class DeviceIdleControllerTest { .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false)); } + @Test + public void testStateActiveToStateInactive_DoNotUseMotionSensor() { + mInjector.useMotionSensor = false; + cleanupDeviceIdleController(); + setupDeviceIdleController(); + mDeviceIdleController.becomeActiveLocked("testing", 0); + verifyStateConditions(STATE_ACTIVE); + + setAlarmSoon(false); + setChargingOn(false); + setScreenOn(false); + setEmergencyCallActive(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyStateConditions(STATE_INACTIVE); + verify(mDeviceIdleController) + .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false)); + // The device configuration doesn't require a motion sensor to proceed with idling. + // This should be the case on TVs or other such devices. We should set an alarm to move + // forward if the motion sensor is missing in this case. + verify(mAlarmManager).setWindow( + anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + } + + @Test + public void testStateActiveToStateInactive_MissingMotionSensor() { + mInjector.useMotionSensor = true; + mMotionSensor = null; + cleanupDeviceIdleController(); + setupDeviceIdleController(); + mDeviceIdleController.becomeActiveLocked("testing", 0); + verifyStateConditions(STATE_ACTIVE); + + setAlarmSoon(false); + setChargingOn(false); + setScreenOn(false); + setEmergencyCallActive(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyStateConditions(STATE_INACTIVE); + verify(mDeviceIdleController) + .scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false)); + // The device configuration requires a motion sensor to proceed with idling, + // so we should never set an alarm to move forward if the motion sensor is + // missing in this case. + verify(mAlarmManager, never()).setWindow( + anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + verify(mAlarmManager, never()).set( + anyInt(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + } + @Test public void testStateActiveToStateInactive_UpcomingAlarm() { final long timeUntilAlarm = mConstants.MIN_TIME_TO_ALARM / 2; @@ -756,6 +821,94 @@ public class DeviceIdleControllerTest { verifyStateConditions(STATE_IDLE_MAINTENANCE); } + @Test + public void testStepIdleStateLocked_ValidStates_MissingMotionSensor() { + mInjector.useMotionSensor = true; + mMotionSensor = null; + cleanupDeviceIdleController(); + setupDeviceIdleController(); + mInjector.locationManager = mLocationManager; + doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString()); + // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon. + setAlarmSoon(false); + + InOrder alarmManagerInOrder = inOrder(mAlarmManager); + + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyStateConditions(STATE_INACTIVE); + + // The device configuration requires a motion sensor to proceed with idling, + // so we should never set an alarm to move forward if the motion sensor is + // missing in this case. + alarmManagerInOrder.verify(mAlarmManager, never()) + .setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + + // Pretend that someone is forcing state stepping via adb + + mDeviceIdleController.stepIdleStateLocked("testing"); + // verifyStateConditions knows this state typically shouldn't happen during normal + // operation, so we can't use it directly here. For this test, all we care about + // is that the state stepped forward. + assertEquals(STATE_IDLE_PENDING, mDeviceIdleController.getState()); + // Still no alarm + alarmManagerInOrder.verify(mAlarmManager, never()) + .setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + + mDeviceIdleController.stepIdleStateLocked("testing"); + // verifyStateConditions knows this state typically shouldn't happen during normal + // operation, so we can't use it directly here. For this test, all we care about + // is that the state stepped forward. + assertEquals(STATE_SENSING, mDeviceIdleController.getState()); + // Still no alarm + alarmManagerInOrder.verify(mAlarmManager, never()) + .setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + + mDeviceIdleController.stepIdleStateLocked("testing"); + // Location manager exists with a provider, so SENSING should go to LOCATING. + // verifyStateConditions knows this state typically shouldn't happen during normal + // operation, so we can't use it directly here. For this test, all we care about + // is that the state stepped forward. + assertEquals(STATE_LOCATING, mDeviceIdleController.getState()); + // Still no alarm + alarmManagerInOrder.verify(mAlarmManager, never()) + .setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + // The device was forced into IDLE. AlarmManager should be notified. + alarmManagerInOrder.verify(mAlarmManager) + .setIdleUntil(anyInt(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + + // Should just alternate between IDLE and IDLE_MAINTENANCE now. Since we've gotten to this + // point, alarms should be set on each transition. + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + alarmManagerInOrder.verify(mAlarmManager) + .setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + alarmManagerInOrder.verify(mAlarmManager) + .setIdleUntil(anyInt(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + alarmManagerInOrder.verify(mAlarmManager) + .setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.deep"), any(), any(Handler.class)); + } + @Test public void testStepIdleStateLocked_ValidStates_WithWakeFromIdleAlarmSoon() { enterDeepState(STATE_ACTIVE);