Merge "Poll for battery changes when Android is interactive"
This commit is contained in:
committed by
Android (Google) Code Review
commit
4b90718a5a
@@ -55,6 +55,9 @@ final class BatteryController implements InputManager.InputDeviceListener {
|
||||
// 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart)
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
@VisibleForTesting
|
||||
static final long POLLING_PERIOD_MILLIS = 10_000; // 10 seconds
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final Context mContext;
|
||||
private final NativeInputManagerService mNative;
|
||||
@@ -71,6 +74,11 @@ final class BatteryController implements InputManager.InputDeviceListener {
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Integer, MonitoredDeviceState> mMonitoredDeviceStates = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean mIsPolling = false;
|
||||
@GuardedBy("mLock")
|
||||
private boolean mIsInteractive = true;
|
||||
|
||||
BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
|
||||
this(context, nativeService, looper, new UEventManager() {});
|
||||
}
|
||||
@@ -135,6 +143,7 @@ final class BatteryController implements InputManager.InputDeviceListener {
|
||||
+ " is monitoring deviceId " + deviceId);
|
||||
}
|
||||
|
||||
updatePollingLocked(true /*delayStart*/);
|
||||
notifyBatteryListener(listenerRecord, deviceState);
|
||||
}
|
||||
}
|
||||
@@ -162,6 +171,23 @@ final class BatteryController implements InputManager.InputDeviceListener {
|
||||
});
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updatePollingLocked(boolean delayStart) {
|
||||
if (mMonitoredDeviceStates.isEmpty() || !mIsInteractive) {
|
||||
// Stop polling.
|
||||
mIsPolling = false;
|
||||
mHandler.removeCallbacks(this::handlePollEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsPolling) {
|
||||
return;
|
||||
}
|
||||
// Start polling.
|
||||
mIsPolling = true;
|
||||
mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0);
|
||||
}
|
||||
|
||||
private boolean hasBattery(int deviceId) {
|
||||
final InputDevice device =
|
||||
Objects.requireNonNull(mContext.getSystemService(InputManager.class))
|
||||
@@ -232,6 +258,8 @@ final class BatteryController implements InputManager.InputDeviceListener {
|
||||
mListenerRecords.remove(pid);
|
||||
if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid);
|
||||
}
|
||||
|
||||
updatePollingLocked(false /*delayStart*/);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -273,10 +301,37 @@ final class BatteryController implements InputManager.InputDeviceListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePollEvent() {
|
||||
synchronized (mLock) {
|
||||
if (!mIsPolling) {
|
||||
return;
|
||||
}
|
||||
final long eventTime = SystemClock.uptimeMillis();
|
||||
mMonitoredDeviceStates.forEach((deviceId, deviceState) -> {
|
||||
// Re-acquire lock in the lambda to silence error-prone build warnings.
|
||||
synchronized (mLock) {
|
||||
if (deviceState.updateBatteryState(eventTime)) {
|
||||
notifyAllListenersForDeviceLocked(deviceState);
|
||||
}
|
||||
}
|
||||
});
|
||||
mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
void onInteractiveChanged(boolean interactive) {
|
||||
synchronized (mLock) {
|
||||
mIsInteractive = interactive;
|
||||
updatePollingLocked(false /*delayStart*/);
|
||||
}
|
||||
}
|
||||
|
||||
void dump(PrintWriter pw, String prefix) {
|
||||
synchronized (mLock) {
|
||||
pw.println(prefix + TAG + ": " + mListenerRecords.size()
|
||||
+ " battery listeners");
|
||||
pw.println(prefix + TAG + ": "
|
||||
+ mListenerRecords.size() + " battery listeners"
|
||||
+ ", Polling = " + mIsPolling
|
||||
+ ", Interactive = " + mIsInteractive);
|
||||
for (int i = 0; i < mListenerRecords.size(); i++) {
|
||||
pw.println(prefix + " " + i + ": " + mListenerRecords.valueAt(i));
|
||||
}
|
||||
|
||||
@@ -3684,6 +3684,7 @@ public class InputManagerService extends IInputManager.Stub
|
||||
@Override
|
||||
public void setInteractive(boolean interactive) {
|
||||
mNative.setInteractive(interactive);
|
||||
mBatteryController.onInteractiveChanged(interactive);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -233,16 +233,20 @@ class BatteryControllerTests {
|
||||
val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
|
||||
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
|
||||
verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
|
||||
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
eq(STATUS_CHARGING), eq(0.78f), anyLong())
|
||||
verify(listener).onBatteryStateChanged(
|
||||
eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
eq(STATUS_CHARGING), eq(0.78f), anyLong()
|
||||
)
|
||||
|
||||
// If the battery presence for the InputDevice changes, the listener is notified.
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID))
|
||||
.thenReturn(createInputDevice(DEVICE_ID, hasBattery = false))
|
||||
notifyDeviceChanged(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(false /*isPresent*/),
|
||||
eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong())
|
||||
verify(listener).onBatteryStateChanged(
|
||||
eq(DEVICE_ID), eq(false /*isPresent*/),
|
||||
eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong()
|
||||
)
|
||||
// Since the battery is no longer present, the UEventListener should be removed.
|
||||
verify(uEventManager).removeListener(uEventListener.value)
|
||||
|
||||
@@ -251,10 +255,67 @@ class BatteryControllerTests {
|
||||
.thenReturn(createInputDevice(DEVICE_ID, hasBattery = true))
|
||||
notifyDeviceChanged(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
verify(listener, times(2)).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
eq(STATUS_CHARGING), eq(0.78f), anyLong())
|
||||
verify(listener, times(2)).onBatteryStateChanged(
|
||||
eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
eq(STATUS_CHARGING), eq(0.78f), anyLong()
|
||||
)
|
||||
// Ensure that a new UEventListener was added.
|
||||
verify(uEventManager, times(2))
|
||||
.addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
|
||||
}
|
||||
|
||||
fun testStartPollingWhenListenerIsRegistered() {
|
||||
val listener = createMockListener()
|
||||
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
|
||||
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
|
||||
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
|
||||
eq(0.78f), anyLong())
|
||||
|
||||
// Assume there is a change in the battery state. Ensure the listener is not notified
|
||||
// while the polling period has not elapsed.
|
||||
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
|
||||
testLooper.moveTimeForward(1)
|
||||
testLooper.dispatchAll()
|
||||
verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
anyInt(), eq(0.80f), anyLong())
|
||||
|
||||
// Move the time forward so that the polling period has elapsed.
|
||||
// The listener should be notified.
|
||||
testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS - 1)
|
||||
testLooper.dispatchNext()
|
||||
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
|
||||
eq(0.80f), anyLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoPollingWhenTheDeviceIsNotInteractive() {
|
||||
batteryController.onInteractiveChanged(false /*interactive*/)
|
||||
|
||||
val listener = createMockListener()
|
||||
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
|
||||
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
|
||||
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
|
||||
eq(0.78f), anyLong())
|
||||
|
||||
// The battery state changed, but we should not be polling for battery changes when the
|
||||
// device is not interactive.
|
||||
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
|
||||
testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS)
|
||||
testLooper.dispatchAll()
|
||||
verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
anyInt(), eq(0.80f), anyLong())
|
||||
|
||||
// The device is now interactive. Battery state polling begins immediately.
|
||||
batteryController.onInteractiveChanged(true /*interactive*/)
|
||||
testLooper.dispatchNext()
|
||||
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
anyInt(), eq(0.80f), anyLong())
|
||||
|
||||
// Ensure that we continue to poll for battery changes.
|
||||
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90)
|
||||
testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS)
|
||||
testLooper.dispatchNext()
|
||||
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||
anyInt(), eq(0.90f), anyLong())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user