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