From 265b8356e8a0fade8395560cfec1aef0edf19970 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Thu, 19 Dec 2019 18:59:42 -0800 Subject: [PATCH] Re-register SMD after motion detected. One shot sensors need to be re-registered after they've been triggered. Since DeviceIdleController wasn't doing that, we weren't properly tracking motion to inform stationary listeners. This change makes sure to re-register the motion sensor after some time to make sure motion is properly tracked. Bug: 140162457 Test: atest com.android.server.DeviceIdleControllerTest Change-Id: Ic712e46bdc9d107d56cea3bce72c2e7b2187530a Merged-In: I8600a8382b7c773be9e200afba96a7f1a74f6e10 --- .../android/server/DeviceIdleController.java | 75 ++++++--- .../server/DeviceIdleControllerTest.java | 146 ++++++++++++++++-- 2 files changed, 194 insertions(+), 27 deletions(-) diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 4aaf0b7b9b364..1fd4a0c384d36 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -613,6 +613,15 @@ public class DeviceIdleController extends SystemService } }; + /** AlarmListener to start monitoring motion if there are registered stationary listeners. */ + private final AlarmManager.OnAlarmListener mMotionRegistrationAlarmListener = () -> { + synchronized (DeviceIdleController.this) { + if (mStationaryListeners.size() > 0) { + startMonitoringMotionLocked(); + } + } + }; + private final AlarmManager.OnAlarmListener mMotionTimeoutAlarmListener = () -> { synchronized (DeviceIdleController.this) { if (!isStationaryLocked()) { @@ -748,6 +757,7 @@ public class DeviceIdleController extends SystemService @Override public void onTrigger(TriggerEvent event) { synchronized (DeviceIdleController.this) { + active = false; motionLocked(); } } @@ -755,6 +765,8 @@ public class DeviceIdleController extends SystemService @Override public void onSensorChanged(SensorEvent event) { synchronized (DeviceIdleController.this) { + mSensorManager.unregisterListener(this, mMotionSensor); + active = false; motionLocked(); } } @@ -1886,6 +1898,27 @@ public class DeviceIdleController extends SystemService return controller.new MyHandler(BackgroundThread.getHandler().getLooper()); } + Sensor getMotionSensor() { + final SensorManager sensorManager = getSensorManager(); + Sensor motionSensor = null; + int sigMotionSensorId = mContext.getResources().getInteger( + com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor); + if (sigMotionSensorId > 0) { + motionSensor = sensorManager.getDefaultSensor(sigMotionSensorId, true); + } + if (motionSensor == null && mContext.getResources().getBoolean( + com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) { + motionSensor = sensorManager.getDefaultSensor( + Sensor.TYPE_WRIST_TILT_GESTURE, true); + } + if (motionSensor == null) { + // As a last ditch, fall back to SMD. + motionSensor = sensorManager.getDefaultSensor( + Sensor.TYPE_SIGNIFICANT_MOTION, true); + } + return motionSensor; + } + PowerManager getPowerManager() { return mContext.getSystemService(PowerManager.class); } @@ -2037,21 +2070,7 @@ public class DeviceIdleController extends SystemService mSensorManager = mInjector.getSensorManager(); if (mUseMotionSensor) { - int sigMotionSensorId = getContext().getResources().getInteger( - com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor); - if (sigMotionSensorId > 0) { - mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true); - } - if (mMotionSensor == null && getContext().getResources().getBoolean( - com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) { - mMotionSensor = mSensorManager.getDefaultSensor( - Sensor.TYPE_WRIST_TILT_GESTURE, true); - } - if (mMotionSensor == null) { - // As a last ditch, fall back to SMD. - mMotionSensor = mSensorManager.getDefaultSensor( - Sensor.TYPE_SIGNIFICANT_MOTION, true); - } + mMotionSensor = mInjector.getMotionSensor(); } if (getContext().getResources().getBoolean( @@ -3422,6 +3441,10 @@ public class DeviceIdleController extends SystemService if (mStationaryListeners.size() > 0) { postStationaryStatusUpdated(); scheduleMotionTimeoutAlarmLocked(); + // 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. + scheduleMotionRegistrationAlarmLocked(); } if (mQuickDozeActivated && !mQuickDozeActivatedWhileIdling) { // Don't exit idle due to motion if quick doze is enabled. @@ -3488,9 +3511,12 @@ public class DeviceIdleController extends SystemService */ private void maybeStopMonitoringMotionLocked() { if (DEBUG) Slog.d(TAG, "maybeStopMonitoringMotionLocked()"); - if (mMotionSensor != null && mMotionListener.active && mStationaryListeners.size() == 0) { - mMotionListener.unregisterLocked(); - cancelMotionTimeoutAlarmLocked(); + if (mMotionSensor != null && mStationaryListeners.size() == 0) { + if (mMotionListener.active) { + mMotionListener.unregisterLocked(); + cancelMotionTimeoutAlarmLocked(); + } + cancelMotionRegistrationAlarmLocked(); } } @@ -3521,6 +3547,10 @@ public class DeviceIdleController extends SystemService mAlarmManager.cancel(mMotionTimeoutAlarmListener); } + private void cancelMotionRegistrationAlarmLocked() { + mAlarmManager.cancel(mMotionRegistrationAlarmListener); + } + void cancelSensingTimeoutAlarmLocked() { if (mNextSensingTimeoutAlarmTime != 0) { mNextSensingTimeoutAlarmTime = 0; @@ -3567,6 +3597,15 @@ public class DeviceIdleController extends SystemService mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler); } + private void scheduleMotionRegistrationAlarmLocked() { + if (DEBUG) Slog.d(TAG, "scheduleMotionRegistrationAlarmLocked"); + long nextMotionRegistrationAlarmTime = + mInjector.getElapsedRealtime() + mConstants.MOTION_INACTIVE_TIMEOUT / 2; + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextMotionRegistrationAlarmTime, + "DeviceIdleController.motion_registration", mMotionRegistrationAlarmListener, + mHandler); + } + private void scheduleMotionTimeoutAlarmLocked() { if (DEBUG) Slog.d(TAG, "scheduleMotionAlarmLocked"); long nextMotionTimeoutAlarmTime = diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 88273b7bc2cf4..fe15ff423a868 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -57,6 +57,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import android.app.ActivityManagerInternal; @@ -66,7 +67,11 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.TriggerEvent; +import android.hardware.TriggerEventListener; import android.location.LocationManager; import android.location.LocationProvider; import android.net.ConnectivityManager; @@ -127,6 +132,8 @@ public class DeviceIdleControllerTest { @Mock private PowerManagerInternal mPowerManagerInternal; @Mock + private Sensor mMotionSensor; + @Mock private SensorManager mSensorManager; class InjectorForTest extends DeviceIdleController.Injector { @@ -193,6 +200,11 @@ public class DeviceIdleControllerTest { return mHandler; } + @Override + Sensor getMotionSensor() { + return mMotionSensor; + } + @Override PowerManager getPowerManager() { return mPowerManager; @@ -1673,22 +1685,36 @@ public class DeviceIdleControllerTest { } @Test - public void testStationaryDetection_QuickDozeOn() { + public void testStationaryDetection_QuickDozeOn_NoMotion() { + // Short timeout for testing. + mConstants.MOTION_INACTIVE_TIMEOUT = 6000L; + doReturn(Sensor.REPORTING_MODE_ONE_SHOT).when(mMotionSensor).getReportingMode(); + doReturn(true).when(mSensorManager) + .requestTriggerSensor(eq(mDeviceIdleController.mMotionListener), eq(mMotionSensor)); setAlarmSoon(false); enterDeepState(STATE_QUICK_DOZE_DELAY); mDeviceIdleController.stepIdleStateLocked("testing"); verifyStateConditions(STATE_IDLE); // Quick doze progression through states, so time should have increased appropriately. mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT; - final ArgumentCaptor alarmListener = ArgumentCaptor + final ArgumentCaptor motionAlarmListener = ArgumentCaptor .forClass(AlarmManager.OnAlarmListener.class); + final ArgumentCaptor motionRegistrationAlarmListener = + ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class); doNothing().when(mAlarmManager).set(anyInt(), anyLong(), eq("DeviceIdleController.motion"), - alarmListener.capture(), any()); + motionAlarmListener.capture(), any()); + doNothing().when(mAlarmManager).set(anyInt(), anyLong(), + eq("DeviceIdleController.motion_registration"), + motionRegistrationAlarmListener.capture(), any()); StationaryListenerForTest stationaryListener = new StationaryListenerForTest(); + spyOn(stationaryListener); + InOrder inOrder = inOrder(stationaryListener); stationaryListener.motionExpected = true; mDeviceIdleController.registerStationaryListener(stationaryListener); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(false)); assertFalse(stationaryListener.isStationary); // Go to IDLE_MAINTENANCE @@ -1700,13 +1726,17 @@ public class DeviceIdleControllerTest { mDeviceIdleController.stepIdleStateLocked("testing"); // Now enough time has passed. - mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2; + mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT; stationaryListener.motionExpected = false; - alarmListener.getValue().onAlarm(); + motionAlarmListener.getValue().onAlarm(); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(true)); assertTrue(stationaryListener.isStationary); stationaryListener.motionExpected = true; - mDeviceIdleController.mMotionListener.onSensorChanged(null); + mDeviceIdleController.mMotionListener.onTrigger(null); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(false)); assertFalse(stationaryListener.isStationary); // Since we're in quick doze, the device shouldn't stop idling. @@ -1715,18 +1745,116 @@ public class DeviceIdleControllerTest { // Go to IDLE_MAINTENANCE mDeviceIdleController.stepIdleStateLocked("testing"); + motionRegistrationAlarmListener.getValue().onAlarm(); mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2; // Back to IDLE + stationaryListener.motionExpected = false; mDeviceIdleController.stepIdleStateLocked("testing"); + verify(mSensorManager, + timeout(mConstants.MOTION_INACTIVE_TIMEOUT).times(2)) + .requestTriggerSensor(eq(mDeviceIdleController.mMotionListener), eq(mMotionSensor)); // Now enough time has passed. - mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2; - stationaryListener.motionExpected = false; - alarmListener.getValue().onAlarm(); + mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT; + motionAlarmListener.getValue().onAlarm(); + inOrder.verify(stationaryListener, + timeout(mConstants.MOTION_INACTIVE_TIMEOUT).times(1)) + .onDeviceStationaryChanged(eq(true)); assertTrue(stationaryListener.isStationary); } + @Test + public void testStationaryDetection_QuickDozeOn_OneShot() { + // Short timeout for testing. + mConstants.MOTION_INACTIVE_TIMEOUT = 6000L; + doReturn(Sensor.REPORTING_MODE_ONE_SHOT).when(mMotionSensor).getReportingMode(); + setAlarmSoon(false); + enterDeepState(STATE_QUICK_DOZE_DELAY); + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + // Quick doze progression through states, so time should have increased appropriately. + mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT; + final ArgumentCaptor alarmListener = ArgumentCaptor + .forClass(AlarmManager.OnAlarmListener.class); + doNothing().when(mAlarmManager) + .set(anyInt(), anyLong(), eq("DeviceIdleController.motion"), any(), any()); + doNothing().when(mAlarmManager).set(anyInt(), anyLong(), + eq("DeviceIdleController.motion_registration"), + alarmListener.capture(), any()); + ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(TriggerEventListener.class); + + StationaryListenerForTest stationaryListener = new StationaryListenerForTest(); + spyOn(stationaryListener); + InOrder inOrder = inOrder(stationaryListener, mSensorManager); + + stationaryListener.motionExpected = true; + mDeviceIdleController.registerStationaryListener(stationaryListener); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(false)); + assertFalse(stationaryListener.isStationary); + inOrder.verify(mSensorManager) + .requestTriggerSensor(listenerCaptor.capture(), eq(mMotionSensor)); + final TriggerEventListener listener = listenerCaptor.getValue(); + + // Trigger motion + listener.onTrigger(mock(TriggerEvent.class)); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(false)); + + // Make sure the listener is re-registered. + alarmListener.getValue().onAlarm(); + inOrder.verify(mSensorManager).requestTriggerSensor(eq(listener), eq(mMotionSensor)); + } + + @Test + public void testStationaryDetection_QuickDozeOn_MultiShot() { + // Short timeout for testing. + mConstants.MOTION_INACTIVE_TIMEOUT = 6000L; + doReturn(Sensor.REPORTING_MODE_CONTINUOUS).when(mMotionSensor).getReportingMode(); + setAlarmSoon(false); + enterDeepState(STATE_QUICK_DOZE_DELAY); + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + // Quick doze progression through states, so time should have increased appropriately. + mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT; + final ArgumentCaptor alarmListener = ArgumentCaptor + .forClass(AlarmManager.OnAlarmListener.class); + doNothing().when(mAlarmManager) + .set(anyInt(), anyLong(), eq("DeviceIdleController.motion"), any(), any()); + doNothing().when(mAlarmManager).set(anyInt(), anyLong(), + eq("DeviceIdleController.motion_registration"), + alarmListener.capture(), any()); + ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + + StationaryListenerForTest stationaryListener = new StationaryListenerForTest(); + spyOn(stationaryListener); + InOrder inOrder = inOrder(stationaryListener, mSensorManager); + + 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)); + final SensorEventListener listener = listenerCaptor.getValue(); + + // Trigger motion + listener.onSensorChanged(mock(SensorEvent.class)); + inOrder.verify(stationaryListener, timeout(1000L).times(1)) + .onDeviceStationaryChanged(eq(false)); + + // Make sure the listener is re-registered. + alarmListener.getValue().onAlarm(); + inOrder.verify(mSensorManager) + .registerListener(eq(listener), eq(mMotionSensor), + eq(SensorManager.SENSOR_DELAY_NORMAL)); + } + private void enterDeepState(int state) { switch (state) { case STATE_ACTIVE: