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

Commit ab3b2e35 authored by Vania Januar's avatar Vania Januar Committed by Android (Google) Code Review
Browse files

Merge "UIEvent for tracking USI battery presence."

parents b6f489fc 99943d7d
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)