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

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

Merge "Log instanceId with stylus UIEvents."

parents 785758b1 19bbf038
Loading
Loading
Loading
Loading
+52 −15
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ import android.os.Handler
import android.util.ArrayMap
import android.util.Log
import android.view.InputDevice
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -33,7 +36,6 @@ 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
@@ -61,12 +63,17 @@ constructor(
    private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList()
    private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> =
        CopyOnWriteArrayList()

    // This map should only be accessed on the handler
    private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
    private val inputDeviceBtSessionIdMap: MutableMap<Int, InstanceId> = ArrayMap()

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

    @VisibleForTesting var instanceIdSequence = InstanceIdSequence(1 shl 13)

    /**
     * Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot
@@ -120,7 +127,7 @@ constructor(

        if (btAddress != null) {
            onStylusUsed()
            onStylusBluetoothConnected(btAddress)
            onStylusBluetoothConnected(deviceId, btAddress)
            executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) }
        }
    }
@@ -136,12 +143,12 @@ constructor(
        inputDeviceAddressMap[deviceId] = currAddress

        if (prevAddress == null && currAddress != null) {
            onStylusBluetoothConnected(currAddress)
            onStylusBluetoothConnected(deviceId, currAddress)
            executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, currAddress) }
        }

        if (prevAddress != null && currAddress == null) {
            onStylusBluetoothDisconnected(prevAddress)
            onStylusBluetoothDisconnected(deviceId, prevAddress)
            executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, prevAddress) }
        }
    }
@@ -155,7 +162,7 @@ constructor(
        val btAddress: String? = inputDeviceAddressMap[deviceId]
        inputDeviceAddressMap.remove(deviceId)
        if (btAddress != null) {
            onStylusBluetoothDisconnected(btAddress)
            onStylusBluetoothDisconnected(deviceId, btAddress)
            executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, btAddress) }
        }
        executeStylusCallbacks { cb -> cb.onStylusRemoved(deviceId) }
@@ -208,8 +215,8 @@ constructor(
        }
    }

    private fun onStylusBluetoothConnected(btAddress: String) {
        uiEventLogger.log(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED)
    private fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
        trackAndLogBluetoothSession(deviceId, true)
        val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
        try {
            bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
@@ -218,8 +225,8 @@ constructor(
        }
    }

    private fun onStylusBluetoothDisconnected(btAddress: String) {
        uiEventLogger.log(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED)
    private fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {
        trackAndLogBluetoothSession(deviceId, false)
        val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
        try {
            bluetoothAdapter.removeOnMetadataChangedListener(device, this)
@@ -251,21 +258,51 @@ constructor(
    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 (batteryStateValid && usiSessionId == null) {
            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) {
            usiSessionId = instanceIdSequence.newInstanceId()
            uiEventLogger.logWithInstanceId(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
                0,
                null,
                usiSessionId
            )
        } else if (!batteryStateValid && usiSessionId != null) {
            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)
            uiEventLogger.logWithInstanceId(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
                0,
                null,
                usiSessionId
            )
            usiSessionId = null
        }
    }

    private fun trackAndLogBluetoothSession(deviceId: Int, bluetoothConnected: Boolean) {
        if (bluetoothConnected) {
            inputDeviceBtSessionIdMap[deviceId] = instanceIdSequence.newInstanceId()
            uiEventLogger.logWithInstanceId(
                StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED,
                0,
                null,
                inputDeviceBtSessionIdMap[deviceId]
            )
        } else {
            uiEventLogger.logWithInstanceId(
                StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED,
                0,
                null,
                inputDeviceBtSessionIdMap[deviceId]
            )
            inputDeviceBtSessionIdMap.remove(deviceId)
        }
    }

+20 −1
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
@@ -62,6 +64,9 @@ constructor(
    private var batteryCapacity = 1.0f
    private var suppressed = false
    private var inputDeviceId: Int? = null
    private var instanceId: InstanceId? = null

    @VisibleForTesting var instanceIdSequence = InstanceIdSequence(1 shl 13)

    fun init() {
        val filter =
@@ -126,6 +131,7 @@ constructor(
    }

    private fun hideNotification() {
        instanceId = null
        notificationManager.cancel(USI_NOTIFICATION_ID)
    }

@@ -204,15 +210,28 @@ constructor(
            }
        }

    /**
     * Logs a stylus USI battery event with instance ID and battery level. The instance ID
     * represents the notification instance, and is reset when a notification is cancelled.
     */
    private fun logUiEvent(metricId: StylusUiEvent) {
        uiEventLogger.logWithPosition(
        uiEventLogger.logWithInstanceIdAndPosition(
            metricId,
            ActivityManager.getCurrentUser(),
            context.packageName,
            getInstanceId(),
            (batteryCapacity * 100.0).toInt()
        )
    }

    @VisibleForTesting
    fun getInstanceId(): InstanceId? {
        if (instanceId == null) {
            instanceId = instanceId ?: instanceIdSequence.newInstanceId()
        }
        return instanceId
    }

    companion object {
        // Low battery threshold matches CrOS, see:
        // https://source.chromium.org/chromium/chromium/src/+/main:ash/system/power/peripheral_battery_notifier.cc;l=41
+42 −13
Original line number Diff line number Diff line
@@ -29,7 +29,9 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.ExtendedMockito.times
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -59,21 +61,16 @@ class StylusManagerTest : SysuiTestCase() {
    @Mock lateinit var bluetoothAdapter: BluetoothAdapter
    @Mock lateinit var bluetoothDevice: BluetoothDevice
    @Mock lateinit var handler: Handler

    @Mock lateinit var featureFlags: FeatureFlags

    @Mock lateinit var uiEventLogger: UiEventLogger

    @Mock lateinit var stylusCallback: StylusManager.StylusCallback

    @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback

    @Mock lateinit var stylusBatteryCallback: StylusManager.StylusBatteryCallback

    @Mock lateinit var otherStylusBatteryCallback: StylusManager.StylusBatteryCallback

    private lateinit var mockitoSession: StaticMockitoSession
    private lateinit var stylusManager: StylusManager
    private val instanceIdSequenceFake = InstanceIdSequenceFake(10)

    @Before
    fun setUp() {
@@ -100,6 +97,8 @@ class StylusManagerTest : SysuiTestCase() {
                uiEventLogger
            )

        stylusManager.instanceIdSequence = instanceIdSequenceFake

        whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
        whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
        whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
@@ -403,7 +402,13 @@ class StylusManagerTest : SysuiTestCase() {
    fun onStylusBluetoothConnected_logsEvent() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        verify(uiEventLogger, times(1)).log(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED)
        verify(uiEventLogger, times(1))
            .logWithInstanceId(
                StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED,
                0,
                null,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
            )
    }

    @Test
@@ -416,12 +421,16 @@ class StylusManagerTest : SysuiTestCase() {
    }

    @Test
    fun onStylusBluetoothDisconnected_logsEvent() {
    fun onStylusBluetoothDisconnected_logsEventInSameSession() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
        val instanceId = InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)

        stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)

        verify(uiEventLogger, times(1)).log(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED)
        verify(uiEventLogger, times(1))
            .logWithInstanceId(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED, 0, null, instanceId)
        verify(uiEventLogger, times(1))
            .logWithInstanceId(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED, 0, null, instanceId)
    }

    @Test
@@ -519,7 +528,12 @@ class StylusManagerTest : SysuiTestCase() {
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, times(1))
            .log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED)
            .logWithInstanceId(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
                0,
                null,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
            )
    }

    @Test
@@ -530,7 +544,7 @@ class StylusManagerTest : SysuiTestCase() {

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

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

    @Test
@@ -539,18 +553,33 @@ class StylusManagerTest : SysuiTestCase() {

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

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

    @Test
    fun onBatteryStateChanged_batteryAbsent_inUsiSession_logSessionEnd() {
        whenever(batteryState.isPresent).thenReturn(true)
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
        val instanceId = InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
        whenever(batteryState.isPresent).thenReturn(false)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, times(1)).log(StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED)
        verify(uiEventLogger, times(1))
            .logWithInstanceId(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
                0,
                null,
                instanceId
            )

        verify(uiEventLogger, times(1))
            .logWithInstanceId(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
                0,
                null,
                instanceId
            )
    }

    @Test
+12 −4
Original line number Diff line number Diff line
@@ -28,7 +28,9 @@ import android.testing.AndroidTestingRunner
import android.view.InputDevice
import androidx.core.app.NotificationManagerCompat
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
@@ -64,13 +66,14 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
    @Mock lateinit var btStylusDevice: InputDevice

    @Mock lateinit var uiEventLogger: UiEventLogger

    @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification>

    private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
    private lateinit var broadcastReceiver: BroadcastReceiver
    private lateinit var contextSpy: Context

    private val instanceIdSequenceFake = InstanceIdSequenceFake(10)

    private val uid = ActivityManager.getCurrentUser()

    @Before
@@ -92,6 +95,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() {

        stylusUsiPowerUi =
            StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler, uiEventLogger)
        stylusUsiPowerUi.instanceIdSequence = instanceIdSequenceFake

        broadcastReceiver = stylusUsiPowerUi.receiver
    }

@@ -212,10 +217,11 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
        stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))

        verify(uiEventLogger, times(1))
            .logWithPosition(
            .logWithInstanceIdAndPosition(
                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN,
                uid,
                contextSpy.packageName,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
                10
            )
    }
@@ -250,10 +256,11 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
        broadcastReceiver.onReceive(contextSpy, intent)

        verify(uiEventLogger, times(1))
            .logWithPosition(
            .logWithInstanceIdAndPosition(
                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED,
                uid,
                contextSpy.packageName,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
                100
            )
    }
@@ -264,10 +271,11 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
        broadcastReceiver.onReceive(contextSpy, intent)

        verify(uiEventLogger, times(1))
            .logWithPosition(
            .logWithInstanceIdAndPosition(
                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED,
                uid,
                contextSpy.packageName,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
                100
            )
    }