From b88801a2bd139a17120099641ac1608cac923d8e Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Fri, 20 May 2022 17:56:06 +0000 Subject: [PATCH] Fix StationaryListener updating. Quick Doze bypasses motion detection, so if the device moves, the device will stay in Doze without resetting the motion sensor registration *for Doze*. If there's a StationaryListener registered, DeviceIdleController will set a new motion sensor registration ~5 minutes after motion is detected. However, DIC doesn't consider the device stationary unless there's been no motion for at least 10 minutes (and the motion sensor registration has been active for the past 10 minutes), so setting the motion timeout alarm to be 10 minutes after the motion recent motion event (which ends up being 5 minutes after DIC re-registers the motion sensor) will result in StationaryListeners being told the device is not stationary if the device is stationary long enough after a previous motion event. Bug: 231779749 Test: atest FrameworksMockingServicesTests:DeviceIdleControllerTest Change-Id: I452e76a4fd5513ff290f89e87182016fa93c1307 --- .../android/server/DeviceIdleController.java | 3 +- .../server/DeviceIdleControllerTest.java | 66 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index e9ce87f554d86..2eb86c1b720fb 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -693,6 +693,7 @@ public class DeviceIdleController extends SystemService synchronized (DeviceIdleController.this) { if (mStationaryListeners.size() > 0) { startMonitoringMotionLocked(); + scheduleMotionTimeoutAlarmLocked(); } } }; @@ -3859,7 +3860,7 @@ public class DeviceIdleController extends SystemService void handleMotionDetectedLocked(long timeout, String type) { if (mStationaryListeners.size() > 0) { postStationaryStatusUpdated(); - scheduleMotionTimeoutAlarmLocked(); + cancelMotionTimeoutAlarmLocked(); // We need to re-register the motion listener, but we don't want the sensors to be // constantly active or to churn the CPU by registering too early, register after some // delay. diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index ac115a26f9e47..cb14864876ab8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -284,7 +284,7 @@ public class DeviceIdleControllerTest { @Override public void onDeviceStationaryChanged(boolean isStationary) { if (isStationary == motionExpected) { - fail("Unexpected device stationary status: " + isStationary); + fail("Got unexpected device stationary status: " + isStationary); } this.isStationary = isStationary; } @@ -2096,6 +2096,70 @@ public class DeviceIdleControllerTest { eq(SensorManager.SENSOR_DELAY_NORMAL)); } + @Test + public void testStationaryDetection_NoDoze_AfterMotion() { + // Short timeout for testing. + mConstants.MOTION_INACTIVE_TIMEOUT = 6000L; + doReturn(Sensor.REPORTING_MODE_CONTINUOUS).when(mMotionSensor).getReportingMode(); + setAlarmSoon(true); + + final ArgumentCaptor regAlarmListener = ArgumentCaptor + .forClass(AlarmManager.OnAlarmListener.class); + final ArgumentCaptor motionAlarmListener = ArgumentCaptor + .forClass(AlarmManager.OnAlarmListener.class); + doNothing().when(mAlarmManager).setWindow( + anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), + motionAlarmListener.capture(), any()); + doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.motion_registration"), + regAlarmListener.capture(), any()); + ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + + StationaryListenerForTest stationaryListener = new StationaryListenerForTest(); + spyOn(stationaryListener); + InOrder inOrder = inOrder(stationaryListener, mSensorManager, mAlarmManager); + + stationaryListener.motionExpected = true; + mDeviceIdleController.registerStationaryListener(stationaryListener); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(false)); + assertFalse(stationaryListener.isStationary); + inOrder.verify(mSensorManager) + .registerListener(listenerCaptor.capture(), eq(mMotionSensor), + eq(SensorManager.SENSOR_DELAY_NORMAL)); + inOrder.verify(mAlarmManager).setWindow( + anyInt(), eq(mInjector.nowElapsed + mConstants.MOTION_INACTIVE_TIMEOUT), anyLong(), + eq("DeviceIdleController.motion"), any(), any()); + final SensorEventListener listener = listenerCaptor.getValue(); + + // Trigger motion + listener.onSensorChanged(mock(SensorEvent.class)); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(false)); + final ArgumentCaptor registrationTimeCaptor = ArgumentCaptor.forClass(Long.class); + inOrder.verify(mAlarmManager).setWindow( + anyInt(), registrationTimeCaptor.capture(), anyLong(), + eq("DeviceIdleController.motion_registration"), any(), any()); + + // Make sure the listener is re-registered. + mInjector.nowElapsed = registrationTimeCaptor.getValue(); + regAlarmListener.getValue().onAlarm(); + inOrder.verify(mSensorManager) + .registerListener(eq(listener), eq(mMotionSensor), + eq(SensorManager.SENSOR_DELAY_NORMAL)); + final ArgumentCaptor timeoutCaptor = ArgumentCaptor.forClass(Long.class); + inOrder.verify(mAlarmManager).setWindow(anyInt(), timeoutCaptor.capture(), anyLong(), + eq("DeviceIdleController.motion"), any(), any()); + + // No motion before timeout + stationaryListener.motionExpected = false; + mInjector.nowElapsed = timeoutCaptor.getValue(); + motionAlarmListener.getValue().onAlarm(); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(true)); + } + private void enterDeepState(int state) { switch (state) { case STATE_ACTIVE: