Merge "Ensure AlarmManager knows of IDLE state." into udc-dev am: c95d515c63

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

Change-Id: Ia392187c1b85990e40b93977e7262244e233cabb
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 9e4321d..1d93eb3 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 @@
             return mConstants;
         }
 
-
         /** Returns the current elapsed realtime in milliseconds. */
         long getElapsedRealtime() {
             return SystemClock.elapsedRealtime();
@@ -3819,6 +3818,7 @@
 
                 // 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 @@
                 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 @@
                 // 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 @@
                 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 8582012..5377ee7 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 @@
         ConstraintController constraintController;
         // Freeze time for testing.
         long nowElapsed;
+        boolean useMotionSensor = true;
 
         InjectorForTest(Context ctx) {
             super(ctx);
@@ -245,7 +246,7 @@
 
         @Override
         boolean useMotionSensor() {
-            return true;
+            return useMotionSensor;
         }
     }
 
@@ -345,6 +346,12 @@
         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 @@
         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);
@@ -618,6 +629,60 @@
     }
 
     @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;
         // Set an upcoming alarm that will prevent full idle.
@@ -757,6 +822,94 @@
     }
 
     @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);
         // Return that there's an alarm coming soon.