1# Avatar with Android
2
3Since Android provides an implementation of the [Pandora APIs](
4https://github.com/google/bt-test-interfaces), Avatar can run with Android
5devices.
6
7## Setup
8
9The standard Avatar setup on Android is to test a [Cuttlefish](
10https://source.android.com/docs/setup/create/cuttlefish) virtual Android DUT
11against a [Bumble](https://github.com/google/bumble) virtual Reference device
12(REF).
13
14Pandora APIs are implemented both on Android in a
15[PandoraServer][pandora-server-code] app and on [Bumble](
16https://github.com/google/bumble/tree/main/bumble/pandora). The communication
17between the virtual Android DUT and the virtual Bumble Reference device is made
18through [Rootcanal][rootcanal-code], a virtual Bluetooth Controller.
19
20![Avatar Android architecture](
21images/avatar-android-bumble-virtual-architecture-simplified.svg)
22
23## Usage
24
25There are two different command line interfaces to use Avatar on Android.
26
27### Prerequisites
28
29You must have a running CF instance. If not, you can run the following commands
30from the root of your Android repository:
31
32```shell
33source build/envsetup.sh
34lunch aosp_cf_x86_64_phone-userdebug
35acloud create --local-image --local-instance
36```
37
38Note: For Googlers, from an internal Android repository, use the
39`cf_x86_64_phone-userdebug` target instead. You can also use a CF remote
40instance by removing `--local-instance`.
41
42### `avatar` CLI (preferred)
43
44You can run all the existing Avatar tests on Android by running the following
45commands from the root of your Android repository:
46
47```shell
48cd packages/modules/Bluetooth
49source android/pandora/test/envsetup.sh
50avatar --help
51avatar format # Format test files
52avatar lint # Lint test files
53avatar run --mobly-std-log  # '--mobly-std-log' to print mobly logs, silent otherwise
54```
55
56Note: If you have errors such as `ModuleNotFoundError: no module named pip`,
57reset your Avatar cache by doing `rm -rf ~/.cache/avatar/venv`.
58
59### `atest` CLI
60
61You can also run all Avatar tests using [`atest`](
62https://source.android.com/docs/core/tests/development/atest):
63
64```shell
65atest avatar -v # All tests in verbose
66```
67
68## Build a new Avatar test
69
70Follow the instructions below to create your first Avatar test.
71
72### Create a test class
73
74Create a new Avatar test class file `codelab_test.py` in the Android Avatar
75tests folder, `packages/modules/Bluetooth/android/pandora/test/`:
76
77```python
78from typing import Optional  # Avatar is strictly typed.
79
80# Importing Mobly modules required for the test.
81from mobly import base_test  # Mobly base test class .
82
83# Importing Avatar classes and functions required for the test.
84from avatar import PandoraDevices
85from avatar.aio import asynchronous  # A decorator to run asynchronous functions.
86from avatar.pandora_client import BumblePandoraClient, PandoraClient
87
88# Importing Pandora gRPC message & enum types.
89from pandora.host_pb2 import RANDOM, DataTypes
90
91
92# The test class to test the LE (Bluetooth Low Energy) Connectivity.
93class CodelabTest(base_test.BaseTestClass):
94    devices: Optional[PandoraDevices] = None
95    dut: PandoraClient
96    ref: BumblePandoraClient  # `BumblePandoraClient` is a sub-class of `PandoraClient`
97
98    # Method to set up the DUT and REF devices for the test (called once).
99    def setup_class(self) -> None:
100        self.devices = PandoraDevices(self)  # Create Pandora devices from the config.
101        self.dut, ref = self.devices
102        assert isinstance(ref, BumblePandoraClient)  # REF device is a Bumble device.
103        self.ref = ref
104
105    # Method to tear down the DUT and REF devices after the test (called once).
106    def teardown_class(self) -> None:
107        # Stopping all the devices if any.
108        if self.devices: self.devices.stop_all()
109
110    # Method to set up the test environment (called before each test).
111    @asynchronous
112    async def setup_test(self) -> None:
113        # Reset DUT and REF devices asynchronously.
114        await asyncio.gather(self.dut.reset(), self.ref.reset())
115
116    # Method to write the actual test.
117    def test_void(self) -> None:
118        assert True  # This is a placeholder for the test body.
119```
120
121For now, your test class contains only a single `test_void`.
122
123### Add a test class to Avatar test suite runner
124
125Add the tests from your test class into
126[Avatar Android test suite runner][avatar-android-suite-runner-code]:
127
128```diff
129diff --git a/android/pandora/test/main.py b/android/pandora/test/main.py
130index a124306e8f..742e087521 100644
131--- a/android/pandora/test/main.py
132+++ b/android/pandora/test/main.py
133@@ -1,11 +1,12 @@
134 from mobly import suite_runner
135
136+import codelab_test
137 import example
138
139 import logging
140 import sys
141
142-_TEST_CLASSES_LIST = [example.ExampleTest]
143+_TEST_CLASSES_LIST = [example.ExampleTest, codelab_test.CodelabTest]
144```
145
146You can now try to run your test class using `avatar`:
147
148```shell
149avatar run --mobly-std-log --include-filter 'CodelabTest'  # All the CodelabTest tests
150avatar run --mobly-std-log --include-filter 'CodelabTest#test_void' # Run only test_void
151```
152
153Or using `atest`:
154
155```shell
156atest avatar -v  # all tests
157atest avatar:'CodelabTest#test_void' -v # Run only test_void
158```
159
160### Add a real test
161
162You can add a new test to your test class by creating a new method `test_<>`,
163which is implemented by a series of calls to the Pandora APIs of either the
164Android DUT or the Bumble REF device and assert statement.
165
166A call to a Pandora API is made using `<device>.<api>.<method>(<arguments>)`.
167Pandora APIs and their descriptions are in
168[`external/pandora/bt-test-interfaces`][pandora-api-code] or
169[`package/module/Bluetooth/pandora/interfaces/pandora_experimental`][pandora-experimental-api-code].
170
171For example, add the following test to your `codelab_test.py` test class:
172
173```python
174# Test the LE connection between the central device (DUT) and peripheral device (REF).
175def test_le_connect_central(self) -> None:
176    # Start advertising on the REF device, this makes it discoverable by the DUT.
177    # The REF advertises as `connectable` and the own address type is set to `random`.
178    advertisement = self.ref.host.Advertise(
179        # Legacy since extended advertising is not yet supported in Bumble.
180        legacy=True,
181        connectable=True,
182        own_address_type=RANDOM,
183        # DUT device matches the REF device using the specific manufacturer data.
184        data=DataTypes(manufacturer_specific_data=b'pause cafe'),
185    )
186
187    # Start scanning on the DUT device.
188    scan = self.dut.host.Scan(own_address_type=RANDOM)
189    # Find the REF device using the specific manufacturer data.
190    peer = next((peer for peer in scan
191        if b'pause cafe' in peer.data.manufacturer_specific_data))
192    scan.cancel()  # Stop the scan process on the DUT device.
193
194    # Connect the DUT device to the REF device as central device.
195    connect_res = self.dut.host.ConnectLE(
196        own_address_type=RANDOM,
197        random=peer.random,  # Random REF address found during scanning.
198    )
199    advertisement.cancel()
200
201    # Assert that the connection was successful.
202    assert connect_res.connection
203    dut_ref = connect_res.connection
204
205    # Disconnect the DUT device from the REF device.
206    self.dut.host.Disconnect(connection=dut_ref)
207```
208
209Then, run your new `test_le_connect_central` test:
210
211```shell
212avatar run --mobly-std-log --include-filter 'CodelabTest'
213```
214
215### Implement your own tests
216
217Before starting, you should make sure you have clearly identified the tests you
218want to build: see [Where should I start to implement Avatar tests?](
219overview#designing-avatar-tests)
220
221When your test is defined, you can implement it using the available
222[stable Pandora APIs][pandora-api-code].
223
224Note: You can find many test examples in
225[`packages/modules/Bluetooth/android/pandora/test/`][avatar-android-tests-code].
226
227**If you need an API which is not part of the finalized Pandora APIs to build
228your test**:
229
2301. If the API you need is **on the Android side**: you can also directly use the
231   [experimental Pandora API][pandora-experimental-api-code], in the same
232   fashion as the stable ones.
233
234   Warning: those APIs are subject to changes.
235
2361. If the API you need is on the Bumble side: you can also use the experimental
237   Pandora APIs by creating custom [Bumble extensions](
238   android-bumble-extensions).
239
2401. If the API you need is not part of the experimental Pandora APIs:
241
242  * Create an issue. The Avatar team will decide whether to create a new API or
243    not. We notably don't want to create APIs for device specific behaviors.
244
245  * If it is decided not to add a new API, you can instead access the Bumble
246    Bluetooth stack directly within your test. For example:
247
248    ```python
249    @asynchronous
250    async def test_pause_cafe(self) -> None:
251        from bumble.core import BT_LE_TRANSPORT
252
253        # `self.ref.device` an instance of `bumble.device.Device`
254        connection = await self.ref.device.find_peer_by_name(  # type: ignore
255            "Pause cafe",
256            transport=BT_LE_TRANSPORT,
257        )
258
259        assert connection
260        await self.ref.device.encrypt(connection, enable=True)  # type: ignore
261    ```
262
263## Contribution guide
264
265### Modify the Avatar repository
266
267All contributions to Avatar (not tests) must be submitted first to GitHub since
268it is the source of truth for Avatar. To simplify the development process,
269Android developers can make their changes on Android and get reviews on Gerrit
270as usual, but then push it first to GitHub:
271
2721. Create your CL in [`external/pandora/Avatar`][avatar-code].
2731. Ask for review on Gerrit as usual.
2741. Upon review and approval, the Avatar team creates a Pull Request on GitHub
275   with you as the author. The PR is directly approved.
2761. After it passes GitHub Avatar CI, the PR is merged.
2771. Then, the Avatar team merges the change from GitHub to Android.
278
279### Upstream experimental Pandora APIs
280
281The Pandora team continuously works to stabilize new Pandora APIs. When an
282experimental Pandora API is considered stable, it is moved from
283[`package/module/Bluetooth/pandora/interfaces/pandora_experimental`][pandora-experimental-api-code]
284to the official stable Pandora API repository in
285[`external/pandora/bt-test-interfaces`][pandora-api-code].
286
287### Upstream Android tests to the Avatar repository
288
289On a regular basis, the Avatar team evaluates Avatar tests which have been
290submitted to Android and upstream them to the Avatar repository in the
291[`cases`](/cases/) folder if they are generic, meaning not related to Android
292specifically.
293
294Such added generic tests are removed from
295[`packages/modules/Bluetooth/android/pandora/test/`][avatar-android-tests-code].
296
297## Presubmit tests
298
299All Avatar tests submitted in the
300[Android Avatar tests folder][avatar-android-tests-code] and added to
301[Avatar suite runner][avatar-android-suite-runner-code] as well as the tests in
302the [generic Avatar tests folder][avatar-tests-code], are run in Android
303Bluetooth presubmit tests (for every CL).
304
305Note: Avatar tests will soon also be run regularly on physical devices in
306Android postsubmit tests.
307
308[pandora-server-code]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/android/pandora/server/
309
310[rootcanal-code]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/tools/rootcanal
311
312[pandora-api-code]: https://cs.android.com/android/platform/superproject/+/main:external/pandora/bt-test-interfaces/pandora
313
314[pandora-experimental-api-code]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/pandora/interfaces/pandora_experimental/
315
316[avatar-tests-code]: https://cs.android.com/android/platform/superproject/+/main:external/pandora/avatar/cases
317
318[avatar-android-tests-code]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/android/pandora/test/
319
320[avatar-code]: https://cs.android.com/android/platform/superproject/+/main:external/pandora/avatar
321
322[avatar-android-suite-runner-code]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/android/pandora/test/main.py
323