Merge "Listen to charging state metadata in StylusManager" into tm-qpr-dev am: 9e7dc1926a
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20579719 Change-Id: I5fd749408f012b2f75eba90a18f6ca4c2b8dd72a Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -35,6 +35,8 @@ import android.app.job.JobScheduler;
|
||||
import android.app.role.RoleManager;
|
||||
import android.app.smartspace.SmartspaceManager;
|
||||
import android.app.trust.TrustManager;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -609,4 +611,16 @@ public class FrameworkServicesModule {
|
||||
static CameraManager provideCameraManager(Context context) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
|
||||
package com.android.systemui.stylus
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.hardware.input.InputManager
|
||||
import android.os.Handler
|
||||
import android.util.ArrayMap
|
||||
import android.util.Log
|
||||
import android.view.InputDevice
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -34,10 +38,14 @@ class StylusManager
|
||||
@Inject
|
||||
constructor(
|
||||
private val inputManager: InputManager,
|
||||
private val bluetoothAdapter: BluetoothAdapter,
|
||||
@Background private val handler: Handler,
|
||||
) : InputManager.InputDeviceListener {
|
||||
@Background private val executor: Executor,
|
||||
) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
|
||||
|
||||
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()
|
||||
|
||||
@@ -60,6 +68,14 @@ constructor(
|
||||
stylusCallbacks.remove(callback)
|
||||
}
|
||||
|
||||
fun registerBatteryCallback(callback: StylusBatteryCallback) {
|
||||
stylusBatteryCallbacks.add(callback)
|
||||
}
|
||||
|
||||
fun unregisterBatteryCallback(callback: StylusBatteryCallback) {
|
||||
stylusBatteryCallbacks.remove(callback)
|
||||
}
|
||||
|
||||
override fun onInputDeviceAdded(deviceId: Int) {
|
||||
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
|
||||
if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
|
||||
@@ -70,6 +86,7 @@ constructor(
|
||||
executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) }
|
||||
|
||||
if (btAddress != null) {
|
||||
onStylusBluetoothConnected(btAddress)
|
||||
executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) }
|
||||
}
|
||||
}
|
||||
@@ -84,10 +101,12 @@ constructor(
|
||||
inputDeviceAddressMap[deviceId] = currAddress
|
||||
|
||||
if (prevAddress == null && currAddress != null) {
|
||||
onStylusBluetoothConnected(currAddress)
|
||||
executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, currAddress) }
|
||||
}
|
||||
|
||||
if (prevAddress != null && currAddress == null) {
|
||||
onStylusBluetoothDisconnected(prevAddress)
|
||||
executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, prevAddress) }
|
||||
}
|
||||
}
|
||||
@@ -98,15 +117,55 @@ constructor(
|
||||
val btAddress: String? = inputDeviceAddressMap[deviceId]
|
||||
inputDeviceAddressMap.remove(deviceId)
|
||||
if (btAddress != null) {
|
||||
onStylusBluetoothDisconnected(btAddress)
|
||||
executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, btAddress) }
|
||||
}
|
||||
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) {
|
||||
stylusCallbacks.forEach(run)
|
||||
}
|
||||
|
||||
private fun executeStylusBatteryCallbacks(run: (cb: StylusBatteryCallback) -> Unit) {
|
||||
stylusBatteryCallbacks.forEach(run)
|
||||
}
|
||||
|
||||
private fun addExistingStylusToMap() {
|
||||
for (deviceId: Int in inputManager.inputDeviceIds) {
|
||||
val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue
|
||||
@@ -125,6 +184,15 @@ constructor(
|
||||
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 {
|
||||
private val TAG = StylusManager::class.simpleName.orEmpty()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.systemui.stylus
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.hardware.input.InputManager
|
||||
import android.os.Handler
|
||||
import android.testing.AndroidTestingRunner
|
||||
@@ -23,12 +25,17 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import java.util.concurrent.Executor
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
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
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -43,12 +50,20 @@ class StylusManagerTest : SysuiTestCase() {
|
||||
|
||||
@Mock lateinit var otherDevice: InputDevice
|
||||
|
||||
@Mock lateinit var bluetoothAdapter: BluetoothAdapter
|
||||
|
||||
@Mock lateinit var bluetoothDevice: BluetoothDevice
|
||||
|
||||
@Mock lateinit var handler: Handler
|
||||
|
||||
@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 stylusManager: StylusManager
|
||||
|
||||
@Before
|
||||
@@ -60,10 +75,12 @@ class StylusManagerTest : SysuiTestCase() {
|
||||
true
|
||||
}
|
||||
|
||||
stylusManager = StylusManager(inputManager, handler)
|
||||
stylusManager = StylusManager(inputManager, bluetoothAdapter, handler, EXECUTOR)
|
||||
|
||||
stylusManager.registerCallback(stylusCallback)
|
||||
|
||||
stylusManager.registerBatteryCallback(stylusBatteryCallback)
|
||||
|
||||
whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
|
||||
whenever(stylusDevice.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(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
|
||||
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))
|
||||
|
||||
whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
|
||||
whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startListener_registersInputDeviceListener() {
|
||||
stylusManager.startListener()
|
||||
|
||||
verify(inputManager, times(1)).registerInputDeviceListener(stylusManager, handler)
|
||||
verify(inputManager, times(1)).registerInputDeviceListener(any(), any())
|
||||
}
|
||||
|
||||
@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 {
|
||||
private val EXECUTOR = Executor { r -> r.run() }
|
||||
|
||||
private const val OTHER_DEVICE_ID = 0
|
||||
private const val STYLUS_DEVICE_ID = 1
|
||||
private const val BT_STYLUS_DEVICE_ID = 2
|
||||
|
||||
Reference in New Issue
Block a user