Add Framework openGattServer

Bug: 267203732
Test: build
Change-Id: Ied81b7a0f33e585466250503f0167a1eaef0f2c4
diff --git a/bluetooth/integration-tests/testapp/src/main/AndroidManifest.xml b/bluetooth/integration-tests/testapp/src/main/AndroidManifest.xml
index 1f1625b..4fd7629 100644
--- a/bluetooth/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/bluetooth/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -21,7 +21,8 @@
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
-    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
 
 </manifest>
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt
index 3066bdc..9f655fb 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt
@@ -66,6 +66,7 @@
             arrayOf(
                 Manifest.permission.ACCESS_FINE_LOCATION,
                 Manifest.permission.BLUETOOTH_ADVERTISE,
+                Manifest.permission.BLUETOOTH_CONNECT,
                 Manifest.permission.BLUETOOTH_SCAN,
             )
         )
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/framework/FwkFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/framework/FwkFragment.kt
index d066bbc..46e8e17 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/framework/FwkFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/framework/FwkFragment.kt
@@ -17,6 +17,12 @@
 package androidx.bluetooth.integration.testapp.ui.framework
 
 import android.annotation.SuppressLint
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothGattCharacteristic
+import android.bluetooth.BluetoothGattDescriptor
+import android.bluetooth.BluetoothGattServer
+import android.bluetooth.BluetoothGattServerCallback
+import android.bluetooth.BluetoothGattService
 import android.bluetooth.BluetoothManager
 import android.bluetooth.le.AdvertiseCallback
 import android.bluetooth.le.AdvertiseData
@@ -80,8 +86,10 @@
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View {
-        Log.d(TAG, "onCreateView() called with: inflater = $inflater, " +
-            "container = $container, savedInstanceState = $savedInstanceState")
+        Log.d(
+            TAG, "onCreateView() called with: inflater = $inflater, " +
+                "container = $container, savedInstanceState = $savedInstanceState"
+        )
         fwkViewModel = ViewModelProvider(this)[FwkViewModel::class.java]
 
         _binding = FragmentFwkBinding.inflate(inflater, container, false)
@@ -99,11 +107,19 @@
             if (isChecked) startAdvertise()
             else stopAdvertise()
         }
+
+        binding.switchGattServer.setOnCheckedChangeListener { _, isChecked ->
+            if (isChecked) openGattServer()
+            else closeGattServer()
+        }
     }
 
+    // Permissions are handled by MainActivity requestBluetoothPermissions
+    @SuppressLint("MissingPermission")
     override fun onDestroyView() {
         super.onDestroyView()
         _binding = null
+        bluetoothGattServer?.close()
     }
 
     // Permissions are handled by MainActivity requestBluetoothPermissions
@@ -170,4 +186,154 @@
 
         bleAdvertiser?.stopAdvertising(advertiseCallback)
     }
+
+    private var bluetoothGattServer: BluetoothGattServer? = null
+
+    // Permissions are handled by MainActivity requestBluetoothPermissions
+    @SuppressLint("MissingPermission")
+    private fun openGattServer() {
+        Log.d(TAG, "openGattServer() called")
+
+        val bluetoothManager =
+            context?.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
+
+        bluetoothGattServer = bluetoothManager?.openGattServer(
+            requireContext(),
+            object : BluetoothGattServerCallback() {
+                override fun onConnectionStateChange(
+                    device: BluetoothDevice?,
+                    status: Int,
+                    newState: Int
+                ) {
+                    Log.d(
+                        TAG,
+                        "onConnectionStateChange() called with: device = $device" +
+                            ", status = $status, newState = $newState"
+                    )
+                }
+
+                override fun onServiceAdded(status: Int, service: BluetoothGattService?) {
+                    Log.d(TAG, "onServiceAdded() called with: status = $status, service = $service")
+                }
+
+                override fun onCharacteristicReadRequest(
+                    device: BluetoothDevice?,
+                    requestId: Int,
+                    offset: Int,
+                    characteristic: BluetoothGattCharacteristic?
+                ) {
+                    Log.d(
+                        TAG,
+                        "onCharacteristicReadRequest() called with: device = $device" +
+                            ", requestId = $requestId, offset = $offset" +
+                            ", characteristic = $characteristic"
+                    )
+                }
+
+                override fun onCharacteristicWriteRequest(
+                    device: BluetoothDevice?,
+                    requestId: Int,
+                    characteristic: BluetoothGattCharacteristic?,
+                    preparedWrite: Boolean,
+                    responseNeeded: Boolean,
+                    offset: Int,
+                    value: ByteArray?
+                ) {
+                    Log.d(
+                        TAG,
+                        "onCharacteristicWriteRequest() called with: device = $device" +
+                            ", requestId = $requestId, characteristic = $characteristic" +
+                            ", preparedWrite = $preparedWrite, responseNeeded = $responseNeeded" +
+                            ", offset = $offset, value = $value"
+                    )
+                }
+
+                override fun onDescriptorReadRequest(
+                    device: BluetoothDevice?,
+                    requestId: Int,
+                    offset: Int,
+                    descriptor: BluetoothGattDescriptor?
+                ) {
+                    Log.d(
+                        TAG,
+                        "onDescriptorReadRequest() called with: device = $device" +
+                            ", requestId = $requestId, offset = $offset, descriptor = $descriptor"
+                    )
+                }
+
+                override fun onDescriptorWriteRequest(
+                    device: BluetoothDevice?,
+                    requestId: Int,
+                    descriptor: BluetoothGattDescriptor?,
+                    preparedWrite: Boolean,
+                    responseNeeded: Boolean,
+                    offset: Int,
+                    value: ByteArray?
+                ) {
+                    Log.d(
+                        TAG,
+                        "onDescriptorWriteRequest() called with: device = $device" +
+                            ", requestId = $requestId, descriptor = $descriptor" +
+                            ", preparedWrite = $preparedWrite, responseNeeded = $responseNeeded" +
+                            ", offset = $offset, value = $value"
+                    )
+                }
+
+                override fun onExecuteWrite(
+                    device: BluetoothDevice?,
+                    requestId: Int,
+                    execute: Boolean
+                ) {
+                    Log.d(
+                        TAG,
+                        "onExecuteWrite() called with: device = $device, requestId = $requestId" +
+                            ", execute = $execute"
+                    )
+                }
+
+                override fun onNotificationSent(device: BluetoothDevice?, status: Int) {
+                    Log.d(
+                        TAG,
+                        "onNotificationSent() called with: device = $device, status = $status"
+                    )
+                }
+
+                override fun onMtuChanged(device: BluetoothDevice?, mtu: Int) {
+                    Log.d(TAG, "onMtuChanged() called with: device = $device, mtu = $mtu")
+                }
+
+                override fun onPhyUpdate(
+                    device: BluetoothDevice?,
+                    txPhy: Int,
+                    rxPhy: Int,
+                    status: Int
+                ) {
+                    Log.d(
+                        TAG, "onPhyUpdate() called with: device = $device, txPhy = $txPhy" +
+                            ", rxPhy = $rxPhy, status = $status"
+                    )
+                }
+
+                override fun onPhyRead(
+                    device: BluetoothDevice?,
+                    txPhy: Int,
+                    rxPhy: Int,
+                    status: Int
+                ) {
+                    Log.d(
+                        TAG,
+                        "onPhyRead() called with: device = $device, txPhy = $txPhy" +
+                            ", rxPhy = $rxPhy, status = $status"
+                    )
+                }
+            })
+    }
+
+    // Permissions are handled by MainActivity requestBluetoothPermissions
+    @SuppressLint("MissingPermission")
+    private fun closeGattServer() {
+        Log.d(TAG, "closeGattServer() called")
+
+        bluetoothGattServer?.close()
+    }
 }
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/activity_main.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 4bdca3c..5d9372f 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -21,7 +21,6 @@
     android:id="@+id/container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingTop="?attr/actionBarSize"
     tools:context=".MainActivity">
 
     <com.google.android.material.bottomnavigation.BottomNavigationView
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_fwk.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_fwk.xml
index 6a72bf2..48c20bf 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_fwk.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_fwk.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.framework.FwkFragment">
 
-    <androidx.appcompat.widget.SwitchCompat
-        android:id="@+id/switch_advertise"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/advertise_using_fwk"
-        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_fwk"
-        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_fwk"
+        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_fwk"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/switch_advertise" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml b/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
index 170c6a7..913a1bb 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
@@ -27,4 +27,8 @@
     <string name="advertise_using_fwk">Advertise using Framework Bluetooth APIs</string>
     <string name="advertise_using_btx">Advertise using BluetoothX APIs</string>
     <string name="advertise_start_message">Advertise started</string>
+
+    <string name="open_gatt_server_using_fwk">Open GATT Server using Framework Bluetooth APIs</string>
+    <string name="open_gatt_server_using_btx">Open GATT Server using BluetoothX APIs</string>
+    <string name="gatt_server_open">GATT Server open</string>
 </resources>