1# Writing a BumbleBluetoothTests: A Comprehensive Guide 2 3This guide seeks to demystify the process using `testDiscoverDkGattService` as an example. 4By the end, you should have a blueprint for constructing similar tests for your Bluetooth 5functionalities. 6 7The BumbleBluetoothTests source code can be found in the Android codebase [here][bumble-bluetooth-tests-code]. 8 9Pandora APIs are implemented both on Android in a [PandoraServer][pandora-server-code] app and on 10[BumblePandoraServer][bumble-github-pandora-server]. The communication between the virtual Android 11DUT and the virtual Bumble Reference device is made through [Rootcanal][rootcanal-code], a virtual 12Bluetooth Controller. 13 14 15## Prerequisites 16 17Before diving in, ensure you are acquainted with: 18- [Android Junit4][android-junit4] for unit testing 19- [Mockito](https://site.mockito.org/) for mocking dependencies 20- [Java gRPC documentation][grpc-java-doc] 21- [Pandora stable APIs][pandora-stable-apis] 22- [Pandora experimental APIs][pandora-experimental-apis] 23 24You must have a running Cuttlefish instance. If not, you can run the following commands from the 25root of your Android repository: 26 27```shell 28cd $ANDROID_BUILD_TOP 29source build/envsetup.sh 30lunch aosp_cf_x86_64_phone-trunk_staging-userdebug 31acloud create # Create a remote instance using the latest know good build image. 32acloud create --local-image # OR: Create a remote instance using a local image. 33acloud create --local-image --local-instance # OR: Create a local instance using a local image. 34``` 35 36Install virtual env: 37 38```shell 39sudo apt install virtualenv 40``` 41Note: For Googlers, from an internal Android repository, use the `cf_x86_64_phone-userdebug` target 42instead. 43 44## Run existing tests 45 46You can run all the existing BumbleBluetoothTests by doing so: 47 48```shell 49atest BumbleBluetoothTests 50``` 51 52If you wish to run a specific test file: 53 54```shell 55atest BumbleBluetoothTests:<package_name>.<test_file_name> 56atest BumbleBluetoothTests:android.bluetooth.DckTest 57``` 58 59And to run a specific test from a test file: 60 61```shell 62atest BumbleBluetoothTests:<package_name>.<test_file_name>#<test_name> 63atest BumbleBluetoothTests:android.bluetooth.DckTest#testDiscoverDkGattService 64``` 65 66Note: The process might not shut down correctly when interrupted with a SIGINT (Ctrl + C) command. 67This can leave behind a ghost process. To locate and terminate it, simply follow these steps: 68 69```shell 70ps aux | grep python3 # Identify the ghost process and its process id 71kill <pid> 72``` 73 74## Crafting the test: Step by Step 75 76### 0. Create the test file 77 78You can either choose to build your new test in Kotlin or in Java. It is totally up to you. 79However for this example we will build one in Kotlin. 80 81Let say we are creating a DCK test. 82 83First, create your file under `p/m/Blueooth/frameworks/bumble/src/android/bluetooth` like so: 84 85```shell 86cd p/m/Bluetooth/frameworks/bumble/src/android/bluetooth 87touch DckTest.kt # We usualy name our test file <test_suite_name>Test.kt/.java 88``` 89 90Then add the minimum requirements: 91 92```kotlin 93import androidx.test.ext.junit.runners.AndroidJUnit4 94 95import org.junit.Rule 96import org.junit.Test 97 98@RunWith(AndroidJUnit4::class) 99public class DckTest { 100 private val TAG = "DckTest" 101 102 // A Rule live from a test setup through it's teardown. 103 // Gives shell permissions during the test. 104 @Rule @JvmField val mPermissionRule = AdoptShellPermissionsRule() 105 106 // Setup a Bumble Pandora device for the duration of the test. 107 // Acting as a Pandora client, it can be interacted with through the Pandora APIs. 108 @Rule @JvmField val mBumble = PandoraDevice() 109 110 @Test 111 fun testDiscoverDkGattService() { 112 // Test implementation 113 } 114} 115``` 116 117### 1. Register with Service via gRPC 118 119Here, we're dealing with Bumble's DCK (Digital Car Key) service. First, we need Bumble to register 120the Dck Gatt service. 121 122```kotlin 123//- `dckBlocking()` is likely a stub accessing the DCK service over gRPC in a synchronous manner. 124//- `withDeadline(Deadline.after(TIMEOUT, TimeUnit.MILLISECONDS))` sets a timeout for the call. 125//- `register(Empty.getDefaultInstance())` communicates our registration to the server. 126mBumble 127 .dckBlocking() 128 .withDeadline(Deadline.after(TIMEOUT, TimeUnit.MILLISECONDS)) 129 .register(Empty.getDefaultInstance()) 130``` 131 132### 2. Advertise Bluetooth Capabilities 133 134If our device wants to be discoverable and indicate its capabilities, it would "advertise" these 135capabilities. Here, it's done via another gRPC call. 136 137```kotlin 138mBumble 139 .hostBlocking() 140 .advertise( 141 AdvertiseRequest.newBuilder() 142 .setLegacy(true) // As of now, Bumble only support legacy advertising (b/266124496). 143 .setConnectable(true) 144 .setOwnAddressType(OwnAddressType.RANDOM) // Ask Bumble to advertise it's `RANDOM` address. 145 .build() 146 ) 147``` 148 149### 3. Fetch a Known Remote Bluetooth Device 150 151To keep things straightforward, the Bumble RANDOM address is set to a predefined constant. 152Typically, an LE scan would be conducted to identify the Bumble device, matching it based on its 153Advertising data. 154 155```kotlin 156val bumbleDevice = 157 bluetoothAdapter.getRemoteLeDevice( 158 Utils.BUMBLE_RANDOM_ADDRESS, 159 BluetoothDevice.ADDRESS_TYPE_RANDOM // Specify address type as RANDOM because the device advertises with this address type. 160 ) 161``` 162 163### 4. Create Mock Callback for GATT Events 164 165Interactions over Bluetooth often involve callback mechanisms. Here, we're mocking the callback 166with Mockito to verify later that expected events occurred. 167 168```kotlin 169val gattCallback = mock(BluetoothGattCallback::class.java) 170``` 171### 5. Initiate and Verify Connection 172 173To bond with Bumble, we initiate a connection and then verify that the connection is successful. 174 175```kotlin 176var bumbleGatt = bumbleDevice.connectGatt(context, false, gattCallback) 177verify(gattCallback, timeout(TIMEOUT)) 178 .onConnectionStateChange( 179 any(), 180 eq(BluetoothGatt.GATT_SUCCESS), 181 eq(BluetoothProfile.STATE_CONNECTED) 182 ) 183``` 184### 6. Discover and Verify GATT Services 185 186After connecting, we seek to find out the services offered by Bumble and affirm their successful 187discovery. 188 189```kotlin 190bumbleGatt.discoverServices() 191verify(gattCallback, timeout(TIMEOUT)) 192 .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS)) 193 194``` 195 196### 7. Confirm Service Availability 197 198Ensure that the specific service (in this example, CCC_DK_UUID) is present on the remote device. 199 200```kotlin 201assertThat(bumbleGatt.getService(CCC_DK_UUID)).isNotNull() 202``` 203### 8. Disconnect and Confirm 204Finally, after our operations, we disconnect and ensure it is done gracefully. 205 206```kotlin 207bumbleGatt.disconnect() 208verify(gattCallback, timeout(TIMEOUT)) 209 .onConnectionStateChange( 210 any(), 211 eq(BluetoothGatt.GATT_SUCCESS), 212 eq(BluetoothProfile.STATE_DISCONNECTED) 213 ) 214``` 215 216## Conclusion 217 218This tutorial provided a step-by-step guide on testing some Bluetooth functionalities on top of the 219Android Bluetooth frameworks, leveraging both gRPC and Bluetooth GATT interactions. For the detailed 220implementation and the full code, refer to our [source code][bumble-bluetooth-tests-code]. 221 222[android-junit4]: https://developer.android.com/reference/androidx/test/runner/AndroidJUnit4 223[bumble-bluetooth-tests-code]: https://cs.android.com/android/platform/superproject/+/main:packages/modules/Bluetooth/framework/tests/bumble/ 224[bumble-github-pandora-server]: https://github.com/google/bumble/tree/main/bumble/pandora 225[grpc-java-doc]: https://grpc.io/docs/languages/java/ 226[pandora-experimental-apis]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/pandora/interfaces/pandora_experimental/ 227[pandora-server-code]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/android/pandora/server/ 228[pandora-stable-apis]: https://cs.android.com/android/platform/superproject/main/+/main:external/pandora/bt-test-interfaces/ 229[rootcanal-code]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/tools/rootcanal 230