Add BluetoothX openGattServer
Bug: 267203732
Test: build
Change-Id: Iafa384227772c67d6b7b1540cdaa992000caf9a8
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/bluetoothx/BtxFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/bluetoothx/BtxFragment.kt
index b10253f..57d2d8f 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/bluetoothx/BtxFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/bluetoothx/BtxFragment.kt
@@ -17,6 +17,11 @@
package androidx.bluetooth.integration.testapp.ui.bluetoothx
import android.annotation.SuppressLint
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothGattCharacteristic
+import android.bluetooth.BluetoothGattDescriptor
+import android.bluetooth.BluetoothGattServerCallback
+import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
@@ -86,13 +91,19 @@
if (isChecked) startAdvertise()
else advertiseJob?.cancel()
}
+
+ binding.switchGattServer.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) openGattServer()
+ else gattServerJob?.cancel()
+ }
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
- advertiseJob?.cancel()
scanJob?.cancel()
+ advertiseJob?.cancel()
+ gattServerJob?.cancel()
}
private val scanScope = CoroutineScope(Dispatchers.Main + Job())
@@ -216,4 +227,348 @@
}
}
}
+
+ private val gattServerScope = CoroutineScope(Dispatchers.Main + Job())
+ private var gattServerJob: Job? = null
+
+ sealed interface GattServerCallback {
+ data class OnConnectionStateChange(
+ val device: BluetoothDevice?,
+ val status: Int,
+ val newState: Int
+ ) : GattServerCallback
+
+ data class OnServiceAdded(
+ val status: Int,
+ val service: BluetoothGattService?
+ ) : GattServerCallback
+
+ data class OnCharacteristicReadRequest(
+ val device: BluetoothDevice?,
+ val requestId: Int,
+ val offset: Int,
+ val characteristic: BluetoothGattCharacteristic?
+ ) : GattServerCallback
+
+ data class OnCharacteristicWriteRequest(
+ val device: BluetoothDevice?,
+ val requestId: Int,
+ val characteristic: BluetoothGattCharacteristic?,
+ val preparedWrite: Boolean,
+ val responseNeeded: Boolean,
+ val offset: Int,
+ val value: ByteArray?
+ ) : GattServerCallback
+
+ data class OnDescriptorReadRequest(
+ val device: BluetoothDevice?,
+ val requestId: Int,
+ val offset: Int,
+ val descriptor: BluetoothGattDescriptor?
+ ) : GattServerCallback
+
+ data class OnDescriptorWriteRequest(
+ val device: BluetoothDevice?,
+ val requestId: Int,
+ val descriptor: BluetoothGattDescriptor?,
+ val preparedWrite: Boolean,
+ val responseNeeded: Boolean,
+ val offset: Int,
+ val value: ByteArray?
+ ) : GattServerCallback
+
+ data class OnExecuteWrite(
+ val device: BluetoothDevice?,
+ val requestId: Int,
+ val execute: Boolean
+ ) : GattServerCallback
+
+ data class OnNotificationSent(
+ val device: BluetoothDevice?,
+ val status: Int
+ ) : GattServerCallback
+
+ data class OnMtuChanged(
+ val device: BluetoothDevice?,
+ val mtu: Int
+ ) : GattServerCallback
+
+ data class OnPhyUpdate(
+ val device: BluetoothDevice?,
+ val txPhy: Int,
+ val rxPhy: Int,
+ val status: Int
+ ) : GattServerCallback
+
+ data class OnPhyRead(
+ val device: BluetoothDevice?,
+ val txPhy: Int,
+ val rxPhy: Int,
+ val status: Int
+ ) : GattServerCallback
+ }
+
+ @SuppressLint("MissingPermission")
+ fun gattServer(): Flow<GattServerCallback> =
+ callbackFlow {
+ val callback = object : BluetoothGattServerCallback() {
+ override fun onConnectionStateChange(
+ device: BluetoothDevice?,
+ status: Int,
+ newState: Int
+ ) {
+ trySend(
+ GattServerCallback.OnConnectionStateChange(device, status, newState)
+ )
+ }
+
+ override fun onServiceAdded(status: Int, service: BluetoothGattService?) {
+ trySend(
+ GattServerCallback.OnServiceAdded(status, service)
+ )
+ }
+
+ override fun onCharacteristicReadRequest(
+ device: BluetoothDevice?,
+ requestId: Int,
+ offset: Int,
+ characteristic: BluetoothGattCharacteristic?
+ ) {
+ trySend(
+ GattServerCallback.OnCharacteristicReadRequest(
+ device,
+ requestId,
+ offset,
+ characteristic
+ )
+ )
+ }
+
+ override fun onCharacteristicWriteRequest(
+ device: BluetoothDevice?,
+ requestId: Int,
+ characteristic: BluetoothGattCharacteristic?,
+ preparedWrite: Boolean,
+ responseNeeded: Boolean,
+ offset: Int,
+ value: ByteArray?
+ ) {
+ trySend(
+ GattServerCallback.OnCharacteristicWriteRequest(
+ device,
+ requestId,
+ characteristic,
+ preparedWrite,
+ responseNeeded,
+ offset,
+ value
+ )
+ )
+ }
+
+ override fun onDescriptorReadRequest(
+ device: BluetoothDevice?,
+ requestId: Int,
+ offset: Int,
+ descriptor: BluetoothGattDescriptor?
+ ) {
+ trySend(
+ GattServerCallback.OnDescriptorReadRequest(
+ device,
+ requestId,
+ offset,
+ descriptor
+ )
+ )
+ }
+
+ override fun onDescriptorWriteRequest(
+ device: BluetoothDevice?,
+ requestId: Int,
+ descriptor: BluetoothGattDescriptor?,
+ preparedWrite: Boolean,
+ responseNeeded: Boolean,
+ offset: Int,
+ value: ByteArray?
+ ) {
+ trySend(
+ GattServerCallback.OnDescriptorWriteRequest(
+ device,
+ requestId,
+ descriptor,
+ preparedWrite,
+ responseNeeded,
+ offset,
+ value
+ )
+ )
+ }
+
+ override fun onExecuteWrite(
+ device: BluetoothDevice?,
+ requestId: Int,
+ execute: Boolean
+ ) {
+ trySend(
+ GattServerCallback.OnExecuteWrite(device, requestId, execute)
+ )
+ }
+
+ override fun onNotificationSent(device: BluetoothDevice?, status: Int) {
+ trySend(
+ GattServerCallback.OnNotificationSent(device, status)
+ )
+ }
+
+ override fun onMtuChanged(device: BluetoothDevice?, mtu: Int) {
+ trySend(
+ GattServerCallback.OnMtuChanged(device, mtu)
+ )
+ }
+
+ override fun onPhyUpdate(
+ device: BluetoothDevice?,
+ txPhy: Int,
+ rxPhy: Int,
+ status: Int
+ ) {
+ trySend(
+ GattServerCallback.OnPhyUpdate(device, txPhy, rxPhy, status)
+ )
+ }
+
+ override fun onPhyRead(
+ device: BluetoothDevice?,
+ txPhy: Int,
+ rxPhy: Int,
+ status: Int
+ ) {
+ trySend(
+ GattServerCallback.OnPhyRead(device, txPhy, rxPhy, status)
+ )
+ }
+ }
+
+ val bluetoothManager =
+ context?.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
+
+ val bluetoothGattServer = bluetoothManager?.openGattServer(requireContext(), callback)
+
+ awaitClose {
+ Log.d(TAG, "awaitClose() called")
+ bluetoothGattServer?.close()
+ }
+ }
+
+ // Permissions are handled by MainActivity requestBluetoothPermissions
+ @SuppressLint("MissingPermission")
+ private fun openGattServer() {
+ Log.d(TAG, "openGattServer() called")
+
+ gattServerJob = gattServerScope.launch {
+ gattServer().collect { gattServerCallback ->
+ when (gattServerCallback) {
+ is GattServerCallback.OnCharacteristicReadRequest -> {
+ val onCharacteristicReadRequest:
+ GattServerCallback.OnCharacteristicReadRequest = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onCharacteristicReadRequest = $onCharacteristicReadRequest"
+ )
+ }
+ is GattServerCallback.OnCharacteristicWriteRequest -> {
+ val onCharacteristicWriteRequest:
+ GattServerCallback.OnCharacteristicWriteRequest = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onCharacteristicWriteRequest = $onCharacteristicWriteRequest"
+ )
+ }
+ is GattServerCallback.OnConnectionStateChange -> {
+ val onConnectionStateChange:
+ GattServerCallback.OnConnectionStateChange = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onConnectionStateChange = $onConnectionStateChange"
+ )
+ }
+ is GattServerCallback.OnDescriptorReadRequest -> {
+ val onDescriptorReadRequest:
+ GattServerCallback.OnDescriptorReadRequest = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onDescriptorReadRequest = $onDescriptorReadRequest"
+ )
+ }
+ is GattServerCallback.OnDescriptorWriteRequest -> {
+ val onDescriptorWriteRequest:
+ GattServerCallback.OnDescriptorWriteRequest = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onDescriptorWriteRequest = $onDescriptorWriteRequest"
+ )
+ }
+ is GattServerCallback.OnExecuteWrite -> {
+ val onExecuteWrite:
+ GattServerCallback.OnExecuteWrite = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onExecuteWrite = $onExecuteWrite"
+ )
+ }
+ is GattServerCallback.OnMtuChanged -> {
+ val onMtuChanged:
+ GattServerCallback.OnMtuChanged = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onMtuChanged = $onMtuChanged"
+ )
+ }
+ is GattServerCallback.OnNotificationSent -> {
+ val onNotificationSent:
+ GattServerCallback.OnNotificationSent = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onNotificationSent = $onNotificationSent"
+ )
+ }
+ is GattServerCallback.OnPhyRead -> {
+ val onPhyRead:
+ GattServerCallback.OnPhyRead = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onPhyRead = $onPhyRead"
+ )
+ }
+ is GattServerCallback.OnPhyUpdate -> {
+ val onPhyUpdate:
+ GattServerCallback.OnPhyUpdate = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onPhyUpdate = $onPhyUpdate"
+ )
+ }
+ is GattServerCallback.OnServiceAdded -> {
+ val onServiceAdded:
+ GattServerCallback.OnServiceAdded = gattServerCallback
+ Log.d(
+ TAG,
+ "openGattServer() called with: " +
+ "onServiceAdded = $onServiceAdded"
+ )
+ }
+ }
+ }
+ }
+ }
}
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_btx.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_btx.xml
index 2f53401..1a750d7 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_btx.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_btx.xml
@@ -20,27 +20,36 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="16dp"
tools:context=".ui.bluetoothx.BtxFragment">
- <androidx.appcompat.widget.SwitchCompat
- android:id="@+id/switch_advertise"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/advertise_using_btx"
- android:layout_marginBottom="16dp"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/button_scan"
- app:layout_constraintEnd_toEndOf="parent" />
-
<Button
android:id="@+id/button_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="16dp"
+ android:layout_marginTop="16dp"
android:text="@string/scan_using_btx"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent" />
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <androidx.appcompat.widget.SwitchCompat
+ android:id="@+id/switch_advertise"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/advertise_using_btx"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/button_scan" />
+
+ <androidx.appcompat.widget.SwitchCompat
+ android:id="@+id/switch_gatt_server"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/open_gatt_server_using_btx"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/switch_advertise" />
</androidx.constraintlayout.widget.ConstraintLayout>