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

Commit d8bd11c3 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge changes I3c7a5121,Ia7ca8af7,I1249878d am: 851590d0

Original change: https://android-review.googlesource.com/c/platform/system/bt/+/1828094

Change-Id: Ia82e2fd4bf136b2b376ad6177cf4b1fe31e737cb
parents cd79721b 851590d0
Loading
Loading
Loading
Loading
+89 −107
Original line number Diff line number Diff line
use bt_topshim::btif::BtSspVariant;

use btstack::bluetooth::{BluetoothDevice, BluetoothTransport, IBluetooth, IBluetoothCallback};
use btstack::RPCProxy;

use manager_service::iface_bluetooth_manager::IBluetoothManager;

use num_traits::cast::FromPrimitive;

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use crate::console_yellow;
use crate::print_info;
use crate::{BluetoothDBus, BluetoothManagerDBus};
use num_traits::cast::FromPrimitive;

use crate::ClientContext;
use crate::{console_red, console_yellow, print_error, print_info};
use btstack::bluetooth::{BluetoothDevice, BluetoothTransport, IBluetooth};
use manager_service::iface_bluetooth_manager::IBluetoothManager;

const INDENT_CHAR: &str = " ";
const BAR1_CHAR: &str = "=";
const BAR2_CHAR: &str = "-";
const MAX_MENU_CHAR_WIDTH: usize = 72;

struct BtCallback {
    objpath: String,
}

impl IBluetoothCallback for BtCallback {
    fn on_address_changed(&self, addr: String) {
        print_info!("Address changed to {}", addr);
    }

    fn on_device_found(&self, remote_device: BluetoothDevice) {
        print_info!("Found device: {:?}", remote_device);
    }

    fn on_discovering_changed(&self, discovering: bool) {
        print_info!("Discovering: {}", discovering);
    }

    fn on_ssp_request(
        &self,
        remote_device: BluetoothDevice,
        _cod: u32,
        variant: BtSspVariant,
        passkey: u32,
    ) {
        if variant == BtSspVariant::PasskeyNotification {
            print_info!(
                "device {}{} would like to pair, enter passkey on remote device: {:06}",
                remote_device.address.to_string(),
                if remote_device.name.len() > 0 {
                    format!(" ({})", remote_device.name)
                } else {
                    String::from("")
                },
                passkey
            );
        }
    }
}

impl RPCProxy for BtCallback {
    fn register_disconnect(&mut self, _f: Box<dyn Fn() + Send>) {}

    fn get_object_id(&self) -> String {
        self.objpath.clone()
    }
}

type CommandFunction = fn(&mut CommandHandler, &Vec<String>);

fn _noop(_handler: &mut CommandHandler, _args: &Vec<String>) {
@@ -80,11 +27,7 @@ pub struct CommandOption {

/// Handles string command entered from command line.
pub(crate) struct CommandHandler {
    bluetooth_manager: Arc<Mutex<Box<BluetoothManagerDBus>>>,
    bluetooth: Arc<Mutex<Box<BluetoothDBus>>>,

    is_bluetooth_callback_registered: bool,

    context: Arc<Mutex<ClientContext>>,
    command_options: HashMap<String, CommandOption>,
}

@@ -124,15 +67,17 @@ fn build_commands() -> HashMap<String, CommandOption> {
    command_options.insert(
        String::from("adapter"),
        CommandOption {
            description: String::from("Enable/Disable Bluetooth adapter. (e.g. adapter enable)"),
            description: String::from(
                "Enable/Disable/Show default bluetooth adapter. (e.g. adapter enable)",
            ),
            function_pointer: CommandHandler::cmd_adapter,
        },
    );
    command_options.insert(
        String::from("get_address"),
        String::from("bond"),
        CommandOption {
            description: String::from("Gets the local device address."),
            function_pointer: CommandHandler::cmd_get_address,
            description: String::from("Creates a bond with a device."),
            function_pointer: CommandHandler::cmd_bond,
        },
    );
    command_options.insert(
@@ -143,10 +88,10 @@ fn build_commands() -> HashMap<String, CommandOption> {
        },
    );
    command_options.insert(
        String::from("bond"),
        String::from("get-address"),
        CommandOption {
            description: String::from("Creates a bond with a device."),
            function_pointer: CommandHandler::cmd_bond,
            description: String::from("Gets the local device address."),
            function_pointer: CommandHandler::cmd_get_address,
        },
    );
    command_options.insert(
@@ -156,6 +101,15 @@ fn build_commands() -> HashMap<String, CommandOption> {
            function_pointer: CommandHandler::cmd_help,
        },
    );
    command_options.insert(
        String::from("list-devices"),
        CommandOption {
            description: String::from(
                "List known remote devices from most recent discovery session.",
            ),
            function_pointer: CommandHandler::cmd_list_devices,
        },
    );
    command_options.insert(
        String::from("quit"),
        CommandOption {
@@ -168,16 +122,8 @@ fn build_commands() -> HashMap<String, CommandOption> {

impl CommandHandler {
    /// Creates a new CommandHandler.
    pub fn new(
        bluetooth_manager: Arc<Mutex<Box<BluetoothManagerDBus>>>,
        bluetooth: Arc<Mutex<Box<BluetoothDBus>>>,
    ) -> CommandHandler {
        CommandHandler {
            bluetooth_manager,
            bluetooth,
            is_bluetooth_callback_registered: false,
            command_options: build_commands(),
        }
    pub fn new(context: Arc<Mutex<ClientContext>>) -> CommandHandler {
        CommandHandler { context, command_options: build_commands() }
    }

    /// Entry point for command and arguments
@@ -195,6 +141,15 @@ impl CommandHandler {
        };
    }

    //  Common message for when the adapter isn't ready
    fn adapter_not_ready(&self) {
        let adapter_idx = self.context.lock().unwrap().default_adapter;
        print_error!(
            "Default adapter {} is not enabled. Enable the adapter before using this command.",
            adapter_idx
        );
    }

    fn cmd_help(&mut self, args: &Vec<String>) {
        if args.len() > 0 {
            match self.command_options.get(&args[0]) {
@@ -245,12 +200,22 @@ impl CommandHandler {
    }

    fn cmd_adapter(&mut self, args: &Vec<String>) {
        enforce_arg_len(args, 1, "adapter <enable|disable>", || match &args[0][0..] {
        let default_adapter = self.context.lock().unwrap().default_adapter;
        enforce_arg_len(args, 1, "adapter <enable|disable|show>", || match &args[0][0..] {
            "enable" => {
                self.bluetooth_manager.lock().unwrap().start(0);
                self.context.lock().unwrap().manager_dbus.start(default_adapter);
            }
            "disable" => {
                self.bluetooth_manager.lock().unwrap().stop(0);
                self.context.lock().unwrap().manager_dbus.stop(default_adapter);
            }
            "show" => {
                let enabled = self.context.lock().unwrap().enabled;
                let address = match self.context.lock().unwrap().adapter_address.as_ref() {
                    Some(x) => x.clone(),
                    None => String::from(""),
                };
                print_info!("State: {}", if enabled { "enabled" } else { "disabled" });
                print_info!("Address: {}", address);
            }
            _ => {
                println!("Invalid argument '{}'", args[0]);
@@ -259,44 +224,54 @@ impl CommandHandler {
    }

    fn cmd_get_address(&mut self, _args: &Vec<String>) {
        let addr = self.bluetooth.lock().unwrap().get_address();
        print_info!("Local address = {}", addr);
        if !self.context.lock().unwrap().adapter_ready {
            self.adapter_not_ready();
            return;
        }

        let address = self.context.lock().unwrap().adapter_dbus.as_ref().unwrap().get_address();
        print_info!("Local address = {}", &address);
        // Cache address for adapter show
        self.context.lock().unwrap().adapter_address = Some(address);
    }

    fn cmd_discovery(&mut self, args: &Vec<String>) {
        enforce_arg_len(args, 1, "discovery <start|stop>", || {
            match &args[0][0..] {
                "start" => {
                    // TODO: Register the BtCallback when getting a OnStateChangedCallback from btmanagerd.
                    if !self.is_bluetooth_callback_registered {
                        self.bluetooth.lock().unwrap().register_callback(Box::new(BtCallback {
                            objpath: String::from(
                                "/org/chromium/bluetooth/client/bluetooth_callback",
                            ),
                        }));
                        self.is_bluetooth_callback_registered = true;
        if !self.context.lock().unwrap().adapter_ready {
            self.adapter_not_ready();
            return;
        }
                    self.bluetooth.lock().unwrap().start_discovery();

        enforce_arg_len(args, 1, "discovery <start|stop>", || match &args[0][0..] {
            "start" => {
                self.context.lock().unwrap().adapter_dbus.as_ref().unwrap().start_discovery();
            }
            "stop" => {
                    self.bluetooth.lock().unwrap().cancel_discovery();
                self.context.lock().unwrap().adapter_dbus.as_ref().unwrap().cancel_discovery();
            }
            _ => {
                println!("Invalid argument '{}'", args[0]);
            }
            }
        });
    }

    fn cmd_bond(&mut self, args: &Vec<String>) {
        if !self.context.lock().unwrap().adapter_ready {
            self.adapter_not_ready();
            return;
        }

        enforce_arg_len(args, 1, "bond <address>", || {
            let device = BluetoothDevice {
                address: String::from(&args[0]),
                name: String::from("Classic Device"),
            };
            self.bluetooth

            self.context
                .lock()
                .unwrap()
                .adapter_dbus
                .as_ref()
                .unwrap()
                .create_bond(device, BluetoothTransport::from_i32(0).unwrap());
        });
    }
@@ -305,6 +280,13 @@ impl CommandHandler {
    pub fn get_command_list(&self) -> Vec<String> {
        self.command_options.keys().map(|key| String::from(key)).collect::<Vec<String>>()
    }

    fn cmd_list_devices(&mut self, _args: &Vec<String>) {
        print_info!("Devices found in most recent discovery session:");
        for (key, val) in self.context.lock().unwrap().found_devices.iter() {
            print_info!("[{:18}] {}", key, val.name);
        }
    }
}

#[cfg(test)]
+247 −40
Original line number Diff line number Diff line
use bt_topshim::topstack;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use dbus::channel::MatchingReceiver;

use dbus::message::MatchRule;

use dbus::nonblock::SyncConnection;

use manager_service::iface_bluetooth_manager::{IBluetoothManager, IBluetoothManagerCallback};

use std::sync::{Arc, Mutex};
use dbus_crossroads::Crossroads;
use tokio::sync::mpsc;

use crate::command_handler::CommandHandler;
use crate::dbus_iface::{BluetoothDBus, BluetoothManagerDBus};
use crate::editor::AsyncEditor;

use dbus_crossroads::Crossroads;
use bt_topshim::btif::BtSspVariant;
use bt_topshim::topstack;
use btstack::bluetooth::{BluetoothDevice, IBluetooth, IBluetoothCallback};
use btstack::RPCProxy;
use manager_service::iface_bluetooth_manager::{IBluetoothManager, IBluetoothManagerCallback};

mod command_handler;
mod console;
@@ -22,17 +22,140 @@ mod dbus_arg;
mod dbus_iface;
mod editor;

/// Context structure for the client. Used to keep track details about the active adapter and its
/// state.
pub(crate) struct ClientContext {
    /// List of adapters and whether they are enabled.
    pub(crate) adapters: HashMap<i32, bool>,

    // TODO(abps) - Change once we have multi-adapter support.
    /// The default adapter is also the active adapter. Defaults to 0.
    pub(crate) default_adapter: i32,

    /// Current adapter is enabled?
    pub(crate) enabled: bool,

    /// Current adapter is ready to be used?
    pub(crate) adapter_ready: bool,

    /// Current adapter address if known.
    pub(crate) adapter_address: Option<String>,

    /// Is adapter discovering?
    pub(crate) discovering_state: bool,

    /// Devices found in current discovery session. List should be cleared when a new discovery
    /// session starts so that previous results don't pollute current search.
    pub(crate) found_devices: HashMap<String, BluetoothDevice>,

    /// Proxy for manager interface.
    pub(crate) manager_dbus: BluetoothManagerDBus,

    /// Proxy for adapter interface. Only exists when the default adapter is enabled.
    pub(crate) adapter_dbus: Option<BluetoothDBus>,

    /// Channel to send actions to take in the foreground
    fg: mpsc::Sender<ForegroundActions>,

    /// Internal DBus connection object.
    dbus_connection: Arc<SyncConnection>,

    /// Internal DBus crossroads object.
    dbus_crossroads: Arc<Mutex<Crossroads>>,
}

impl ClientContext {
    pub fn new(
        dbus_connection: Arc<SyncConnection>,
        dbus_crossroads: Arc<Mutex<Crossroads>>,
        tx: mpsc::Sender<ForegroundActions>,
    ) -> ClientContext {
        // Manager interface is always available but adapter interface requires
        // that the specific adapter is enabled.
        let manager_dbus =
            BluetoothManagerDBus::new(dbus_connection.clone(), dbus_crossroads.clone());

        ClientContext {
            adapters: HashMap::new(),
            default_adapter: 0,
            enabled: false,
            adapter_ready: false,
            adapter_address: None,
            discovering_state: false,
            found_devices: HashMap::new(),
            manager_dbus,
            adapter_dbus: None,
            fg: tx,
            dbus_connection,
            dbus_crossroads,
        }
    }

    // Creates adapter proxy, registers callbacks and initializes address.
    fn create_adapter_proxy(context: Arc<Mutex<ClientContext>>, idx: &i32) {
        let conn = context.lock().unwrap().dbus_connection.clone();
        let cr = context.lock().unwrap().dbus_crossroads.clone();

        let dbus = BluetoothDBus::new(conn, cr, *idx);
        context.lock().unwrap().adapter_dbus = Some(dbus);

        // Trigger callback registration in the foreground
        let fg = context.lock().unwrap().fg.clone();
        tokio::spawn(async move {
            let objpath = String::from("/org/chromium/bluetooth/client/bluetooth_callback");
            let _ = fg.send(ForegroundActions::RegisterAdapterCallback(objpath)).await;
        });
    }
}

/// Actions to take on the foreground loop. This allows us to queue actions in
/// callbacks that get run in the foreground context.
enum ForegroundActions {
    RegisterAdapterCallback(String), // Register callbacks with this objpath
    Readline(rustyline::Result<String>), // Readline result from rustyline
}

/// Callback context for manager interface callbacks.
struct BtManagerCallback {
    objpath: String,
    context: Arc<Mutex<ClientContext>>,
}

impl IBluetoothManagerCallback for BtManagerCallback {
    fn on_hci_device_changed(&self, hci_interface: i32, present: bool) {
        print_info!("hci{} present = {}", hci_interface, present);

        if present {
            self.context.lock().unwrap().adapters.entry(hci_interface).or_insert(false);
        } else {
            self.context.lock().unwrap().adapters.remove(&hci_interface);
        }
    }

    fn on_hci_enabled_changed(&self, hci_interface: i32, enabled: bool) {
        print_info!("hci{} enabled = {}", hci_interface, enabled);

        self.context
            .lock()
            .unwrap()
            .adapters
            .entry(hci_interface)
            .and_modify(|v| *v = enabled)
            .or_insert(enabled);

        // When the default adapter's state is updated, we need to modify a few more things.
        // Only do this if we're not repeating the previous state.
        let prev_enabled = self.context.lock().unwrap().enabled;
        let default_adapter = self.context.lock().unwrap().default_adapter;
        if hci_interface == default_adapter && prev_enabled != enabled {
            self.context.lock().unwrap().enabled = enabled;
            self.context.lock().unwrap().adapter_ready = false;
            if enabled {
                ClientContext::create_adapter_proxy(self.context.clone(), &hci_interface);
            } else {
                self.context.lock().unwrap().adapter_dbus = None;
            }
        }
    }
}

@@ -44,20 +167,66 @@ impl manager_service::RPCProxy for BtManagerCallback {
    }
}

struct API {
    bluetooth_manager: Arc<Mutex<Box<BluetoothManagerDBus>>>,
    bluetooth: Arc<Mutex<Box<BluetoothDBus>>>,
/// Callback container for adapter interface callbacks.
struct BtCallback {
    objpath: String,
    context: Arc<Mutex<ClientContext>>,
}

impl IBluetoothCallback for BtCallback {
    fn on_address_changed(&self, addr: String) {
        print_info!("Address changed to {}", &addr);
        self.context.lock().unwrap().adapter_address = Some(addr);
    }

    fn on_device_found(&self, remote_device: BluetoothDevice) {
        self.context
            .lock()
            .unwrap()
            .found_devices
            .entry(remote_device.address.clone())
            .or_insert(remote_device.clone());

        print_info!("Found device: {:?}", remote_device);
    }

// This creates the API implementations over D-Bus.
fn create_api_dbus(conn: Arc<SyncConnection>, cr: Arc<Mutex<Crossroads>>, idx: i32) -> API {
    let bluetooth_manager =
        Arc::new(Mutex::new(Box::new(BluetoothManagerDBus::new(conn.clone(), cr.clone()))));
    fn on_discovering_changed(&self, discovering: bool) {
        self.context.lock().unwrap().discovering_state = discovering;

    let bluetooth =
        Arc::new(Mutex::new(Box::new(BluetoothDBus::new(conn.clone(), cr.clone(), idx))));
        if discovering {
            self.context.lock().unwrap().found_devices.clear();
        }
        print_info!("Discovering: {}", discovering);
    }

    API { bluetooth_manager, bluetooth }
    fn on_ssp_request(
        &self,
        remote_device: BluetoothDevice,
        _cod: u32,
        variant: BtSspVariant,
        passkey: u32,
    ) {
        if variant == BtSspVariant::PasskeyNotification {
            print_info!(
                "device {}{} would like to pair, enter passkey on remote device: {:06}",
                remote_device.address.to_string(),
                if remote_device.name.len() > 0 {
                    format!(" ({})", remote_device.name)
                } else {
                    String::from("")
                },
                passkey
            );
        }
    }
}

impl RPCProxy for BtCallback {
    fn register_disconnect(&mut self, _f: Box<dyn Fn() + Send>) {}

    fn get_object_id(&self) -> String {
        self.objpath.clone()
    }
}

/// Runs a command line program that interacts with a Bluetooth stack.
@@ -92,17 +261,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
            }),
        );

        // We only need hci index 0 for now.
        // TODO: Have a mechanism (e.g. CLI argument or btclient command) to select the hci index.
        let api = create_api_dbus(conn, cr, 0);
        // Accept foreground actions with mpsc
        let (tx, rx) = mpsc::channel::<ForegroundActions>(10);

        // Create the context needed for handling commands
        let context = Arc::new(Mutex::new(ClientContext::new(conn, cr, tx.clone())));

        // TODO: Registering the callback should be done when btmanagerd is ready (detect with
        // ObjectManager).
        api.bluetooth_manager.lock().unwrap().register_callback(Box::new(BtManagerCallback {
        context.lock().unwrap().manager_dbus.register_callback(Box::new(BtManagerCallback {
            objpath: String::from("/org/chromium/bluetooth/client/bluetooth_manager_callback"),
            context: context.clone(),
        }));

        let mut handler = CommandHandler::new(api.bluetooth_manager.clone(), api.bluetooth.clone());
        let mut handler = CommandHandler::new(context.clone());

        let args: Vec<String> = std::env::args().collect();

@@ -110,21 +282,56 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
        if args.len() > 1 {
            handler.process_cmd_line(&args[1], &args[2..].to_vec());
        } else {
            start_interactive_shell(handler).await;
            start_interactive_shell(handler, tx, rx, context).await;
        }
        return Result::Ok(());
    })
}

async fn start_interactive_shell(mut handler: CommandHandler) {
    let editor = AsyncEditor::new(handler.get_command_list().clone());
async fn start_interactive_shell(
    mut handler: CommandHandler,
    tx: mpsc::Sender<ForegroundActions>,
    mut rx: mpsc::Receiver<ForegroundActions>,
    context: Arc<Mutex<ClientContext>>,
) {
    let command_list = handler.get_command_list().clone();

    // Async task to keep reading new lines from user
    tokio::spawn(async move {
        let editor = AsyncEditor::new(command_list);

        loop {
            let result = editor.readline().await;
        match result {
            Err(_err) => break,
            let _ = tx.send(ForegroundActions::Readline(result)).await;
        }
    });

    loop {
        let m = rx.recv().await;

        if m.is_none() {
            break;
        }

        match m.unwrap() {
            // Once adapter is ready, register callbacks, get the address and mark it as ready
            ForegroundActions::RegisterAdapterCallback(objpath) => {
                context
                    .lock()
                    .unwrap()
                    .adapter_dbus
                    .as_mut()
                    .unwrap()
                    .register_callback(Box::new(BtCallback { objpath, context: context.clone() }));
                context.lock().unwrap().adapter_ready = true;
            }
            ForegroundActions::Readline(result) => match result {
                Err(_err) => {
                    break;
                }
                Ok(line) => {
                let command_vec = line.split(" ").map(|s| String::from(s)).collect::<Vec<String>>();
                    let command_vec =
                        line.split(" ").map(|s| String::from(s)).collect::<Vec<String>>();
                    let cmd = &command_vec[0];
                    if cmd.eq("quit") {
                        break;
@@ -134,8 +341,8 @@ async fn start_interactive_shell(mut handler: CommandHandler) {
                        &command_vec[1..command_vec.len()].to_vec(),
                    );
                }
            },
        }
    }

    print_info!("Client exiting");
}