Merge "Listen to charging state metadata in StylusManager" into tm-qpr-dev
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user