Loading packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +52 −15 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -120,7 +127,7 @@ constructor( if (btAddress != null) { onStylusUsed() onStylusBluetoothConnected(btAddress) onStylusBluetoothConnected(deviceId, btAddress) executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) } } } Loading @@ -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) } } } Loading @@ -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) } Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -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) } } Loading packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +20 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 = Loading Loading @@ -126,6 +131,7 @@ constructor( } private fun hideNotification() { instanceId = null notificationManager.cancel(USI_NOTIFICATION_ID) } Loading Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt +42 −13 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() { Loading @@ -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) Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -530,7 +544,7 @@ class StylusManagerTest : SysuiTestCase() { stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) verify(uiEventLogger, never()).log(any()) verifyZeroInteractions(uiEventLogger) } @Test Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +12 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -92,6 +95,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() { stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler, uiEventLogger) stylusUsiPowerUi.instanceIdSequence = instanceIdSequenceFake broadcastReceiver = stylusUsiPowerUi.receiver } Loading Loading @@ -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 ) } Loading Loading @@ -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 ) } Loading @@ -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 ) } Loading Loading
packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +52 −15 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -120,7 +127,7 @@ constructor( if (btAddress != null) { onStylusUsed() onStylusBluetoothConnected(btAddress) onStylusBluetoothConnected(deviceId, btAddress) executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) } } } Loading @@ -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) } } } Loading @@ -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) } Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -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) } } Loading
packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +20 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 = Loading Loading @@ -126,6 +131,7 @@ constructor( } private fun hideNotification() { instanceId = null notificationManager.cancel(USI_NOTIFICATION_ID) } Loading Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt +42 −13 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() { Loading @@ -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) Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -530,7 +544,7 @@ class StylusManagerTest : SysuiTestCase() { stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) verify(uiEventLogger, never()).log(any()) verifyZeroInteractions(uiEventLogger) } @Test Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +12 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -92,6 +95,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() { stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler, uiEventLogger) stylusUsiPowerUi.instanceIdSequence = instanceIdSequenceFake broadcastReceiver = stylusUsiPowerUi.receiver } Loading Loading @@ -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 ) } Loading Loading @@ -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 ) } Loading @@ -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 ) } Loading