Merge "Don't disable GPS in Doze until the device is stationary." into qt-qpr1-dev

This commit is contained in:
TreeHugger Robot
2019-10-30 23:14:48 +00:00
committed by Android (Google) Code Review
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: