Don't disable GPS in Doze until the device is stationary.

GPS is normally disabled when the device is Dozing. This means that GPS
is turned off in Battery Saver when quick doze is enabled. This
interferes with navigation and possibly other use cases. This changes
the behavior so that GPS is only disabled during Dozing if the device is
stationary. This will not interfere with regular Doze since the device
has to be stationary before it will enter regular Doze, and also allows
GPS to continue to work when Quick Doze activates while the user is
still carrying the device around.

Bug: 140162457
Test: atest BatterySaverLocationTest
Test: atest com.android.server.DeviceIdleControllerTest
Test: [manual] Shorten motion timeout, step into idle, check gps
provider dump after short timeout to check mDisableGpsForPowerManager=true
Merged-In: I7bc68c8a5e6744aac3a160ac14fc3375f9374d82
Change-Id: I7bc68c8a5e6744aac3a160ac14fc3375f9374d82
(cherry picked from commit 810c77d5a0)
This commit is contained in:
Kweku Adams
2019-08-28 07:45:00 -07:00
parent 06a28b0655
commit b8bb9fb569
3 changed files with 349 additions and 20 deletions

View File

@@ -291,6 +291,7 @@ public class DeviceIdleController extends SystemService
private boolean mLightEnabled;
private boolean mDeepEnabled;
private boolean mQuickDozeActivated;
private boolean mQuickDozeActivatedWhileIdling;
private boolean mForceIdle;
private boolean mNetworkConnected;
private boolean mScreenOn;
@@ -302,6 +303,10 @@ public class DeviceIdleController extends SystemService
private boolean mHasNetworkLocation;
private Location mLastGenericLocation;
private Location mLastGpsLocation;
/** Time in the elapsed realtime timebase when this listener last received a motion event. */
private long mLastMotionEventElapsed;
// Current locked state of the screen
private boolean mScreenLocked;
private int mNumBlockingConstraints = 0;
@@ -547,6 +552,9 @@ public class DeviceIdleController extends SystemService
*/
private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();
private final ArraySet<StationaryListener> mStationaryListeners =
new ArraySet<>();
private static final int EVENT_NULL = 0;
private static final int EVENT_NORMAL = 1;
private static final int EVENT_LIGHT_IDLE = 2;
@@ -605,6 +613,21 @@ public class DeviceIdleController extends SystemService
}
};
private final AlarmManager.OnAlarmListener mMotionTimeoutAlarmListener = () -> {
synchronized (DeviceIdleController.this) {
if (!isStationaryLocked()) {
// If the device keeps registering motion, then the alarm should be
// rescheduled, so this shouldn't go off until the device is stationary.
// This case may happen in a race condition (alarm goes off right before
// motion is detected, but handleMotionDetectedLocked is called before
// we enter this block).
Slog.w(TAG, "motion timeout went off and device isn't stationary");
return;
}
}
postStationaryStatusUpdated();
};
private final AlarmManager.OnAlarmListener mSensingTimeoutAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
@@ -654,12 +677,70 @@ public class DeviceIdleController extends SystemService
}
};
/** Post stationary status only to this listener. */
private void postStationaryStatus(StationaryListener listener) {
mHandler.obtainMessage(MSG_REPORT_STATIONARY_STATUS, listener).sendToTarget();
}
/** Post stationary status to all registered listeners. */
private void postStationaryStatusUpdated() {
mHandler.sendEmptyMessage(MSG_REPORT_STATIONARY_STATUS);
}
private boolean isStationaryLocked() {
final long now = mInjector.getElapsedRealtime();
return mMotionListener.active
// Listening for motion for long enough and last motion was long enough ago.
&& now - Math.max(mMotionListener.activatedTimeElapsed, mLastMotionEventElapsed)
>= mConstants.MOTION_INACTIVE_TIMEOUT;
}
@VisibleForTesting
void registerStationaryListener(StationaryListener listener) {
synchronized (this) {
if (!mStationaryListeners.add(listener)) {
// Listener already registered.
return;
}
postStationaryStatus(listener);
if (mMotionListener.active) {
if (!isStationaryLocked() && mStationaryListeners.size() == 1) {
// First listener to be registered and the device isn't stationary, so we
// need to register the alarm to report the device is stationary.
scheduleMotionTimeoutAlarmLocked();
}
} else {
startMonitoringMotionLocked();
scheduleMotionTimeoutAlarmLocked();
}
}
}
private void unregisterStationaryListener(StationaryListener listener) {
synchronized (this) {
if (mStationaryListeners.remove(listener) && mStationaryListeners.size() == 0
// Motion detection is started when transitioning from INACTIVE to IDLE_PENDING
// and so doesn't need to be on for ACTIVE or INACTIVE states.
// Motion detection isn't needed when idling due to Quick Doze.
&& (mState == STATE_ACTIVE || mState == STATE_INACTIVE
|| mQuickDozeActivated)) {
maybeStopMonitoringMotionLocked();
}
}
}
@VisibleForTesting
final class MotionListener extends TriggerEventListener
implements SensorEventListener {
boolean active = false;
/**
* Time in the elapsed realtime timebase when this listener was activated. Only valid if
* {@link #active} is true.
*/
long activatedTimeElapsed;
public boolean isActive() {
return active;
}
@@ -667,7 +748,6 @@ public class DeviceIdleController extends SystemService
@Override
public void onTrigger(TriggerEvent event) {
synchronized (DeviceIdleController.this) {
active = false;
motionLocked();
}
}
@@ -675,8 +755,6 @@ public class DeviceIdleController extends SystemService
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (DeviceIdleController.this) {
mSensorManager.unregisterListener(this, mMotionSensor);
active = false;
motionLocked();
}
}
@@ -694,6 +772,7 @@ public class DeviceIdleController extends SystemService
}
if (success) {
active = true;
activatedTimeElapsed = mInjector.getElapsedRealtime();
} else {
Slog.e(TAG, "Unable to register for " + mMotionSensor);
}
@@ -1307,6 +1386,8 @@ public class DeviceIdleController extends SystemService
private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
@VisibleForTesting
static final int MSG_REPORT_STATIONARY_STATUS = 13;
final class MyHandler extends Handler {
MyHandler(Looper looper) {
@@ -1443,6 +1524,30 @@ public class DeviceIdleController extends SystemService
updatePreIdleFactor();
maybeDoImmediateMaintenance();
} break;
case MSG_REPORT_STATIONARY_STATUS: {
final StationaryListener newListener = (StationaryListener) msg.obj;
final StationaryListener[] listeners;
final boolean isStationary;
synchronized (DeviceIdleController.this) {
isStationary = isStationaryLocked();
if (newListener == null) {
// Only notify all listeners if we aren't directing to one listener.
listeners = mStationaryListeners.toArray(
new StationaryListener[mStationaryListeners.size()]);
} else {
listeners = null;
}
}
if (listeners != null) {
for (StationaryListener listener : listeners) {
listener.onDeviceStationaryChanged(isStationary);
}
}
if (newListener != null) {
newListener.onDeviceStationaryChanged(isStationary);
}
}
break;
}
}
}
@@ -1628,6 +1733,19 @@ public class DeviceIdleController extends SystemService
}
}
/**
* Listener to be notified when DeviceIdleController determines that the device has
* moved or is stationary.
*/
public interface StationaryListener {
/**
* Called when DeviceIdleController has determined that the device is stationary or moving.
*
* @param isStationary true if the device is stationary, false otherwise
*/
void onDeviceStationaryChanged(boolean isStationary);
}
public class LocalService {
public void onConstraintStateChanged(IDeviceIdleConstraint constraint, boolean active) {
synchronized (DeviceIdleController.this) {
@@ -1693,6 +1811,24 @@ public class DeviceIdleController extends SystemService
public int[] getPowerSaveTempWhitelistAppIds() {
return DeviceIdleController.this.getAppIdTempWhitelistInternal();
}
/**
* Registers a listener that will be notified when the system has detected that the device
* is
* stationary or in motion.
*/
public void registerStationaryListener(StationaryListener listener) {
DeviceIdleController.this.registerStationaryListener(listener);
}
/**
* Unregisters a registered stationary listener from being notified when the system has
* detected
* that the device is stationary or in motion.
*/
public void unregisterStationaryListener(StationaryListener listener) {
DeviceIdleController.this.unregisterStationaryListener(listener);
}
}
static class Injector {
@@ -1734,6 +1870,11 @@ public class DeviceIdleController extends SystemService
return mConstants;
}
/** Returns the current elapsed realtime in milliseconds. */
long getElapsedRealtime() {
return SystemClock.elapsedRealtime();
}
LocationManager getLocationManager() {
if (mLocationManager == null) {
mLocationManager = mContext.getSystemService(LocationManager.class);
@@ -2601,6 +2742,8 @@ public class DeviceIdleController extends SystemService
void updateQuickDozeFlagLocked(boolean enabled) {
if (DEBUG) Slog.i(TAG, "updateQuickDozeFlagLocked: enabled=" + enabled);
mQuickDozeActivated = enabled;
mQuickDozeActivatedWhileIdling =
mQuickDozeActivated && (mState == STATE_IDLE || mState == STATE_IDLE_MAINTENANCE);
if (enabled) {
// If Quick Doze is enabled, see if we should go straight into it.
becomeInactiveIfAppropriateLocked();
@@ -2767,10 +2910,11 @@ public class DeviceIdleController extends SystemService
mNextIdleDelay = 0;
mNextLightIdleDelay = 0;
mIdleStartTime = 0;
mQuickDozeActivatedWhileIdling = false;
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
stopMonitoringMotionLocked();
maybeStopMonitoringMotionLocked();
mAnyMotionDetector.stop();
updateActiveConstraintsLocked();
}
@@ -3270,11 +3414,23 @@ public class DeviceIdleController extends SystemService
void motionLocked() {
if (DEBUG) Slog.d(TAG, "motionLocked()");
// The motion sensor will have been disabled at this point
mLastMotionEventElapsed = mInjector.getElapsedRealtime();
handleMotionDetectedLocked(mConstants.MOTION_INACTIVE_TIMEOUT, "motion");
}
void handleMotionDetectedLocked(long timeout, String type) {
if (mStationaryListeners.size() > 0) {
postStationaryStatusUpdated();
scheduleMotionTimeoutAlarmLocked();
}
if (mQuickDozeActivated && !mQuickDozeActivatedWhileIdling) {
// Don't exit idle due to motion if quick doze is enabled.
// However, if the device started idling due to the normal progression (going through
// all the states) and then had quick doze activated, come out briefly on motion so the
// user can get slightly fresher content.
return;
}
maybeStopMonitoringMotionLocked();
// The device is not yet active, so we want to go back to the pending idle
// state to wait again for no motion. Note that we only monitor for motion
// after moving out of the inactive state, so no need to worry about that.
@@ -3326,10 +3482,15 @@ public class DeviceIdleController extends SystemService
}
}
void stopMonitoringMotionLocked() {
if (DEBUG) Slog.d(TAG, "stopMonitoringMotionLocked()");
if (mMotionSensor != null && mMotionListener.active) {
/**
* Stops motion monitoring. Will not stop monitoring if there are registered stationary
* listeners.
*/
private void maybeStopMonitoringMotionLocked() {
if (DEBUG) Slog.d(TAG, "maybeStopMonitoringMotionLocked()");
if (mMotionSensor != null && mMotionListener.active && mStationaryListeners.size() == 0) {
mMotionListener.unregisterLocked();
cancelMotionTimeoutAlarmLocked();
}
}
@@ -3356,6 +3517,10 @@ public class DeviceIdleController extends SystemService
}
}
private void cancelMotionTimeoutAlarmLocked() {
mAlarmManager.cancel(mMotionTimeoutAlarmListener);
}
void cancelSensingTimeoutAlarmLocked() {
if (mNextSensingTimeoutAlarmTime != 0) {
mNextSensingTimeoutAlarmTime = 0;
@@ -3402,6 +3567,14 @@ public class DeviceIdleController extends SystemService
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}
private void scheduleMotionTimeoutAlarmLocked() {
if (DEBUG) Slog.d(TAG, "scheduleMotionAlarmLocked");
long nextMotionTimeoutAlarmTime =
mInjector.getElapsedRealtime() + mConstants.MOTION_INACTIVE_TIMEOUT;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextMotionTimeoutAlarmTime,
"DeviceIdleController.motion", mMotionTimeoutAlarmListener, mHandler);
}
void scheduleSensingTimeoutAlarmLocked(long delay) {
if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
mNextSensingTimeoutAlarmTime = SystemClock.elapsedRealtime() + delay;
@@ -4322,9 +4495,14 @@ public class DeviceIdleController extends SystemService
}
pw.println(" }");
}
if (mUseMotionSensor) {
if (mUseMotionSensor || mStationaryListeners.size() > 0) {
pw.print(" mMotionActive="); pw.println(mMotionListener.active);
pw.print(" mNotMoving="); pw.println(mNotMoving);
pw.print(" mMotionListener.activatedTimeElapsed=");
pw.println(mMotionListener.activatedTimeElapsed);
pw.print(" mLastMotionEventElapsed="); pw.println(mLastMotionEventElapsed);
pw.print(" "); pw.print(mStationaryListeners.size());
pw.println(" stationary listeners registered");
}
pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
pw.print(mHasGps); pw.print(" mHasNetwork=");

View File

@@ -75,6 +75,8 @@ import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.internal.telephony.TelephonyIntents;
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
@@ -178,6 +180,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
private static final int AGPS_SUPL_MODE_MSA = 0x02;
private static final int AGPS_SUPL_MODE_MSB = 0x01;
private static final int UPDATE_LOW_POWER_MODE = 1;
private static final int SET_REQUEST = 3;
private static final int INJECT_NTP_TIME = 5;
// PSDS stands for Predicted Satellite Data Service
@@ -370,6 +373,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
// True if gps should be disabled because of PowerManager controls
private boolean mDisableGpsForPowerManager = false;
/**
* True if the device idle controller has determined that the device is stationary. This is only
* updated when the device enters idle mode.
*/
private volatile boolean mIsDeviceStationary = false;
/**
* Properties loaded from PROPERTIES_FILE.
* It must be accessed only inside {@link #mHandler}.
@@ -462,6 +471,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
public GnssNavigationMessageProvider getGnssNavigationMessageProvider() {
return mGnssNavigationMessageProvider;
}
private final DeviceIdleController.StationaryListener mDeviceIdleStationaryListener =
isStationary -> {
mIsDeviceStationary = isStationary;
// Call updateLowPowerMode on handler thread so it's always called from the same
// thread.
mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -478,11 +496,22 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
case ALARM_TIMEOUT:
hibernate();
break;
case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
DeviceIdleController.LocalService deviceIdleService = LocalServices.getService(
DeviceIdleController.LocalService.class);
if (mPowerManager.isDeviceIdleMode()) {
deviceIdleService.registerStationaryListener(mDeviceIdleStationaryListener);
} else {
deviceIdleService.unregisterStationaryListener(
mDeviceIdleStationaryListener);
}
// Intentional fall-through.
case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
case Intent.ACTION_SCREEN_OFF:
case Intent.ACTION_SCREEN_ON:
updateLowPowerMode();
// Call updateLowPowerMode on handler thread so it's always called from the
// same thread.
mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
break;
case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
@@ -540,10 +569,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
}
private void updateLowPowerMode() {
// Disable GPS if we are in device idle mode.
boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode();
final PowerSaveState result =
mPowerManager.getPowerSaveState(ServiceType.LOCATION);
// Disable GPS if we are in device idle mode and the device is stationary.
boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode() && mIsDeviceStationary;
final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.LOCATION);
switch (result.locationMode) {
case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
case PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
@@ -2048,6 +2076,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
case REPORT_SV_STATUS:
handleReportSvStatus((SvStatusInfo) msg.obj);
break;
case UPDATE_LOW_POWER_MODE:
updateLowPowerMode();
break;
}
if (msg.arg2 == 1) {
// wakelock was taken for this message, release it

View File

@@ -17,6 +17,7 @@ package com.android.server;
import static androidx.test.InstrumentationRegistry.getContext;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
@@ -31,6 +32,7 @@ import static com.android.server.DeviceIdleController.LIGHT_STATE_INACTIVE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_PRE_IDLE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK;
import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS;
import static com.android.server.DeviceIdleController.STATE_ACTIVE;
import static com.android.server.DeviceIdleController.STATE_IDLE;
import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE;
@@ -51,6 +53,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -70,6 +73,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -85,11 +89,13 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
/**
* Tests for {@link com.android.server.DeviceIdleController}.
@@ -97,6 +103,7 @@ import org.mockito.quality.Strictness;
@RunWith(AndroidJUnit4.class)
public class DeviceIdleControllerTest {
private DeviceIdleController mDeviceIdleController;
private DeviceIdleController.MyHandler mHandler;
private AnyMotionDetectorForTest mAnyMotionDetector;
private AppStateTrackerForTest mAppStateTracker;
private DeviceIdleController.Constants mConstants;
@@ -110,8 +117,6 @@ public class DeviceIdleControllerTest {
@Mock
private ContentResolver mContentResolver;
@Mock
private DeviceIdleController.MyHandler mHandler;
@Mock
private IActivityManager mIActivityManager;
@Mock
private LocationManager mLocationManager;
@@ -128,6 +133,8 @@ public class DeviceIdleControllerTest {
ConnectivityService connectivityService;
LocationManager locationManager;
ConstraintController constraintController;
// Freeze time for testing.
long nowElapsed;
InjectorForTest(Context ctx) {
super(ctx);
@@ -154,6 +161,11 @@ public class DeviceIdleControllerTest {
return connectivityService;
}
@Override
long getElapsedRealtime() {
return nowElapsed;
}
@Override
LocationManager getLocationManager() {
return locationManager;
@@ -161,6 +173,23 @@ public class DeviceIdleControllerTest {
@Override
DeviceIdleController.MyHandler getHandler(DeviceIdleController controller) {
if (mHandler == null) {
mHandler = controller.new MyHandler(getContext().getMainLooper());
spyOn(mHandler);
doNothing().when(mHandler).handleMessage(argThat((message) ->
message.what != MSG_REPORT_STATIONARY_STATUS));
doAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
Message msg = invocation.getArgument(0);
mHandler.handleMessage(msg);
return true;
}
}).when(mHandler).sendMessageDelayed(
argThat((message) -> message.what == MSG_REPORT_STATIONARY_STATUS),
anyLong());
}
return mHandler;
}
@@ -226,6 +255,19 @@ public class DeviceIdleControllerTest {
}
}
private class StationaryListenerForTest implements DeviceIdleController.StationaryListener {
boolean motionExpected = false;
boolean isStationary = false;
@Override
public void onDeviceStationaryChanged(boolean isStationary) {
if (isStationary == motionExpected) {
fail("Unexpected device stationary status: " + isStationary);
}
this.isStationary = isStationary;
}
}
@Before
public void setUp() {
mMockingSession = mockitoSession()
@@ -255,8 +297,6 @@ public class DeviceIdleControllerTest {
doReturn(true).when(mSensorManager).registerListener(any(), any(), anyInt());
mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper());
mAnyMotionDetector = new AnyMotionDetectorForTest();
mHandler = mock(DeviceIdleController.MyHandler.class, Answers.RETURNS_DEEP_STUBS);
doNothing().when(mHandler).handleMessage(any());
mInjector = new InjectorForTest(getContext());
doNothing().when(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
@@ -1607,6 +1647,86 @@ public class DeviceIdleControllerTest {
1.0f, curfactor, delta);
}
@Test
public void testStationaryDetection_QuickDozeOff() {
setQuickDozeEnabled(false);
enterDeepState(STATE_IDLE);
// Regular progression through states, so time should have increased appropriately.
mInjector.nowElapsed += mConstants.IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.SENSING_TIMEOUT
+ mConstants.LOCATING_TIMEOUT;
StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
mDeviceIdleController.registerStationaryListener(stationaryListener);
// Go to IDLE_MAINTENANCE
mDeviceIdleController.stepIdleStateLocked("testing");
// Back to IDLE
mDeviceIdleController.stepIdleStateLocked("testing");
assertTrue(stationaryListener.isStationary);
// Test motion
stationaryListener.motionExpected = true;
mDeviceIdleController.mMotionListener.onTrigger(null);
assertFalse(stationaryListener.isStationary);
}
@Test
public void testStationaryDetection_QuickDozeOn() {
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"),
alarmListener.capture(), any());
StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
stationaryListener.motionExpected = true;
mDeviceIdleController.registerStationaryListener(stationaryListener);
assertFalse(stationaryListener.isStationary);
// Go to IDLE_MAINTENANCE
mDeviceIdleController.stepIdleStateLocked("testing");
mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
// Back to IDLE
mDeviceIdleController.stepIdleStateLocked("testing");
// Now enough time has passed.
mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
stationaryListener.motionExpected = false;
alarmListener.getValue().onAlarm();
assertTrue(stationaryListener.isStationary);
stationaryListener.motionExpected = true;
mDeviceIdleController.mMotionListener.onSensorChanged(null);
assertFalse(stationaryListener.isStationary);
// Since we're in quick doze, the device shouldn't stop idling.
verifyStateConditions(STATE_IDLE);
// Go to IDLE_MAINTENANCE
mDeviceIdleController.stepIdleStateLocked("testing");
mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
// Back to IDLE
mDeviceIdleController.stepIdleStateLocked("testing");
// Now enough time has passed.
mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
stationaryListener.motionExpected = false;
alarmListener.getValue().onAlarm();
assertTrue(stationaryListener.isStationary);
}
private void enterDeepState(int state) {
switch (state) {
case STATE_ACTIVE: