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

Commit c19c59f6 authored by Aritra Sen's avatar Aritra Sen Committed by Gerrit Code Review
Browse files

Merge "Add facade APIs for HFP service required for topshim testing."

parents 37e126dd bd9bd20d
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()