Merge "Listen to charging state metadata in StylusManager" into tm-qpr-dev

This commit is contained in:
Vania Januar
2022-12-11 12:33:06 +00:00
committed by Android (Google) Code Review
3 changed files with 203 additions and 4 deletions

View File

@@ -35,6 +35,8 @@ import android.app.job.JobScheduler;
import android.app.role.RoleManager; import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager; import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager; import android.app.trust.TrustManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@@ -609,4 +611,16 @@ public class FrameworkServicesModule {
static CameraManager provideCameraManager(Context context) { static CameraManager provideCameraManager(Context context) {
return context.getSystemService(CameraManager.class); return context.getSystemService(CameraManager.class);
} }
@Provides
@Singleton
static BluetoothManager provideBluetoothManager(Context context) {
return context.getSystemService(BluetoothManager.class);
}
@Provides
@Singleton
static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
return bluetoothManager.getAdapter();
}
} }

View File

@@ -16,13 +16,17 @@
package com.android.systemui.stylus package com.android.systemui.stylus
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.hardware.input.InputManager import android.hardware.input.InputManager
import android.os.Handler import android.os.Handler
import android.util.ArrayMap import android.util.ArrayMap
import android.util.Log
import android.view.InputDevice import android.view.InputDevice
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
import javax.inject.Inject import javax.inject.Inject
/** /**
@@ -34,10 +38,14 @@ class StylusManager
@Inject @Inject
constructor( constructor(
private val inputManager: InputManager, private val inputManager: InputManager,
private val bluetoothAdapter: BluetoothAdapter,
@Background private val handler: Handler, @Background private val handler: Handler,
) : InputManager.InputDeviceListener { @Background private val executor: Executor,
) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList() private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList()
private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> =
CopyOnWriteArrayList()
// This map should only be accessed on the handler // This map should only be accessed on the handler
private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap() private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
@@ -60,6 +68,14 @@ constructor(
stylusCallbacks.remove(callback) stylusCallbacks.remove(callback)
} }
fun registerBatteryCallback(callback: StylusBatteryCallback) {
stylusBatteryCallbacks.add(callback)
}
fun unregisterBatteryCallback(callback: StylusBatteryCallback) {
stylusBatteryCallbacks.remove(callback)
}
override fun onInputDeviceAdded(deviceId: Int) { override fun onInputDeviceAdded(deviceId: Int) {
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
@@ -70,6 +86,7 @@ constructor(
executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) } executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) }
if (btAddress != null) { if (btAddress != null) {
onStylusBluetoothConnected(btAddress)
executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) } executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) }
} }
} }
@@ -84,10 +101,12 @@ constructor(
inputDeviceAddressMap[deviceId] = currAddress inputDeviceAddressMap[deviceId] = currAddress
if (prevAddress == null && currAddress != null) { if (prevAddress == null && currAddress != null) {
onStylusBluetoothConnected(currAddress)
executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, currAddress) } executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, currAddress) }
} }
if (prevAddress != null && currAddress == null) { if (prevAddress != null && currAddress == null) {
onStylusBluetoothDisconnected(prevAddress)
executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, prevAddress) } executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, prevAddress) }
} }
} }
@@ -98,15 +117,55 @@ constructor(
val btAddress: String? = inputDeviceAddressMap[deviceId] val btAddress: String? = inputDeviceAddressMap[deviceId]
inputDeviceAddressMap.remove(deviceId) inputDeviceAddressMap.remove(deviceId)
if (btAddress != null) { if (btAddress != null) {
onStylusBluetoothDisconnected(btAddress)
executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, btAddress) } executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, btAddress) }
} }
executeStylusCallbacks { cb -> cb.onStylusRemoved(deviceId) } executeStylusCallbacks { cb -> cb.onStylusRemoved(deviceId) }
} }
override fun onMetadataChanged(device: BluetoothDevice, key: Int, value: ByteArray?) {
handler.post executeMetadataChanged@{
if (key != BluetoothDevice.METADATA_MAIN_CHARGING || value == null)
return@executeMetadataChanged
val inputDeviceId: Int =
inputDeviceAddressMap.filterValues { it == device.address }.keys.firstOrNull()
?: return@executeMetadataChanged
val isCharging = String(value) == "true"
executeStylusBatteryCallbacks { cb ->
cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging)
}
}
}
private fun onStylusBluetoothConnected(btAddress: String) {
val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
} catch (e: IllegalArgumentException) {
Log.e(TAG, "$e: Metadata listener already registered for device. Ignoring.")
}
}
private fun onStylusBluetoothDisconnected(btAddress: String) {
val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.removeOnMetadataChangedListener(device, this)
} catch (e: IllegalArgumentException) {
Log.e(TAG, "$e: Metadata listener does not exist for device. Ignoring.")
}
}
private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) { private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) {
stylusCallbacks.forEach(run) stylusCallbacks.forEach(run)
} }
private fun executeStylusBatteryCallbacks(run: (cb: StylusBatteryCallback) -> Unit) {
stylusBatteryCallbacks.forEach(run)
}
private fun addExistingStylusToMap() { private fun addExistingStylusToMap() {
for (deviceId: Int in inputManager.inputDeviceIds) { for (deviceId: Int in inputManager.inputDeviceIds) {
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue
@@ -125,6 +184,15 @@ constructor(
fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {} fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {}
} }
/** Callback interface to receive stylus battery events from the StylusManager. */
interface StylusBatteryCallback {
fun onStylusBluetoothChargingStateChanged(
inputDeviceId: Int,
btDevice: BluetoothDevice,
isCharging: Boolean
) {}
}
companion object { companion object {
private val TAG = StylusManager::class.simpleName.orEmpty() private val TAG = StylusManager::class.simpleName.orEmpty()
} }

View File

@@ -15,6 +15,8 @@
*/ */
package com.android.systemui.stylus package com.android.systemui.stylus
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.hardware.input.InputManager import android.hardware.input.InputManager
import android.os.Handler import android.os.Handler
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
@@ -23,12 +25,17 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import java.util.concurrent.Executor
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.* import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@@ -43,12 +50,20 @@ class StylusManagerTest : SysuiTestCase() {
@Mock lateinit var otherDevice: InputDevice @Mock lateinit var otherDevice: InputDevice
@Mock lateinit var bluetoothAdapter: BluetoothAdapter
@Mock lateinit var bluetoothDevice: BluetoothDevice
@Mock lateinit var handler: Handler @Mock lateinit var handler: Handler
@Mock lateinit var stylusCallback: StylusManager.StylusCallback @Mock lateinit var stylusCallback: StylusManager.StylusCallback
@Mock lateinit var otherStylusCallback: StylusManager.StylusCallback @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback
@Mock lateinit var stylusBatteryCallback: StylusManager.StylusBatteryCallback
@Mock lateinit var otherStylusBatteryCallback: StylusManager.StylusBatteryCallback
private lateinit var stylusManager: StylusManager private lateinit var stylusManager: StylusManager
@Before @Before
@@ -60,10 +75,12 @@ class StylusManagerTest : SysuiTestCase() {
true true
} }
stylusManager = StylusManager(inputManager, handler) stylusManager = StylusManager(inputManager, bluetoothAdapter, handler, EXECUTOR)
stylusManager.registerCallback(stylusCallback) stylusManager.registerCallback(stylusCallback)
stylusManager.registerBatteryCallback(stylusBatteryCallback)
whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false) whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
@@ -75,13 +92,16 @@ class StylusManagerTest : SysuiTestCase() {
whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice) whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice) whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID)) whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))
whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)
} }
@Test @Test
fun startListener_registersInputDeviceListener() { fun startListener_registersInputDeviceListener() {
stylusManager.startListener() stylusManager.startListener()
verify(inputManager, times(1)).registerInputDeviceListener(stylusManager, handler) verify(inputManager, times(1)).registerInputDeviceListener(any(), any())
} }
@Test @Test
@@ -211,7 +231,104 @@ class StylusManagerTest : SysuiTestCase() {
} }
} }
@Test
fun onStylusBluetoothConnected_registersMetadataListener() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
verify(bluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any())
}
@Test
fun onStylusBluetoothConnected_noBluetoothDevice_doesNotRegisterMetadataListener() {
whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(null)
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
verify(bluetoothAdapter, never()).addOnMetadataChangedListener(any(), any(), any())
}
@Test
fun onStylusBluetoothDisconnected_unregistersMetadataListener() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
verify(bluetoothAdapter, times(1)).removeOnMetadataChangedListener(any(), any())
}
@Test
fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
stylusManager.registerBatteryCallback(otherStylusBatteryCallback)
stylusManager.onMetadataChanged(
bluetoothDevice,
BluetoothDevice.METADATA_MAIN_CHARGING,
"true".toByteArray()
)
verify(stylusBatteryCallback, times(1))
.onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
verify(otherStylusBatteryCallback, times(1))
.onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
}
@Test
fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
stylusManager.onMetadataChanged(
bluetoothDevice,
BluetoothDevice.METADATA_MAIN_CHARGING,
"true".toByteArray()
)
verify(stylusBatteryCallback, times(1))
.onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
}
@Test
fun onMetadataChanged_chargingStateFalse_executesBatteryCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
stylusManager.onMetadataChanged(
bluetoothDevice,
BluetoothDevice.METADATA_MAIN_CHARGING,
"false".toByteArray()
)
verify(stylusBatteryCallback, times(1))
.onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, false)
}
@Test
fun onMetadataChanged_chargingStateNoDevice_doesNotExecuteBatteryCallbacks() {
stylusManager.onMetadataChanged(
bluetoothDevice,
BluetoothDevice.METADATA_MAIN_CHARGING,
"true".toByteArray()
)
verifyNoMoreInteractions(stylusBatteryCallback)
}
@Test
fun onMetadataChanged_notChargingState_doesNotExecuteBatteryCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
stylusManager.onMetadataChanged(
bluetoothDevice,
BluetoothDevice.METADATA_DEVICE_TYPE,
"true".toByteArray()
)
verify(stylusBatteryCallback, never())
.onStylusBluetoothChargingStateChanged(any(), any(), any())
}
companion object { companion object {
private val EXECUTOR = Executor { r -> r.run() }
private const val OTHER_DEVICE_ID = 0 private const val OTHER_DEVICE_ID = 0
private const val STYLUS_DEVICE_ID = 1 private const val STYLUS_DEVICE_ID = 1
private const val BT_STYLUS_DEVICE_ID = 2 private const val BT_STYLUS_DEVICE_ID = 2