Ensure AlarmManager knows of IDLE state.

AlarmManager relies on AlarmManager.setIdleUntil to know when the device
is entering the IDLE state. Devices that want to use a motion sensor
but don't have one normally won't proceed to the IDLE state, but someone
could force the device into the IDLE state via adb. In that situation,
AlarmManager should still be told that the device entered the IDLE
state. The alarm scheduling method expects mState to be updated before
its called, so if the method is called before the state is updated, it
will do the wrong thing.

Bug: 270672228
Test: atest DeviceIdleTest
Test: atest FrameworksMockingServicesTests:DeviceIdleControllerTest
Change-Id: I95a06e37f218bfebc86ffa6a984b50b60013af7d
This commit is contained in:
Kweku Adams
2023-03-09 18:12:21 +00:00
parent bf1ccb7831
commit 26987e0338
2 changed files with 156 additions and 4 deletions

View File

@@ -2402,7 +2402,6 @@ public class DeviceIdleController extends SystemService
return mConstants;
}
/** Returns the current elapsed realtime in milliseconds. */
long getElapsedRealtime() {
return SystemClock.elapsedRealtime();
@@ -3819,6 +3818,7 @@ public class DeviceIdleController extends SystemService
// Everything is in place to go into IDLE state.
case STATE_IDLE_MAINTENANCE:
moveToStateLocked(STATE_IDLE, reason);
scheduleAlarmLocked(mNextIdleDelay, true);
if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
" ms.");
@@ -3829,7 +3829,6 @@ public class DeviceIdleController extends SystemService
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
}
moveToStateLocked(STATE_IDLE, reason);
if (mLightState != LIGHT_STATE_OVERRIDE) {
moveToLightStateLocked(LIGHT_STATE_OVERRIDE, "deep");
cancelLightAlarmLocked();
@@ -3842,6 +3841,7 @@ public class DeviceIdleController extends SystemService
// We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire();
moveToStateLocked(STATE_IDLE_MAINTENANCE, reason);
scheduleAlarmLocked(mNextIdlePendingDelay, false);
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
"Next alarm in " + mNextIdlePendingDelay + " ms.");
@@ -3851,7 +3851,6 @@ public class DeviceIdleController extends SystemService
if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
}
moveToStateLocked(STATE_IDLE_MAINTENANCE, reason);
addEvent(EVENT_DEEP_MAINTENANCE, null);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;

View File

@@ -154,6 +154,7 @@ public class DeviceIdleControllerTest {
ConstraintController constraintController;
// Freeze time for testing.
long nowElapsed;
boolean useMotionSensor = true;
InjectorForTest(Context ctx) {
super(ctx);
@@ -245,7 +246,7 @@ public class DeviceIdleControllerTest {
@Override
boolean useMotionSensor() {
return true;
return useMotionSensor;
}
}
@@ -345,6 +346,12 @@ public class DeviceIdleControllerTest {
mAnyMotionDetector = new AnyMotionDetectorForTest();
mInjector = new InjectorForTest(getContext());
setupDeviceIdleController();
}
private void setupDeviceIdleController() {
reset(mTelephonyManager);
mDeviceIdleController = new DeviceIdleController(getContext(), mInjector);
spyOn(mDeviceIdleController);
doNothing().when(mDeviceIdleController).publishBinderService(any(), any());
@@ -371,6 +378,10 @@ public class DeviceIdleControllerTest {
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
}
@After
public void cleanupDeviceIdleController() {
// DeviceIdleController adds these to LocalServices in the constructor, so we have to remove
// them after each test, otherwise, subsequent tests will fail.
LocalServices.removeServiceForTest(AppStateTracker.class);
@@ -617,6 +628,60 @@ public class DeviceIdleControllerTest {
.scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
}
@Test
public void testStateActiveToStateInactive_DoNotUseMotionSensor() {
mInjector.useMotionSensor = false;
cleanupDeviceIdleController();
setupDeviceIdleController();
mDeviceIdleController.becomeActiveLocked("testing", 0);
verifyStateConditions(STATE_ACTIVE);
setAlarmSoon(false);
setChargingOn(false);
setScreenOn(false);
setEmergencyCallActive(false);
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_INACTIVE);
verify(mDeviceIdleController)
.scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
// The device configuration doesn't require a motion sensor to proceed with idling.
// This should be the case on TVs or other such devices. We should set an alarm to move
// forward if the motion sensor is missing in this case.
verify(mAlarmManager).setWindow(
anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
}
@Test
public void testStateActiveToStateInactive_MissingMotionSensor() {
mInjector.useMotionSensor = true;
mMotionSensor = null;
cleanupDeviceIdleController();
setupDeviceIdleController();
mDeviceIdleController.becomeActiveLocked("testing", 0);
verifyStateConditions(STATE_ACTIVE);
setAlarmSoon(false);
setChargingOn(false);
setScreenOn(false);
setEmergencyCallActive(false);
mDeviceIdleController.becomeInactiveIfAppropriateLocked();
verifyStateConditions(STATE_INACTIVE);
verify(mDeviceIdleController)
.scheduleAlarmLocked(eq(mConstants.INACTIVE_TIMEOUT), eq(false));
// The device configuration requires a motion sensor to proceed with idling,
// so we should never set an alarm to move forward if the motion sensor is
// missing in this case.
verify(mAlarmManager, never()).setWindow(
anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
verify(mAlarmManager, never()).set(
anyInt(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
}
@Test
public void testStateActiveToStateInactive_UpcomingAlarm() {
final long timeUntilAlarm = mConstants.MIN_TIME_TO_ALARM / 2;
@@ -756,6 +821,94 @@ public class DeviceIdleControllerTest {
verifyStateConditions(STATE_IDLE_MAINTENANCE);
}
@Test
public void testStepIdleStateLocked_ValidStates_MissingMotionSensor() {
mInjector.useMotionSensor = true;
mMotionSensor = null;
cleanupDeviceIdleController();
setupDeviceIdleController();
mInjector.locationManager = mLocationManager;
doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString());
// Make sure the controller doesn't think there's a wake-from-idle alarm coming soon.
setAlarmSoon(false);
InOrder alarmManagerInOrder = inOrder(mAlarmManager);
// Set state to INACTIVE.
mDeviceIdleController.becomeActiveLocked("testing", 0);
setChargingOn(false);
setScreenOn(false);
verifyStateConditions(STATE_INACTIVE);
// The device configuration requires a motion sensor to proceed with idling,
// so we should never set an alarm to move forward if the motion sensor is
// missing in this case.
alarmManagerInOrder.verify(mAlarmManager, never())
.setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
// Pretend that someone is forcing state stepping via adb
mDeviceIdleController.stepIdleStateLocked("testing");
// verifyStateConditions knows this state typically shouldn't happen during normal
// operation, so we can't use it directly here. For this test, all we care about
// is that the state stepped forward.
assertEquals(STATE_IDLE_PENDING, mDeviceIdleController.getState());
// Still no alarm
alarmManagerInOrder.verify(mAlarmManager, never())
.setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
mDeviceIdleController.stepIdleStateLocked("testing");
// verifyStateConditions knows this state typically shouldn't happen during normal
// operation, so we can't use it directly here. For this test, all we care about
// is that the state stepped forward.
assertEquals(STATE_SENSING, mDeviceIdleController.getState());
// Still no alarm
alarmManagerInOrder.verify(mAlarmManager, never())
.setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
mDeviceIdleController.stepIdleStateLocked("testing");
// Location manager exists with a provider, so SENSING should go to LOCATING.
// verifyStateConditions knows this state typically shouldn't happen during normal
// operation, so we can't use it directly here. For this test, all we care about
// is that the state stepped forward.
assertEquals(STATE_LOCATING, mDeviceIdleController.getState());
// Still no alarm
alarmManagerInOrder.verify(mAlarmManager, never())
.setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
mDeviceIdleController.stepIdleStateLocked("testing");
verifyStateConditions(STATE_IDLE);
// The device was forced into IDLE. AlarmManager should be notified.
alarmManagerInOrder.verify(mAlarmManager)
.setIdleUntil(anyInt(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
// Should just alternate between IDLE and IDLE_MAINTENANCE now. Since we've gotten to this
// point, alarms should be set on each transition.
mDeviceIdleController.stepIdleStateLocked("testing");
verifyStateConditions(STATE_IDLE_MAINTENANCE);
alarmManagerInOrder.verify(mAlarmManager)
.setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
mDeviceIdleController.stepIdleStateLocked("testing");
verifyStateConditions(STATE_IDLE);
alarmManagerInOrder.verify(mAlarmManager)
.setIdleUntil(anyInt(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
mDeviceIdleController.stepIdleStateLocked("testing");
verifyStateConditions(STATE_IDLE_MAINTENANCE);
alarmManagerInOrder.verify(mAlarmManager)
.setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.deep"), any(), any(Handler.class));
}
@Test
public void testStepIdleStateLocked_ValidStates_WithWakeFromIdleAlarmSoon() {
enterDeepState(STATE_ACTIVE);