summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/input/BatteryController.java59
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt73
3 files changed, 125 insertions, 8 deletions
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 86732a12ec2c..32f17dc18033 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -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));
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4da0c0df390c..05c4f77d5c81 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3684,6 +3684,7 @@ public class InputManagerService extends IInputManager.Stub
@Override
public void setInteractive(boolean interactive) {
mNative.setInteractive(interactive);
+ mBatteryController.onInteractiveChanged(interactive);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index d118661b84f5..40757182890c 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -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())
+ }
}