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

Commit bd9bd20d authored by Aritra Sen's avatar Aritra Sen
Browse files

Add facade APIs for HFP service required for topshim testing.

Bug: 250899887
Test: mma -j $(nproc)
Test: ./build.py
Test: system/gd/cert/run --clean --topshim
Tag: #floss
Change-Id: Ib050a2e6b005ab0ad21cf9cb3def563597bdded9

Change-Id: I8b5395b2ee351aab1519acbabbba2e8372923f6a
parent e886e06c
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -100,6 +100,14 @@ service GattService {
  rpc ServerReadPhy(google.protobuf.Empty) returns (google.protobuf.Empty) {}
}

service HfpService {
  rpc StartSlc(StartSlcRequest) returns (google.protobuf.Empty) {}
  rpc StopSlc(StopSlcRequest) returns (google.protobuf.Empty) {}
  rpc ConnectAudio(ConnectAudioRequest) returns (google.protobuf.Empty) {}
  rpc DisconnectAudio(DisconnectAudioRequest) returns (google.protobuf.Empty) {}
  rpc SetVolume(SetVolumeRequest) returns (google.protobuf.Empty) {}
}

enum EventType {
  ADAPTER_STATE = 0;
  SSP_REQUEST = 1;
@@ -152,3 +160,33 @@ message A2dpSourceSetActiveDevicetResponse {}
message RemoveBondRequest {
  string address = 1;
}

message StartSlcRequest {
  Connection connection = 1;
}

message StopSlcRequest {
  Connection connection = 1;
}

message ConnectAudioRequest {
  Connection connection = 1;
  bool is_sco_offload_enabled = 2;
  bool force_cvsd = 3;
}

message DisconnectAudioRequest {
  Connection connection = 1;
}

message SetVolumeRequest {
  Connection connection = 1;
  int32 volume = 2;
}

// A Token representing an ACL connection.
// It's acquired via a Connect on the Host service (Bluetooth Core stack in our case).
message Connection {
// For our HFP APIs this would store the bluetooth address but staying consistent with Pandora naming.
  bytes cookie = 1;
}
+142 −0
Original line number Diff line number Diff line
//! HFP service facade

use bt_topshim::btif::{BluetoothInterface, RawAddress};
use bt_topshim::profiles::hfp::{Hfp, HfpCallbacksDispatcher};
use bt_topshim_facade_protobuf::empty::Empty;
use bt_topshim_facade_protobuf::facade::{
    ConnectAudioRequest, DisconnectAudioRequest, SetVolumeRequest, StartSlcRequest, StopSlcRequest,
};
use bt_topshim_facade_protobuf::facade_grpc::{create_hfp_service, HfpService};

use grpcio::*;

use std::str::from_utf8;
use std::sync::{Arc, Mutex};
use tokio::runtime::Runtime;

fn get_hfp_dispatcher() -> HfpCallbacksDispatcher {
    HfpCallbacksDispatcher { dispatch: Box::new(move |_cb| {}) }
}

/// Main object for Hfp facade service
#[derive(Clone)]
pub struct HfpServiceImpl {
    #[allow(dead_code)]
    rt: Arc<Runtime>,
    pub btif_hfp: Arc<Mutex<Hfp>>,
}

impl HfpServiceImpl {
    /// Create a new instance of the root facade service
    pub fn create(rt: Arc<Runtime>, btif_intf: Arc<Mutex<BluetoothInterface>>) -> grpcio::Service {
        let mut btif_hfp = Hfp::new(&btif_intf.lock().unwrap());
        btif_hfp.initialize(get_hfp_dispatcher());

        create_hfp_service(Self { rt, btif_hfp: Arc::new(Mutex::new(btif_hfp)) })
    }
}

impl HfpService for HfpServiceImpl {
    fn start_slc(&mut self, ctx: RpcContext<'_>, req: StartSlcRequest, sink: UnarySink<Empty>) {
        let hfp = self.btif_hfp.clone();
        ctx.spawn(async move {
            let bt_addr = &req.connection.unwrap().cookie;
            if let Some(addr) = RawAddress::from_bytes(bt_addr) {
                hfp.lock().unwrap().connect(addr);
                sink.success(Empty::default()).await.unwrap();
            } else {
                sink.fail(RpcStatus::with_message(
                    RpcStatusCode::INVALID_ARGUMENT,
                    format!("Invalid Request Address: {}", from_utf8(bt_addr).unwrap()),
                ))
                .await
                .unwrap();
            }
        })
    }

    fn stop_slc(&mut self, ctx: RpcContext<'_>, req: StopSlcRequest, sink: UnarySink<Empty>) {
        let hfp = self.btif_hfp.clone();
        ctx.spawn(async move {
            let bt_addr = &req.connection.unwrap().cookie;
            if let Some(addr) = RawAddress::from_bytes(bt_addr) {
                hfp.lock().unwrap().disconnect(addr);
                sink.success(Empty::default()).await.unwrap();
            } else {
                sink.fail(RpcStatus::with_message(
                    RpcStatusCode::INVALID_ARGUMENT,
                    format!("Invalid Request Address: {}", from_utf8(bt_addr).unwrap()),
                ))
                .await
                .unwrap();
            }
        })
    }

    fn connect_audio(
        &mut self,
        ctx: RpcContext<'_>,
        req: ConnectAudioRequest,
        sink: UnarySink<Empty>,
    ) {
        let hfp = self.btif_hfp.clone();
        ctx.spawn(async move {
            let bt_addr = &req.connection.unwrap().cookie;
            if let Some(addr) = RawAddress::from_bytes(bt_addr) {
                hfp.lock().unwrap().connect_audio(addr, req.is_sco_offload_enabled, req.force_cvsd);
                hfp.lock().unwrap().set_active_device(addr);
                sink.success(Empty::default()).await.unwrap();
            } else {
                sink.fail(RpcStatus::with_message(
                    RpcStatusCode::INVALID_ARGUMENT,
                    format!("Invalid Request Address: {}", from_utf8(bt_addr).unwrap()),
                ))
                .await
                .unwrap();
            }
        })
    }

    fn disconnect_audio(
        &mut self,
        ctx: RpcContext<'_>,
        req: DisconnectAudioRequest,
        sink: UnarySink<Empty>,
    ) {
        let hfp = self.btif_hfp.clone();
        ctx.spawn(async move {
            let bt_addr = &req.connection.unwrap().cookie;
            if let Some(addr) = RawAddress::from_bytes(bt_addr) {
                hfp.lock().unwrap().disconnect_audio(addr);
                sink.success(Empty::default()).await.unwrap();
            } else {
                sink.fail(RpcStatus::with_message(
                    RpcStatusCode::INVALID_ARGUMENT,
                    format!("Invalid Request Address: {}", from_utf8(bt_addr).unwrap()),
                ))
                .await
                .unwrap();
            }
        })
    }

    fn set_volume(&mut self, ctx: RpcContext<'_>, req: SetVolumeRequest, sink: UnarySink<Empty>) {
        let hfp = self.btif_hfp.clone();
        ctx.spawn(async move {
            let bt_addr = &req.connection.unwrap().cookie;
            if let Some(addr) = RawAddress::from_bytes(bt_addr) {
                // TODO(aritrasen): Consider using TryFrom and cap the maximum volume here
                // since `as` silently deals with data overflow, which might not be preferred.
                hfp.lock().unwrap().set_volume(req.volume as i8, addr);
                sink.success(Empty::default()).await.unwrap();
            } else {
                sink.fail(RpcStatus::with_message(
                    RpcStatusCode::INVALID_ARGUMENT,
                    format!("Invalid Request Address: {}", from_utf8(bt_addr).unwrap()),
                ))
                .await
                .unwrap();
            }
        })
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ use tokio::runtime::Runtime;

mod adapter_service;
mod gatt_service;
mod hfp_service;
mod media_service;
mod security_service;

@@ -85,6 +86,8 @@ async fn async_main(rt: Arc<Runtime>, mut sigint: mpsc::UnboundedReceiver<()>) {

    let gatt_service_impl = gatt_service::GattServiceImpl::create(rt.clone(), btif_intf.clone());

    let hfp_service_impl = hfp_service::HfpServiceImpl::create(rt.clone(), btif_intf.clone());

    let media_service_impl = media_service::MediaServiceImpl::create(rt.clone(), btif_intf.clone());

    let start_stack_now = value_t!(matches, "start-stack-now", bool).unwrap();
@@ -97,6 +100,7 @@ async fn async_main(rt: Arc<Runtime>, mut sigint: mpsc::UnboundedReceiver<()>) {
        .register_service(adapter_service_impl)
        .register_service(security_service_impl)
        .register_service(gatt_service_impl)
        .register_service(hfp_service_impl)
        .register_service(media_service_impl)
        .bind("0.0.0.0", grpc_port)
        .build()