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

Commit 99943d7d authored by Vania Januar's avatar Vania Januar
Browse files

UIEvent for tracking USI battery presence.

Bug: 267816496
Test: StylusManagerTest
Change-Id: I69edb7fc04f4af5ca1768aaf0001f62885bc71d3
parent bf0e9a82
Loading
Loading
Loading
Loading
+54 −2
Original line number Diff line number Diff line
@@ -30,6 +30,9 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.shared.hardware.hasInputDevice
import com.android.systemui.shared.hardware.isInternalStylusSource
import com.android.systemui.statusbar.notification.collection.listbuilder.DEBUG
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -59,8 +62,10 @@ constructor(
        CopyOnWriteArrayList()
    // This map should only be accessed on the handler
    private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
    // This variable should only be accessed on the handler

    // These variables should only be accessed on the handler
    private var hasStarted: Boolean = false
    private var isInUsiSession: Boolean = false

    /**
     * Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot
@@ -70,6 +75,10 @@ constructor(
        handler.post {
            if (hasStarted) return@post
            hasStarted = true
            isInUsiSession =
                inputManager.hasInputDevice {
                    it.isInternalStylusSource && isBatteryStateValid(it.batteryState)
                }
            addExistingStylusToMap()

            inputManager.registerInputDeviceListener(this, handler)
@@ -177,7 +186,18 @@ constructor(
        handler.post {
            if (!hasStarted) return@post

            if (batteryState.isPresent) {
            if (DEBUG) {
                Log.d(
                    TAG,
                    "onBatteryStateChanged for $deviceId. " +
                        "batteryState present: ${batteryState.isPresent}, " +
                        "capacity: ${batteryState.capacity}"
                )
            }

            val batteryStateValid = isBatteryStateValid(batteryState)
            trackAndLogUsiSession(deviceId, batteryStateValid)
            if (batteryStateValid) {
                onStylusUsed()
            }

@@ -221,6 +241,37 @@ constructor(
        executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
    }

    /**
     * Uses the input device battery state to track whether a current USI session is active. The
     * InputDevice battery state updates USI battery on USI stylus input, and removes the last-known
     * USI stylus battery presence after 1 hour of not detecting input. As SysUI and StylusManager
     * is persistently running, relies on tracking sessions via an in-memory isInUsiSession boolean.
     */
    private fun trackAndLogUsiSession(deviceId: Int, batteryStateValid: Boolean) {
        // TODO(b/268618918) handle cases where an invalid battery callback from a previous stylus
        //  is sent after the actual valid callback
        if (batteryStateValid && !isInUsiSession) {
            if (DEBUG) {
                Log.d(
                    TAG,
                    "USI battery newly present, entering new USI session. Device ID: $deviceId"
                )
            }
            isInUsiSession = true
            uiEventLogger.log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED)
        } else if (!batteryStateValid && isInUsiSession) {
            if (DEBUG) {
                Log.d(TAG, "USI battery newly absent, exiting USI session Device ID: $deviceId")
            }
            isInUsiSession = false
            uiEventLogger.log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED)
        }
    }

    private fun isBatteryStateValid(batteryState: BatteryState): Boolean {
        return batteryState.isPresent && batteryState.capacity > 0.0f
    }

    private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) {
        stylusCallbacks.forEach(run)
    }
@@ -295,5 +346,6 @@ constructor(

    companion object {
        private val TAG = StylusManager::class.simpleName.orEmpty()
        private val DEBUG = false
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -31,7 +31,11 @@ enum class StylusUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
    @UiEvent(doc = "UIEvent for Toast shown when stylus stopped charging")
    STYLUS_STOPPED_CHARGING(1303),
    @UiEvent(doc = "UIEvent for bluetooth stylus connected") BLUETOOTH_STYLUS_CONNECTED(1304),
    @UiEvent(doc = "UIEvent for bluetooth stylus disconnected") BLUETOOTH_STYLUS_DISCONNECTED(1305);
    @UiEvent(doc = "UIEvent for bluetooth stylus disconnected") BLUETOOTH_STYLUS_DISCONNECTED(1305),
    @UiEvent(doc = "UIEvent for start of a USI session via battery presence")
    USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED(1306),
    @UiEvent(doc = "UIEvent for end of a USI session via battery absence")
    USI_STYLUS_BATTERY_PRESENCE_REMOVED(1307);

    override fun getId() = _id
}
+44 −0
Original line number Diff line number Diff line
@@ -96,6 +96,9 @@ class StylusManagerTest : SysuiTestCase() {
        whenever(stylusDevice.bluetoothAddress).thenReturn(null)
        whenever(btStylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)

        whenever(stylusDevice.batteryState).thenReturn(batteryState)
        whenever(batteryState.capacity).thenReturn(0.5f)

        whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
        whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
        whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
@@ -493,6 +496,47 @@ class StylusManagerTest : SysuiTestCase() {
        verify(stylusCallback, times(1)).onStylusFirstUsed()
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_notInUsiSession_logsSessionStart() {
        whenever(batteryState.isPresent).thenReturn(true)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, times(1))
            .log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED)
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_inUsiSession_doesNotLogSessionStart() {
        whenever(batteryState.isPresent).thenReturn(true)
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
        clearInvocations(uiEventLogger)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, never()).log(any())
    }

    @Test
    fun onBatteryStateChanged_batteryAbsent_notInUsiSession_doesNotLogSessionEnd() {
        whenever(batteryState.isPresent).thenReturn(false)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, never()).log(any())
    }

    @Test
    fun onBatteryStateChanged_batteryAbsent_inUsiSession_logSessionEnd() {
        whenever(batteryState.isPresent).thenReturn(true)
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
        whenever(batteryState.isPresent).thenReturn(false)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, times(1)).log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED)
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_stylusUsed_doesNotUpdateEverUsedFlag() {
        whenever(inputManager.isStylusEverUsed(mContext)).thenReturn(true)