diff options
3 files changed, 131 insertions, 1 deletions
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index 36199debaa6e..9d4f18113555 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -371,6 +371,17 @@ final class BatteryController { } } + public void notifyStylusGestureStarted(int deviceId, long eventTime) { + synchronized (mLock) { + final DeviceMonitor monitor = mDeviceMonitors.get(deviceId); + if (monitor == null) { + return; + } + + monitor.onStylusGestureStarted(eventTime); + } + } + public void dump(PrintWriter pw, String prefix) { synchronized (mLock) { final String indent = prefix + " "; @@ -557,6 +568,8 @@ final class BatteryController { public void onTimeout(long eventTime) {} + public void onStylusGestureStarted(long eventTime) {} + // Returns the current battery state that can be used to notify listeners BatteryController. public State getBatteryStateForReporting() { return new State(mState); @@ -600,6 +613,22 @@ final class BatteryController { } @Override + public void onStylusGestureStarted(long eventTime) { + processChangesAndNotify(eventTime, (time) -> { + final boolean wasValid = mValidityTimeoutCallback != null; + if (!wasValid && mState.capacity == 0.f) { + // Handle a special case where the USI device reports a battery capacity of 0 + // at boot until the first battery update. To avoid wrongly sending out a + // battery capacity of 0 if we detect stylus presence before the capacity + // is first updated, do not validate the battery state when the state is not + // valid and the capacity is 0. + return; + } + markUsiBatteryValid(); + }); + } + + @Override public void onTimeout(long eventTime) { processChangesAndNotify(eventTime, (time) -> markUsiBatteryInvalid()); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 3d990cc7c9c4..08e16763c6aa 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -3055,6 +3055,7 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") private void notifyStylusGestureStarted(int deviceId, long eventTime) { + mBatteryController.notifyStylusGestureStarted(deviceId, eventTime); } /** 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 c68db3460dac..6590a2b500e4 100644 --- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt @@ -36,6 +36,7 @@ import androidx.test.InstrumentationRegistry import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS import com.android.server.input.BatteryController.UEventManager import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener +import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.MatcherAssert.assertThat @@ -528,10 +529,109 @@ class BatteryControllerTests { matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f)) // The battery is no longer present after the timeout expires. - testLooper.moveTimeForward(BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS - 1) + testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 1) testLooper.dispatchNext() listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2)) assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), isInvalidBatteryState(USI_DEVICE_ID)) } + + @Test + fun testStylusPresenceExtendsValidUsiBatteryState() { + `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") + `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING) + `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78) + + addInputDevice(USI_DEVICE_ID, supportsUsi = true) + testLooper.dispatchNext() + val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) + verify(uEventManager) + .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) + + // There is a UEvent signaling a battery change. The battery state is now valid. + uEventListener.value!!.onBatteryUEvent(TIMESTAMP) + val listener = createMockListener() + batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) + listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f) + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)) + + // Stylus presence is detected before the validity timeout expires. + testLooper.moveTimeForward(100) + testLooper.dispatchAll() + batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP) + + // Ensure that timeout was extended, and the battery state is now valid for longer. + testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 100) + testLooper.dispatchAll() + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)) + + // Ensure the validity period expires after the expected amount of time. + testLooper.moveTimeForward(100) + testLooper.dispatchNext() + listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID)) + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + isInvalidBatteryState(USI_DEVICE_ID)) + } + + @Test + fun testStylusPresenceMakesUsiBatteryStateValid() { + `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") + `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING) + `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78) + + addInputDevice(USI_DEVICE_ID, supportsUsi = true) + testLooper.dispatchNext() + val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) + verify(uEventManager) + .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) + + // The USI battery state is initially invalid. + val listener = createMockListener() + batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) + listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID)) + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + isInvalidBatteryState(USI_DEVICE_ID)) + + // A stylus presence is detected. This validates the battery state. + batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP) + + listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f) + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)) + } + + @Test + fun testStylusPresenceDoesNotMakeUsiBatteryStateValidAtBoot() { + `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") + // At boot, the USI device always reports a capacity value of 0. + `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_UNKNOWN) + `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(0) + + addInputDevice(USI_DEVICE_ID, supportsUsi = true) + testLooper.dispatchNext() + val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) + verify(uEventManager) + .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) + + // The USI battery state is initially invalid. + val listener = createMockListener() + batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) + listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID)) + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + isInvalidBatteryState(USI_DEVICE_ID)) + + // Since the capacity reported is 0, stylus presence does not validate the battery state. + batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP) + + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + isInvalidBatteryState(USI_DEVICE_ID)) + + // However, if a UEvent reports a battery capacity of 0, the battery state is now valid. + uEventListener.value!!.onBatteryUEvent(TIMESTAMP) + listener.verifyNotified(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f) + assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), + matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f)) + } } |