Loading apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +91 −4 Original line number Diff line number Diff line Loading @@ -363,6 +363,11 @@ public class DeviceIdleController extends SystemService private Location mLastGenericLocation; @GuardedBy("this") private Location mLastGpsLocation; @GuardedBy("this") private boolean mBatterySaverEnabled; @GuardedBy("this") private boolean mIsOffBody; private Sensor mOffBodySensor; /** Time in the elapsed realtime timebase when this listener last received a motion event. */ @GuardedBy("this") Loading Loading @@ -421,6 +426,7 @@ public class DeviceIdleController extends SystemService private static final int ACTIVE_REASON_FORCED = 6; private static final int ACTIVE_REASON_ALARM = 7; private static final int ACTIVE_REASON_EMERGENCY_CALL = 8; private static final int ACTIVE_REASON_ONBODY = 9; @VisibleForTesting static String stateToString(int state) { Loading Loading @@ -817,6 +823,55 @@ public class DeviceIdleController extends SystemService } } /** * LowLatencyOffBodyListener monitors if a device is on body or off body. */ @VisibleForTesting final class LowLatencyOffBodyListener implements SensorEventListener { @Override public void onSensorChanged(SensorEvent event) { if (DEBUG) { Slog.d(TAG, "LowLatencyOffBodyListener detects onSensorChanged event, values are: " + Arrays.toString(event.values)); } if (event.values == null || event.values.length == 0) { // The event returned should contain a single value to indicate off-body state. // No value indicates something went wrong. Take no action and log an error. Slog.e(TAG, "LowLatencyOffBodyListener detects onSensorChanged event but no event " + "value returns."); return; } synchronized (DeviceIdleController.this) { mIsOffBody = (event.values[0] == 0); // Get into quick doze faster when the device is off body instead of taking // traditional multi-stage approach. updateQuickDozeFlagLocked(); if (!mIsOffBody && !mBatterySaverEnabled) { mActiveReason = ACTIVE_REASON_ONBODY; becomeActiveLocked("onbody", Process.myUid()); } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} public void registerLocked() { mOffBodySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true); if (mOffBodySensor == null) { Slog.w(TAG, "Body sensor is NULL, unable to register mOffBodySensor."); return; } mSensorManager.registerListener(this, mOffBodySensor, SensorManager.SENSOR_DELAY_NORMAL); } } @VisibleForTesting final LowLatencyOffBodyListener mLowLatencyOffBodyListener = new LowLatencyOffBodyListener(); @VisibleForTesting final class MotionListener extends TriggerEventListener implements SensorEventListener { Loading Loading @@ -978,6 +1033,7 @@ public class DeviceIdleController extends SystemService */ private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; private static final String KEY_USE_WINDOW_ALARMS = "use_window_alarms"; private static final String KEY_USE_BODY_SENSOR = "use_body_sensor"; private long mDefaultFlexTimeShort = !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L; Loading Loading @@ -1037,6 +1093,7 @@ public class DeviceIdleController extends SystemService private long mDefaultNotificationAllowlistDurationMs = 30 * 1000L; private boolean mDefaultWaitForUnlock = true; private boolean mDefaultUseWindowAlarms = true; private boolean mDefaultUseBodySensor = false; /** * A somewhat short alarm window size that we will tolerate for various alarm timings. Loading Loading @@ -1277,6 +1334,11 @@ public class DeviceIdleController extends SystemService */ public boolean USE_WINDOW_ALARMS = mDefaultUseWindowAlarms; /** * Whether to use an on/off body signal to affect state transition policy. */ public boolean USE_BODY_SENSOR = mDefaultUseBodySensor; private final boolean mSmallBatteryDevice; public Constants() { Loading Loading @@ -1383,6 +1445,8 @@ public class DeviceIdleController extends SystemService com.android.internal.R.bool.device_idle_wait_for_unlock); mDefaultUseWindowAlarms = res.getBoolean( com.android.internal.R.bool.device_idle_use_window_alarms); mDefaultUseBodySensor = res.getBoolean( com.android.internal.R.bool.device_idle_use_body_sensor); FLEX_TIME_SHORT = mDefaultFlexTimeShort; LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout; Loading Loading @@ -1416,6 +1480,7 @@ public class DeviceIdleController extends SystemService NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs; WAIT_FOR_UNLOCK = mDefaultWaitForUnlock; USE_WINDOW_ALARMS = mDefaultUseWindowAlarms; USE_BODY_SENSOR = mDefaultUseBodySensor; } private long getTimeout(long defTimeout, long compTimeout) { Loading Loading @@ -1577,6 +1642,10 @@ public class DeviceIdleController extends SystemService USE_WINDOW_ALARMS = properties.getBoolean( KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms); break; case KEY_USE_BODY_SENSOR: USE_BODY_SENSOR = properties.getBoolean( KEY_USE_BODY_SENSOR, mDefaultUseBodySensor); break; default: Slog.e(TAG, "Unknown configuration key: " + name); break; Loading Loading @@ -1713,6 +1782,9 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_USE_WINDOW_ALARMS); pw.print("="); pw.println(USE_WINDOW_ALARMS); pw.print(" "); pw.print(KEY_USE_BODY_SENSOR); pw.print("="); pw.println(USE_BODY_SENSOR); } } Loading Loading @@ -2577,15 +2649,19 @@ public class DeviceIdleController extends SystemService mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); if (mConstants.USE_BODY_SENSOR) { mLowLatencyOffBodyListener.registerLocked(); } mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE, state -> { synchronized (DeviceIdleController.this) { updateQuickDozeFlagLocked(state.batterySaverEnabled); mBatterySaverEnabled = state.batterySaverEnabled; updateQuickDozeFlagLocked(); } }); updateQuickDozeFlagLocked( mLocalPowerManager.getLowPowerState( ServiceType.QUICK_DOZE).batterySaverEnabled); mBatterySaverEnabled = mLocalPowerManager.getLowPowerState( ServiceType.QUICK_DOZE).batterySaverEnabled; updateQuickDozeFlagLocked(); mLocalActivityTaskManager.registerScreenObserver(mScreenObserver); Loading Loading @@ -3274,6 +3350,17 @@ public class DeviceIdleController extends SystemService } } /** Calls to {@link #updateQuickDozeFlagLocked(boolean)} by considering appropriate signals. */ @GuardedBy("this") private void updateQuickDozeFlagLocked() { if (mConstants.USE_BODY_SENSOR) { // Only disable the quick doze flag when the device is on body and battery saver is off. updateQuickDozeFlagLocked(mIsOffBody || mBatterySaverEnabled); } else { updateQuickDozeFlagLocked(mBatterySaverEnabled); } } /** Updates the quick doze flag and enters deep doze if appropriate. */ @VisibleForTesting @GuardedBy("this") Loading core/res/res/values/config_device_idle.xml +3 −0 Original line number Diff line number Diff line Loading @@ -119,5 +119,8 @@ <!-- Default for DeviceIdleController.Constants.USE_WINDOW_ALARMS --> <bool name="device_idle_use_window_alarms">true</bool> <!-- Default for DeviceIdleController.Constants.USE_BODY_SENSOR --> <bool name="device_idle_use_body_sensor">false</bool> </resources> core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -4474,6 +4474,7 @@ <java-symbol type="integer" name="device_idle_notification_allowlist_duration_ms" /> <java-symbol type="bool" name="device_idle_wait_for_unlock" /> <java-symbol type="bool" name="device_idle_use_window_alarms" /> <java-symbol type="bool" name="device_idle_use_body_sensor" /> <!-- Binder heavy hitter watcher configs --> <java-symbol type="bool" name="config_defaultBinderHeavyHitterWatcherEnabled" /> Loading services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +79 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; Loading Loading @@ -144,6 +145,8 @@ public class DeviceIdleControllerTest { private SensorManager mSensorManager; @Mock private TelephonyManager mTelephonyManager; @Mock private Sensor mOffBodySensor; class InjectorForTest extends DeviceIdleController.Injector { ConnectivityManager connectivityManager; Loading Loading @@ -2409,6 +2412,82 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_ACTIVE); } @Test public void testLowLatencyBodyDetection_NoBodySensor() { mConstants.USE_BODY_SENSOR = true; doReturn(null).when(mSensorManager).getDefaultSensor( eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); cleanupDeviceIdleController(); setupDeviceIdleController(); verify(mSensorManager, never()) .registerListener(any(), any(), anyInt()); } @Test public void testLowLatencyBodyDetection_NoBatterySaver_QuickDoze() { mConstants.USE_BODY_SENSOR = true; doReturn(mOffBodySensor) .when(mSensorManager) .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled( false).build(); when(mPowerManagerInternal.getLowPowerState(anyInt())) .thenReturn(powerSaveState); cleanupDeviceIdleController(); setupDeviceIdleController(); ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager) .registerListener(listenerCaptor.capture(), eq(mOffBodySensor), eq(SensorManager.SENSOR_DELAY_NORMAL)); final SensorEventListener listener = listenerCaptor.getValue(); // Set the device as off body float[] valsZero = {0.0f}; SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero); listener.onSensorChanged(offbodyEvent); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); // Set the device as on body float[] valsNonZero = {1.0f}; SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero); listener.onSensorChanged(onbodyEvent); assertFalse(mDeviceIdleController.isQuickDozeEnabled()); verifyStateConditions(STATE_ACTIVE); } @Test public void testLowLatencyBodyDetection_WithBatterySaver_QuickDoze() { mConstants.USE_BODY_SENSOR = true; doReturn(mOffBodySensor) .when(mSensorManager) .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled( true).build(); when(mPowerManagerInternal.getLowPowerState(anyInt())) .thenReturn(powerSaveState); cleanupDeviceIdleController(); setupDeviceIdleController(); ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager) .registerListener(listenerCaptor.capture(), eq(mOffBodySensor), eq(SensorManager.SENSOR_DELAY_NORMAL)); final SensorEventListener listener = listenerCaptor.getValue(); // Set the device as off body float[] valsZero = {0.0f}; SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero); listener.onSensorChanged(offbodyEvent); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); // Set the device as on body. Quick doze should remain enabled because battery saver is on. float[] valsNonZero = {1.0f}; SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero); listener.onSensorChanged(onbodyEvent); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); } private void enterDeepState(int state) { switch (state) { case STATE_ACTIVE: Loading Loading
apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +91 −4 Original line number Diff line number Diff line Loading @@ -363,6 +363,11 @@ public class DeviceIdleController extends SystemService private Location mLastGenericLocation; @GuardedBy("this") private Location mLastGpsLocation; @GuardedBy("this") private boolean mBatterySaverEnabled; @GuardedBy("this") private boolean mIsOffBody; private Sensor mOffBodySensor; /** Time in the elapsed realtime timebase when this listener last received a motion event. */ @GuardedBy("this") Loading Loading @@ -421,6 +426,7 @@ public class DeviceIdleController extends SystemService private static final int ACTIVE_REASON_FORCED = 6; private static final int ACTIVE_REASON_ALARM = 7; private static final int ACTIVE_REASON_EMERGENCY_CALL = 8; private static final int ACTIVE_REASON_ONBODY = 9; @VisibleForTesting static String stateToString(int state) { Loading Loading @@ -817,6 +823,55 @@ public class DeviceIdleController extends SystemService } } /** * LowLatencyOffBodyListener monitors if a device is on body or off body. */ @VisibleForTesting final class LowLatencyOffBodyListener implements SensorEventListener { @Override public void onSensorChanged(SensorEvent event) { if (DEBUG) { Slog.d(TAG, "LowLatencyOffBodyListener detects onSensorChanged event, values are: " + Arrays.toString(event.values)); } if (event.values == null || event.values.length == 0) { // The event returned should contain a single value to indicate off-body state. // No value indicates something went wrong. Take no action and log an error. Slog.e(TAG, "LowLatencyOffBodyListener detects onSensorChanged event but no event " + "value returns."); return; } synchronized (DeviceIdleController.this) { mIsOffBody = (event.values[0] == 0); // Get into quick doze faster when the device is off body instead of taking // traditional multi-stage approach. updateQuickDozeFlagLocked(); if (!mIsOffBody && !mBatterySaverEnabled) { mActiveReason = ACTIVE_REASON_ONBODY; becomeActiveLocked("onbody", Process.myUid()); } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} public void registerLocked() { mOffBodySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true); if (mOffBodySensor == null) { Slog.w(TAG, "Body sensor is NULL, unable to register mOffBodySensor."); return; } mSensorManager.registerListener(this, mOffBodySensor, SensorManager.SENSOR_DELAY_NORMAL); } } @VisibleForTesting final LowLatencyOffBodyListener mLowLatencyOffBodyListener = new LowLatencyOffBodyListener(); @VisibleForTesting final class MotionListener extends TriggerEventListener implements SensorEventListener { Loading Loading @@ -978,6 +1033,7 @@ public class DeviceIdleController extends SystemService */ private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; private static final String KEY_USE_WINDOW_ALARMS = "use_window_alarms"; private static final String KEY_USE_BODY_SENSOR = "use_body_sensor"; private long mDefaultFlexTimeShort = !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L; Loading Loading @@ -1037,6 +1093,7 @@ public class DeviceIdleController extends SystemService private long mDefaultNotificationAllowlistDurationMs = 30 * 1000L; private boolean mDefaultWaitForUnlock = true; private boolean mDefaultUseWindowAlarms = true; private boolean mDefaultUseBodySensor = false; /** * A somewhat short alarm window size that we will tolerate for various alarm timings. Loading Loading @@ -1277,6 +1334,11 @@ public class DeviceIdleController extends SystemService */ public boolean USE_WINDOW_ALARMS = mDefaultUseWindowAlarms; /** * Whether to use an on/off body signal to affect state transition policy. */ public boolean USE_BODY_SENSOR = mDefaultUseBodySensor; private final boolean mSmallBatteryDevice; public Constants() { Loading Loading @@ -1383,6 +1445,8 @@ public class DeviceIdleController extends SystemService com.android.internal.R.bool.device_idle_wait_for_unlock); mDefaultUseWindowAlarms = res.getBoolean( com.android.internal.R.bool.device_idle_use_window_alarms); mDefaultUseBodySensor = res.getBoolean( com.android.internal.R.bool.device_idle_use_body_sensor); FLEX_TIME_SHORT = mDefaultFlexTimeShort; LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout; Loading Loading @@ -1416,6 +1480,7 @@ public class DeviceIdleController extends SystemService NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs; WAIT_FOR_UNLOCK = mDefaultWaitForUnlock; USE_WINDOW_ALARMS = mDefaultUseWindowAlarms; USE_BODY_SENSOR = mDefaultUseBodySensor; } private long getTimeout(long defTimeout, long compTimeout) { Loading Loading @@ -1577,6 +1642,10 @@ public class DeviceIdleController extends SystemService USE_WINDOW_ALARMS = properties.getBoolean( KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms); break; case KEY_USE_BODY_SENSOR: USE_BODY_SENSOR = properties.getBoolean( KEY_USE_BODY_SENSOR, mDefaultUseBodySensor); break; default: Slog.e(TAG, "Unknown configuration key: " + name); break; Loading Loading @@ -1713,6 +1782,9 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_USE_WINDOW_ALARMS); pw.print("="); pw.println(USE_WINDOW_ALARMS); pw.print(" "); pw.print(KEY_USE_BODY_SENSOR); pw.print("="); pw.println(USE_BODY_SENSOR); } } Loading Loading @@ -2577,15 +2649,19 @@ public class DeviceIdleController extends SystemService mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); if (mConstants.USE_BODY_SENSOR) { mLowLatencyOffBodyListener.registerLocked(); } mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE, state -> { synchronized (DeviceIdleController.this) { updateQuickDozeFlagLocked(state.batterySaverEnabled); mBatterySaverEnabled = state.batterySaverEnabled; updateQuickDozeFlagLocked(); } }); updateQuickDozeFlagLocked( mLocalPowerManager.getLowPowerState( ServiceType.QUICK_DOZE).batterySaverEnabled); mBatterySaverEnabled = mLocalPowerManager.getLowPowerState( ServiceType.QUICK_DOZE).batterySaverEnabled; updateQuickDozeFlagLocked(); mLocalActivityTaskManager.registerScreenObserver(mScreenObserver); Loading Loading @@ -3274,6 +3350,17 @@ public class DeviceIdleController extends SystemService } } /** Calls to {@link #updateQuickDozeFlagLocked(boolean)} by considering appropriate signals. */ @GuardedBy("this") private void updateQuickDozeFlagLocked() { if (mConstants.USE_BODY_SENSOR) { // Only disable the quick doze flag when the device is on body and battery saver is off. updateQuickDozeFlagLocked(mIsOffBody || mBatterySaverEnabled); } else { updateQuickDozeFlagLocked(mBatterySaverEnabled); } } /** Updates the quick doze flag and enters deep doze if appropriate. */ @VisibleForTesting @GuardedBy("this") Loading
core/res/res/values/config_device_idle.xml +3 −0 Original line number Diff line number Diff line Loading @@ -119,5 +119,8 @@ <!-- Default for DeviceIdleController.Constants.USE_WINDOW_ALARMS --> <bool name="device_idle_use_window_alarms">true</bool> <!-- Default for DeviceIdleController.Constants.USE_BODY_SENSOR --> <bool name="device_idle_use_body_sensor">false</bool> </resources>
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -4474,6 +4474,7 @@ <java-symbol type="integer" name="device_idle_notification_allowlist_duration_ms" /> <java-symbol type="bool" name="device_idle_wait_for_unlock" /> <java-symbol type="bool" name="device_idle_use_window_alarms" /> <java-symbol type="bool" name="device_idle_use_body_sensor" /> <!-- Binder heavy hitter watcher configs --> <java-symbol type="bool" name="config_defaultBinderHeavyHitterWatcherEnabled" /> Loading
services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +79 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; Loading Loading @@ -144,6 +145,8 @@ public class DeviceIdleControllerTest { private SensorManager mSensorManager; @Mock private TelephonyManager mTelephonyManager; @Mock private Sensor mOffBodySensor; class InjectorForTest extends DeviceIdleController.Injector { ConnectivityManager connectivityManager; Loading Loading @@ -2409,6 +2412,82 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_ACTIVE); } @Test public void testLowLatencyBodyDetection_NoBodySensor() { mConstants.USE_BODY_SENSOR = true; doReturn(null).when(mSensorManager).getDefaultSensor( eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); cleanupDeviceIdleController(); setupDeviceIdleController(); verify(mSensorManager, never()) .registerListener(any(), any(), anyInt()); } @Test public void testLowLatencyBodyDetection_NoBatterySaver_QuickDoze() { mConstants.USE_BODY_SENSOR = true; doReturn(mOffBodySensor) .when(mSensorManager) .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled( false).build(); when(mPowerManagerInternal.getLowPowerState(anyInt())) .thenReturn(powerSaveState); cleanupDeviceIdleController(); setupDeviceIdleController(); ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager) .registerListener(listenerCaptor.capture(), eq(mOffBodySensor), eq(SensorManager.SENSOR_DELAY_NORMAL)); final SensorEventListener listener = listenerCaptor.getValue(); // Set the device as off body float[] valsZero = {0.0f}; SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero); listener.onSensorChanged(offbodyEvent); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); // Set the device as on body float[] valsNonZero = {1.0f}; SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero); listener.onSensorChanged(onbodyEvent); assertFalse(mDeviceIdleController.isQuickDozeEnabled()); verifyStateConditions(STATE_ACTIVE); } @Test public void testLowLatencyBodyDetection_WithBatterySaver_QuickDoze() { mConstants.USE_BODY_SENSOR = true; doReturn(mOffBodySensor) .when(mSensorManager) .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled( true).build(); when(mPowerManagerInternal.getLowPowerState(anyInt())) .thenReturn(powerSaveState); cleanupDeviceIdleController(); setupDeviceIdleController(); ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager) .registerListener(listenerCaptor.capture(), eq(mOffBodySensor), eq(SensorManager.SENSOR_DELAY_NORMAL)); final SensorEventListener listener = listenerCaptor.getValue(); // Set the device as off body float[] valsZero = {0.0f}; SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero); listener.onSensorChanged(offbodyEvent); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); // Set the device as on body. Quick doze should remain enabled because battery saver is on. float[] valsNonZero = {1.0f}; SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero); listener.onSensorChanged(onbodyEvent); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); } private void enterDeepState(int state) { switch (state) { case STATE_ACTIVE: Loading