UIEvent for tracking USI battery presence.
Bug: 267816496 Test: StylusManagerTest Change-Id: I69edb7fc04f4af5ca1768aaf0001f62885bc71d3
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user