Loading services/core/java/com/android/server/input/BatteryController.java +57 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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() {}); } Loading Loading @@ -135,6 +143,7 @@ final class BatteryController implements InputManager.InputDeviceListener { + " is monitoring deviceId " + deviceId); } updatePollingLocked(true /*delayStart*/); notifyBatteryListener(listenerRecord, deviceState); } } Loading Loading @@ -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)) Loading Loading @@ -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") Loading Loading @@ -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)); } Loading services/core/java/com/android/server/input/InputManagerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -3684,6 +3684,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void setInteractive(boolean interactive) { mNative.setInteractive(interactive); mBatteryController.onInteractiveChanged(interactive); } @Override Loading services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +67 −6 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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()) } } Loading
services/core/java/com/android/server/input/BatteryController.java +57 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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() {}); } Loading Loading @@ -135,6 +143,7 @@ final class BatteryController implements InputManager.InputDeviceListener { + " is monitoring deviceId " + deviceId); } updatePollingLocked(true /*delayStart*/); notifyBatteryListener(listenerRecord, deviceState); } } Loading Loading @@ -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)) Loading Loading @@ -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") Loading Loading @@ -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)); } Loading
services/core/java/com/android/server/input/InputManagerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -3684,6 +3684,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void setInteractive(boolean interactive) { mNative.setInteractive(interactive); mBatteryController.onInteractiveChanged(interactive); } @Override Loading
services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +67 −6 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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()) } }