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>