OSDN Git Service

Re-register SMD after motion detected.
authorKweku Adams <kwekua@google.com>
Fri, 20 Dec 2019 02:59:42 +0000 (18:59 -0800)
committerKweku Adams <kwekua@google.com>
Fri, 20 Dec 2019 02:59:42 +0000 (18:59 -0800)
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

services/core/java/com/android/server/DeviceIdleController.java
services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java

index 4aaf0b7..1fd4a0c 100644 (file)
@@ -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 =
index 88273b7..fe15ff4 100644 (file)
@@ -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 {
@@ -194,6 +201,11 @@ public class DeviceIdleControllerTest {
         }
 
         @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<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> motionAlarmListener = ArgumentCaptor
                 .forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> 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<AlarmManager.OnAlarmListener> 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<TriggerEventListener> 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<AlarmManager.OnAlarmListener> 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<SensorEventListener> 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: