Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 47300feb authored by Prabir Pradhan's avatar Prabir Pradhan Committed by Android (Google) Code Review
Browse files

Merge "Introduce monitoring behavior changes for USI devices (2/n)"

parents 31071af4 12b56577
Loading
Loading
Loading
Loading
+86 −4
Original line number Diff line number Diff line
@@ -65,6 +65,8 @@ final class BatteryController {

    @VisibleForTesting
    static final long POLLING_PERIOD_MILLIS = 10_000; // 10 seconds
    @VisibleForTesting
    static final long USI_BATTERY_VALIDITY_DURATION_MILLIS = 60 * 60_000; // 1 hour

    private final Object mLock = new Object();
    private final Context mContext;
@@ -336,6 +338,17 @@ final class BatteryController {
        }
    }

    private void handleMonitorTimeout(int deviceId) {
        synchronized (mLock) {
            final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
            if (monitor == null) {
                return;
            }
            final long updateTime = SystemClock.uptimeMillis();
            monitor.onTimeout(updateTime);
        }
    }

    /** Gets the current battery state of an input device. */
    public IInputDeviceBatteryState getBatteryState(int deviceId) {
        synchronized (mLock) {
@@ -448,7 +461,7 @@ final class BatteryController {

    // Holds the state of an InputDevice for which battery changes are currently being monitored.
    private class DeviceMonitor {
        private final State mState;
        protected final State mState;
        // Represents whether the input device has a sysfs battery node.
        protected boolean mHasBattery = false;

@@ -463,7 +476,7 @@ final class BatteryController {
            configureDeviceMonitor(eventTime);
        }

        private void processChangesAndNotify(long eventTime, Consumer<Long> changes) {
        protected void processChangesAndNotify(long eventTime, Consumer<Long> changes) {
            final State oldState = getBatteryStateForReporting();
            changes.accept(eventTime);
            final State newState = getBatteryStateForReporting();
@@ -521,7 +534,7 @@ final class BatteryController {
            stopMonitoring();
        }

        private void updateBatteryStateFromNative(long eventTime) {
        protected void updateBatteryStateFromNative(long eventTime) {
            mState.updateIfChanged(
                    queryBatteryStateFromNative(mState.deviceId, eventTime, mHasBattery));
        }
@@ -542,6 +555,8 @@ final class BatteryController {
            return false;
        }

        public void onTimeout(long eventTime) {}

        // Returns the current battery state that can be used to notify listeners BatteryController.
        public State getBatteryStateForReporting() {
            return new State(mState);
@@ -560,10 +575,71 @@ final class BatteryController {
    // Universal Stylus Initiative (USI) protocol.
    private class UsiDeviceMonitor extends DeviceMonitor {

        // For USI devices, we only treat the battery state as valid for a fixed amount of time
        // after receiving a battery update. Once the timeout has passed, we signal to all listeners
        // that there is no longer a battery present for the device. The battery state is valid
        // as long as this callback is non-null.
        @Nullable
        private Runnable mValidityTimeoutCallback;

        UsiDeviceMonitor(int deviceId) {
            super(deviceId);
        }

        @Override
        public void onPoll(long eventTime) {
            // Disregard polling for USI devices.
        }

        @Override
        public void onUEvent(long eventTime) {
            processChangesAndNotify(eventTime, (time) -> {
                updateBatteryStateFromNative(time);
                markUsiBatteryValid();
            });
        }

        @Override
        public void onTimeout(long eventTime) {
            processChangesAndNotify(eventTime, (time) -> markUsiBatteryInvalid());
        }

        @Override
        public void onConfiguration(long eventTime) {
            super.onConfiguration(eventTime);

            if (!mHasBattery) {
                throw new IllegalStateException(
                        "UsiDeviceMonitor: USI devices are always expected to "
                                + "report a valid battery, but no battery was detected!");
            }
        }

        private void markUsiBatteryValid() {
            if (mValidityTimeoutCallback != null) {
                mHandler.removeCallbacks(mValidityTimeoutCallback);
            } else {
                final int deviceId = mState.deviceId;
                mValidityTimeoutCallback =
                        () -> BatteryController.this.handleMonitorTimeout(deviceId);
            }
            mHandler.postDelayed(mValidityTimeoutCallback, USI_BATTERY_VALIDITY_DURATION_MILLIS);
        }

        private void markUsiBatteryInvalid() {
            if (mValidityTimeoutCallback == null) {
                return;
            }
            mHandler.removeCallbacks(mValidityTimeoutCallback);
            mValidityTimeoutCallback = null;
        }

        @Override
        public State getBatteryStateForReporting() {
            return mValidityTimeoutCallback != null
                    ? new State(mState) : new State(mState.deviceId);
        }

        @Override
        public boolean requiresPolling() {
            // Do not poll the battery state for USI devices.
@@ -575,6 +651,12 @@ final class BatteryController {
            // Do not remove the battery monitor for USI devices.
            return true;
        }

        @Override
        public String toString() {
            return super.toString()
                    + ", UsiStateIsValid=" + (mValidityTimeoutCallback != null);
        }
    }

    // An interface used to change the API of UEventObserver to a more test-friendly format.
@@ -635,7 +717,7 @@ final class BatteryController {
            }
        }

        private void reset(int deviceId) {
        public void reset(int deviceId) {
            initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN,
                    Float.NaN /*capacity*/);
        }
+50 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.input
import android.content.Context
import android.content.ContextWrapper
import android.hardware.BatteryState.STATUS_CHARGING
import android.hardware.BatteryState.STATUS_DISCHARGING
import android.hardware.BatteryState.STATUS_FULL
import android.hardware.BatteryState.STATUS_UNKNOWN
import android.hardware.input.IInputDeviceBatteryListener
@@ -484,4 +485,53 @@ class BatteryControllerTests {
        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
        assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
    }

    @Test
    fun testExpectedFlowForUsiBattery() {
        `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"))

        // A USI device's battery state is not valid until the first UEvent notification.
        // Add a listener, and ensure it is notified that the battery state is not present.
        val listener = createMockListener()
        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))

        // Ensure that querying for battery state also returns the same invalid result.
        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
            isInvalidBatteryState(USI_DEVICE_ID))

        // There is a UEvent signaling a battery change. The battery state is now valid.
        uEventListener.value!!.onBatteryUEvent(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))

        // There is another UEvent notification. The battery state is now updated.
        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(64)
        uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f)
        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))

        // The battery state is still valid after a millisecond.
        testLooper.moveTimeForward(1)
        testLooper.dispatchAll()
        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
            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.dispatchNext()
        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2))
        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
            isInvalidBatteryState(USI_DEVICE_ID))
    }
}