Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8324bada authored by Charlie Boutier's avatar Charlie Boutier
Browse files

BumbleBluetoothTests: Add avrcp service

* Add Avrp gRPC service
* Add AVDTP / AVRCP sdp records
* Update SDP tests to reflect SDP modifications

Test: atest BumbleBluetoothTests
Flag: TEST ONLY
Bug: 353575671
Change-Id: I98c253ceb80d8dbb9461e1fd8924cfaf93d6db9d
parent 968f1e5a
Loading
Loading
Loading
Loading
+11 −8
Original line number Diff line number Diff line
@@ -13,7 +13,6 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.bluetooth;

import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +37,9 @@ import org.junit.runner.RunWith;

import pandora.HostProto.ConnectRequest;

import java.util.Arrays;
import java.util.List;

/** Test cases for {@link ServiceDiscoveryManager}. */
@RunWith(AndroidJUnit4.class)
public class SdpClientTest {
@@ -47,7 +49,7 @@ public class SdpClientTest {
    private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class);
    private final BluetoothAdapter mAdapter = mManager.getAdapter();

    private SettableFuture<ParcelUuid[]> mFutureIntent;
    private SettableFuture<List<ParcelUuid>> mFutureIntent;

    @Rule public final AdoptShellPermissionsRule mPermissionRule = new AdoptShellPermissionsRule();

@@ -61,7 +63,7 @@ public class SdpClientTest {
                        ParcelUuid[] parcelUuids =
                                intent.getParcelableArrayExtra(
                                        BluetoothDevice.EXTRA_UUID, ParcelUuid.class);
                        mFutureIntent.set(parcelUuids);
                        mFutureIntent.set(Arrays.asList(parcelUuids));
                    }
                }
            };
@@ -76,21 +78,22 @@ public class SdpClientTest {
        String local_addr = mAdapter.getAddress();
        byte[] local_bytes_addr = Utils.addressBytesFromString(local_addr);

        // Initiate connect from remote
        mBumble.hostBlocking()
                .connect(
                        ConnectRequest.newBuilder()
                                .setAddress(ByteString.copyFrom(local_bytes_addr))
                                .build());

        // Get the remote device
        BluetoothDevice device = mBumble.getRemoteDevice();

        // Execute service discovery procedure
        assertThat(device.fetchUuidsWithSdp()).isTrue();

        ParcelUuid[] arr = mFutureIntent.get();
        assertThat(arr).asList().contains(BluetoothUuid.HFP);
        assertThat(mFutureIntent.get())
                .containsExactly(
                        BluetoothUuid.HFP,
                        BluetoothUuid.A2DP_SOURCE,
                        BluetoothUuid.A2DP_SINK,
                        BluetoothUuid.AVRCP);

        mContext.unregisterReceiver(mConnectionStateReceiver);
    }
+6 −2
Original line number Diff line number Diff line
@@ -24,13 +24,15 @@ from bumble_experimental.asha import AshaService
from bumble_experimental.dck import DckService
from bumble_experimental.gatt import GATTService
from bumble_experimental.rfcomm import RFCOMMService
from bumble_experimental.avrcp import AvrcpService

from pandora_experimental.asha_grpc_aio import add_AshaServicer_to_server
from pandora_experimental.dck_grpc_aio import add_DckServicer_to_server
from pandora_experimental.gatt_grpc_aio import add_GATTServicer_to_server
from pandora_experimental.rfcomm_grpc_aio import add_RFCOMMServicer_to_server
from pandora_experimental.avrcp_grpc_aio import add_AVRCPServicer_to_server

from typing import Dict, Any
from typing import Any, Dict

BUMBLE_SERVER_GRPC_PORT = 7999
ROOTCANAL_PORT_CUTTLEFISH = 7300
@@ -68,7 +70,9 @@ def args_parser() -> argparse.ArgumentParser:
    return parser


def register_experimental_services():
def register_experimental_services() -> None:
    bumble_server.register_servicer_hook(
        lambda bumble, _, server: add_AVRCPServicer_to_server(AvrcpService(bumble.device), server))
    bumble_server.register_servicer_hook(
        lambda bumble, _, server: add_AshaServicer_to_server(AshaService(bumble.device), server))
    bumble_server.register_servicer_hook(
+76 −0
Original line number Diff line number Diff line
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from bumble.avdtp import Listener as AvdtpListener, MediaCodecCapabilities, AVDTP_AUDIO_MEDIA_TYPE
from bumble.avrcp import Protocol as AvrcpProtocol, make_target_service_sdp_records, make_controller_service_sdp_records
from bumble.a2dp import (A2DP_SBC_CODEC_TYPE, SBC_DUAL_CHANNEL_MODE, SBC_JOINT_STEREO_CHANNEL_MODE,
                         SBC_LOUDNESS_ALLOCATION_METHOD, SBC_MONO_CHANNEL_MODE, SBC_SNR_ALLOCATION_METHOD,
                         SBC_STEREO_CHANNEL_MODE, SbcMediaCodecInformation, make_audio_sink_service_sdp_records,
                         make_audio_source_service_sdp_records)
from bumble.device import Device
from pandora_experimental.avrcp_grpc_aio import AVRCPServicer


class AvrcpService(AVRCPServicer):
    device: Device

    def __init__(self, device: Device) -> None:
        super().__init__()
        self.device = device

        sdp_records = {
            0x00010002: make_audio_source_service_sdp_records(0x00010002),  # A2DP Source
            0x00010003: make_audio_sink_service_sdp_records(0x00010003),  # A2DP Sink
            0x00010004: make_controller_service_sdp_records(0x00010004),  # AVRCP Controller
            0x00010005: make_target_service_sdp_records(0x00010005),  # AVRCP Target
        }
        self.device.sdp_service_records.update(sdp_records)

        # Register AVDTP L2cap
        avdtp_listener = AvdtpListener.for_device(device)

        def on_avdtp_connection(server) -> None:  # type: ignore
            server.add_sink(codec_capabilities())  # type: ignore

        avdtp_listener.on('connection', on_avdtp_connection)  # type: ignore

        # Register AVRCP L2cap
        avrcp_protocol = AvrcpProtocol(delegate=None)
        avrcp_protocol.listen(device)


def codec_capabilities() -> MediaCodecCapabilities:
    """Codec capabilities for the Bumble sink devices."""

    return MediaCodecCapabilities(
        media_type=AVDTP_AUDIO_MEDIA_TYPE,
        media_codec_type=A2DP_SBC_CODEC_TYPE,
        media_codec_information=SbcMediaCodecInformation.from_lists(
            sampling_frequencies=[48000, 44100, 32000, 16000],
            channel_modes=[
                SBC_MONO_CHANNEL_MODE,
                SBC_DUAL_CHANNEL_MODE,
                SBC_STEREO_CHANNEL_MODE,
                SBC_JOINT_STEREO_CHANNEL_MODE,
            ],
            block_lengths=[4, 8, 12, 16],
            subbands=[4, 8],
            allocation_methods=[
                SBC_LOUDNESS_ALLOCATION_METHOD,
                SBC_SNR_ALLOCATION_METHOD,
            ],
            minimum_bitpool_value=2,
            maximum_bitpool_value=53,
        ),
    )