diff options
| author | 2022-09-26 17:15:21 +0000 | |
|---|---|---|
| committer | 2022-10-26 20:31:25 +0000 | |
| commit | 6d5a29bc19f7a13404ec20a930d0c9f1245a157a (patch) | |
| tree | 1bf84d5f581831ede845f84bc9a9b33819259cae | |
| parent | 545592604ba536896d597845e8db8049e451a135 (diff) | |
Introduce monitoring behavior changes for USI devices (3/n)
Extend USI battery timeout when stylus presence is detected.
The kernel only sends a UEvent for a battery node if there is a change
in the battery state. For example, when a stylus is first being used,
its battery value will be reported via a UEvent. If the stylus is used
again the next day and its battery level has not changed, no UEvent will
be produced.
To address this case, we will validate the USI battery state whenever
the presence of a stylus is detected and extend the validity timeout.
The only exception is when the stylus battery capacity is 0. When
Android first boots, the USI battery capacity will be repoted as 0. In
the event that stylus presence is detected before the battery level is
updated, we do not want to notify listeners of the capacity being 0.
Bug: 243005009
Test: atest BatteryControllerTests
Change-Id: I746496b618d7a6f51f34db65e69e7eb88c92fc87
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)) + } } |