AdapterStateChange and HciDeviceChange callback

 * Move observers to main.
 * Add dbus callback util.
 * Use UpstartInvoker instead of NativeSubprocess.
 * Fix multiple issues.

Bug: 189501676
Tag: #floss
Test: cargo test
Test: run btmanagerd and try sending dbus commands
Change-Id: Ie0a678a6f5622221f5d12bb2b8b82a72971ab949
diff --git a/system/gd/rust/linux/mgmt/Cargo.toml b/system/gd/rust/linux/mgmt/Cargo.toml
index 042d808..e182273 100644
--- a/system/gd/rust/linux/mgmt/Cargo.toml
+++ b/system/gd/rust/linux/mgmt/Cargo.toml
@@ -16,6 +16,7 @@
 dbus-crossroads = "0.3.0"
 inotify = "*"
 nix = "*"
+regex = "1.5"
 serde_json = "1.0"
 tokio = { version = "1.0", features = ["fs", "macros", "rt-multi-thread", "sync"] }
 
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs
index e57c165..130e974 100644
--- a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs
@@ -96,7 +96,11 @@
     }
 }
 
-pub fn list_hci_devices() -> Vec<String> {
+pub fn list_hci_devices() -> Vec<i32> {
+    hci_devices_string_to_int(list_hci_devices_string())
+}
+
+fn list_hci_devices_string() -> Vec<String> {
     match std::fs::read_dir(HCI_DEVICES_DIR) {
         Ok(entries) => entries
             .map(|e| e.unwrap().path().file_name().unwrap().to_str().unwrap().to_string())
@@ -105,7 +109,7 @@
     }
 }
 
-pub fn hci_devices_string_to_int(devices: Vec<String>) -> Vec<i32> {
+fn hci_devices_string_to_int(devices: Vec<String>) -> Vec<i32> {
     devices
         .into_iter()
         .filter_map(|e| if e.starts_with("hci") { e[3..].parse::<i32>().ok() } else { None })
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_callback_util.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_callback_util.rs
new file mode 100644
index 0000000..7ab45db
--- /dev/null
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_callback_util.rs
@@ -0,0 +1,71 @@
+use dbus::nonblock::{Proxy, SyncConnection};
+use std::sync::Arc;
+use std::time::Duration;
+use tokio::sync::Mutex;
+
+#[derive(Clone)]
+pub struct DbusCallbackUtil {
+    dbus_connection: Arc<SyncConnection>,
+    state_change_observers: Arc<Mutex<Vec<String>>>,
+    hci_device_change_observer: Arc<Mutex<Vec<String>>>,
+}
+
+impl DbusCallbackUtil {
+    pub fn new(
+        dbus_connection: Arc<SyncConnection>,
+        state_change_observers: Arc<Mutex<Vec<String>>>,
+        hci_device_change_observer: Arc<Mutex<Vec<String>>>,
+    ) -> Self {
+        DbusCallbackUtil {
+            dbus_connection: dbus_connection,
+            state_change_observers: state_change_observers,
+            hci_device_change_observer: hci_device_change_observer,
+        }
+    }
+
+    pub async fn send_hci_device_change_callback(
+        &self,
+        hci_device: i32,
+        present: bool,
+    ) -> Result<(), Box<dyn std::error::Error>> {
+        for path in &*self.hci_device_change_observer.lock().await {
+            let proxy = Proxy::new(
+                "org.chromium.bluetooth.Manager",
+                path,
+                Duration::from_secs(2),
+                self.dbus_connection.clone(),
+            );
+            proxy
+                .method_call(
+                    "org.chromium.bluetooth.Manager",
+                    "HciDeviceChangeCallback",
+                    (hci_device, present),
+                )
+                .await?;
+        }
+        Ok(())
+    }
+
+    pub async fn send_adapter_state_change_callback(
+        &self,
+        hci_device: i32,
+        state: i32,
+    ) -> Result<(), Box<dyn std::error::Error>> {
+        for path in &*self.state_change_observers.lock().await {
+            let proxy = Proxy::new(
+                "org.chromium.bluetooth.Manager",
+                path,
+                Duration::from_secs(2),
+                self.dbus_connection.clone(),
+            );
+            proxy
+                .method_call(
+                    "org.chromium.bluetooth.Manager",
+                    "AdapterStateChangeCallback",
+                    (hci_device, state),
+                )
+                .await?;
+        }
+        Ok(())
+    }
+}
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs
index 27ac7b1..43ab8ca 100644
--- a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs
@@ -1,13 +1,16 @@
 mod config_util;
+mod dbus_callback_util;
 mod state_machine;
 
 use dbus::channel::MatchingReceiver;
 use dbus::message::MatchRule;
+use dbus::nonblock::SyncConnection;
 use dbus_crossroads::Crossroads;
 use dbus_tokio::connection;
 use std::process::Command;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
+use tokio::sync::Mutex;
 
 const BLUEZ_INIT_TARGET: &str = "bluetoothd";
 
@@ -15,6 +18,9 @@
 struct ManagerContext {
     proxy: state_machine::StateMachineProxy,
     floss_enabled: Arc<AtomicBool>,
+    dbus_connection: Arc<SyncConnection>,
+    state_change_observer: Arc<Mutex<Vec<String>>>,
+    hci_device_change_observer: Arc<Mutex<Vec<String>>>,
 }
 
 #[tokio::main]
@@ -22,15 +28,26 @@
     // Initialize config util
     config_util::fix_config_file_format();
 
+    // Connect to the D-Bus system bus (this is blocking, unfortunately).
+    let (resource, conn) = connection::new_system_sync()?;
+
     let context = state_machine::start_new_state_machine_context();
     let proxy = context.get_proxy();
+    let state_change_observer = Arc::new(Mutex::new(Vec::new()));
+    let hci_device_change_observer = Arc::new(Mutex::new(Vec::new()));
     let manager_context = ManagerContext {
         proxy: proxy,
         floss_enabled: Arc::new(AtomicBool::new(config_util::is_floss_enabled())),
+        dbus_connection: conn.clone(),
+        state_change_observer: state_change_observer.clone(),
+        hci_device_change_observer: hci_device_change_observer.clone(),
     };
 
-    // Connect to the D-Bus system bus (this is blocking, unfortunately).
-    let (resource, c) = connection::new_system_sync()?;
+    let dbus_callback_util = dbus_callback_util::DbusCallbackUtil::new(
+        conn.clone(),
+        state_change_observer.clone(),
+        hci_device_change_observer.clone(),
+    );
 
     // The resource is a task that should be spawned onto a tokio compatible
     // reactor ASAP. If the resource ever finishes, you lost connection to D-Bus.
@@ -40,7 +57,7 @@
     });
 
     // Let's request a name on the bus, so that clients can find us.
-    c.request_name("org.chromium.bluetooth.Manager", false, true, false).await?;
+    conn.request_name("org.chromium.bluetooth.Manager", false, true, false).await?;
 
     // Create a new crossroads instance.
     // The instance is configured so that introspection and properties interfaces
@@ -49,7 +66,7 @@
 
     // Enable async support for the crossroads instance.
     cr.set_async_support(Some((
-        c.clone(),
+        conn.clone(),
         Box::new(|x| {
             tokio::spawn(x);
         }),
@@ -101,30 +118,20 @@
             let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
             async move {
                 let state = proxy.get_state().await;
-                let result = match state {
-                    state_machine::State::Off => 0,
-                    state_machine::State::TurningOn => 1,
-                    state_machine::State::On => 2,
-                    state_machine::State::TurningOff => 3,
-                };
+                let result = state_machine::state_to_i32(state);
                 ctx.reply(Ok((result,)))
             }
         });
+        // Register AdapterStateChangeCallback(int hci_device, int state) on specified object_path
         b.method_with_cr_async(
             "RegisterStateChangeObserver",
             ("object_path",),
             (),
             |mut ctx, cr, (object_path,): (String,)| {
-                let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
+                let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
                 async move {
-                    let result = proxy.register_state_change_observer(object_path.clone()).await;
-                    match result {
-                        Ok(()) => ctx.reply(Ok(())),
-                        Err(_) => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
-                            "cannot register {}",
-                            object_path
-                        )))),
-                    }
+                    manager_context.state_change_observer.lock().await.push(object_path.clone());
+                    ctx.reply(Ok(()))
                 }
             },
         );
@@ -133,12 +140,15 @@
             ("object_path",),
             (),
             |mut ctx, cr, (object_path,): (String,)| {
-                let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
+                let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
                 async move {
-                    let result = proxy.unregister_state_change_observer(object_path.clone()).await;
-                    match result {
-                        Ok(()) => ctx.reply(Ok(())),
-                        Err(_) => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
+                    let mut observers = manager_context.state_change_observer.lock().await;
+                    match observers.iter().position(|x| *x == object_path) {
+                        Some(index) => {
+                            observers.remove(index);
+                            ctx.reply(Ok(()))
+                        }
+                        _ => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
                             "cannot unregister {}",
                             object_path
                         )))),
@@ -176,9 +186,13 @@
                             .args(&["stop", BLUEZ_INIT_TARGET])
                             .output()
                             .expect("failed to stop bluetoothd");
-                        let _ = proxy.start_bluetooth(0).await;
+                        // TODO: Implement multi-hci case
+                        let default_device = config_util::list_hci_devices()[0];
+                        let _ = proxy.start_bluetooth(default_device).await;
                     } else if prev != enabled {
-                        let _ = proxy.stop_bluetooth(0).await;
+                        // TODO: Implement multi-hci case
+                        let default_device = config_util::list_hci_devices()[0];
+                        let _ = proxy.stop_bluetooth(default_device).await;
                         Command::new("initctl")
                             .args(&["start", BLUEZ_INIT_TARGET])
                             .output()
@@ -188,6 +202,44 @@
                 }
             },
         );
+        b.method_with_cr_async("ListHciDevices", (), ("devices",), |mut ctx, _cr, ()| {
+            let devices = config_util::list_hci_devices();
+            async move { ctx.reply(Ok((devices,))) }
+        });
+        // Register AdapterStateChangeCallback(int hci_device, int state) on specified object_path
+        b.method_with_cr_async(
+            "RegisterHciDeviceChangeObserver",
+            ("object_path",),
+            (),
+            |mut ctx, cr, (object_path,): (String,)| {
+                let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
+                async move {
+                    manager_context.hci_device_change_observer.lock().await.push(object_path);
+                    ctx.reply(Ok(()))
+                }
+            },
+        );
+        b.method_with_cr_async(
+            "UnregisterHciDeviceChangeObserver",
+            ("object_path",),
+            (),
+            |mut ctx, cr, (object_path,): (String,)| {
+                let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
+                async move {
+                    let mut observers = manager_context.hci_device_change_observer.lock().await;
+                    match observers.iter().position(|x| *x == object_path) {
+                        Some(index) => {
+                            observers.remove(index);
+                            ctx.reply(Ok(()))
+                        }
+                        _ => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
+                            "cannot unregister {}",
+                            object_path
+                        )))),
+                    }
+                }
+            },
+        );
     });
 
     // Let's add the "/org/chromium/bluetooth/Manager" path, which implements the org.chromium.bluetooth.Manager interface,
@@ -195,7 +247,7 @@
     cr.insert("/org/chromium/bluetooth/Manager", &[iface_token], manager_context);
 
     // We add the Crossroads instance to the connection so that incoming method calls will be handled.
-    c.start_receive(
+    conn.start_receive(
         MatchRule::new_method_call(),
         Box::new(move |msg, conn| {
             cr.handle_message(msg, conn).unwrap();
@@ -204,7 +256,7 @@
     );
 
     tokio::spawn(async move {
-        state_machine::mainloop(context).await;
+        state_machine::mainloop(context, dbus_callback_util).await;
     });
 
     std::future::pending::<()>().await;
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs
index 5a67143..bec83b8 100644
--- a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs
@@ -1,6 +1,7 @@
 use bt_common::time::Alarm;
 use nix::sys::signal::{self, Signal};
 use nix::unistd::Pid;
+use regex::Regex;
 use std::process::{Child, Command, Stdio};
 use std::sync::Arc;
 use std::time::Duration;
@@ -8,12 +9,26 @@
 use tokio::sync::mpsc::error::SendError;
 use tokio::sync::{mpsc, Mutex};
 
+// Directory for Bluetooth pid file
+pub const PID_DIR: &str = "/var/run/bluetooth";
+
 #[derive(Debug, PartialEq, Copy, Clone)]
+#[repr(i32)]
 pub enum State {
-    Off,        // Bluetooth is not running
-    TurningOn,  // We are not notified that the Bluetooth is running
-    On,         // Bluetooth is running
-    TurningOff, // We are not notified that the Bluetooth is stopped
+    Off = 0,        // Bluetooth is not running
+    TurningOn = 1,  // We are not notified that the Bluetooth is running
+    On = 2,         // Bluetooth is running
+    TurningOff = 3, // We are not notified that the Bluetooth is stopped
+}
+
+impl From<State> for i32 {
+    fn from(item: State) -> i32 {
+        item as i32
+    }
+}
+
+pub fn state_to_i32(state: State) -> i32 {
+    i32::from(state)
 }
 
 #[derive(Debug)]
@@ -40,23 +55,18 @@
     }
 
     pub fn get_proxy(&self) -> StateMachineProxy {
-        StateMachineProxy {
-            tx: self.tx.clone(),
-            state: self.state_machine.state.clone(),
-            state_change_observers: self.state_machine.state_change_observers.clone(),
-        }
+        StateMachineProxy { tx: self.tx.clone(), state: self.state_machine.state.clone() }
     }
 }
 
-pub fn start_new_state_machine_context() -> StateMachineContext<NativeSubprocess> {
-    StateMachineContext::new(ManagerStateMachine::new_native())
+pub fn start_new_state_machine_context() -> StateMachineContext<UpstartInvoker> {
+    StateMachineContext::new(ManagerStateMachine::new_upstart())
 }
 
 #[derive(Clone)]
 pub struct StateMachineProxy {
     tx: mpsc::Sender<StateMachineActions>,
     state: Arc<Mutex<State>>,
-    state_change_observers: Arc<Mutex<Vec<String>>>,
 }
 
 impl StateMachineProxy {
@@ -77,34 +87,21 @@
     pub async fn get_state(&self) -> State {
         *self.state.lock().await
     }
-
-    pub async fn register_state_change_observer(
-        &self,
-        object_path: String,
-    ) -> Result<(), SendError<StateMachineActions>> {
-        self.state_change_observers.lock().await.push(object_path);
-        Ok(())
-    }
-
-    pub async fn unregister_state_change_observer(
-        &self,
-        object_path: String,
-    ) -> Result<(), SendError<StateMachineActions>> {
-        let mut observers = self.state_change_observers.lock().await;
-        let index = observers.iter().position(|x| *x == object_path).unwrap();
-        observers.remove(index);
-        Ok(())
-    }
 }
 
 fn pid_inotify_async_fd() -> AsyncFd<inotify::Inotify> {
     let mut pid_detector = inotify::Inotify::init().expect("cannot use inotify");
     pid_detector
-        .add_watch("/var/run", inotify::WatchMask::CREATE | inotify::WatchMask::DELETE)
+        .add_watch(PID_DIR, inotify::WatchMask::CREATE | inotify::WatchMask::DELETE)
         .expect("failed to add watch");
     AsyncFd::new(pid_detector).expect("failed to add async fd")
 }
 
+fn get_hci_interface_from_pid_file_name(path: &str) -> Option<i32> {
+    let re = Regex::new(r"bluetooth([0-9]+).pid").unwrap();
+    re.captures(path)?.get(1)?.as_str().parse().ok()
+}
+
 fn hci_devices_inotify_async_fd() -> AsyncFd<inotify::Inotify> {
     let mut detector = inotify::Inotify::init().expect("cannot use inotify");
     detector
@@ -116,14 +113,21 @@
     AsyncFd::new(detector).expect("failed to add async fd")
 }
 
-pub async fn mainloop<PM>(mut context: StateMachineContext<PM>)
-where
+fn get_hci_interface_from_device(path: &str) -> Option<i32> {
+    let re = Regex::new(r"hci([0-9]+)").unwrap();
+    re.captures(path)?.get(1)?.as_str().parse().ok()
+}
+
+pub async fn mainloop<PM>(
+    mut context: StateMachineContext<PM>,
+    dbus_callback_util: crate::dbus_callback_util::DbusCallbackUtil,
+) where
     PM: ProcessManager + Send,
 {
     let mut command_timeout = Alarm::new();
     let command_timeout_duration = Duration::from_secs(2);
     let mut pid_async_fd = pid_inotify_async_fd();
-    let mut config_async_fd = hci_devices_inotify_async_fd();
+    let mut hci_devices_async_fd = hci_devices_inotify_async_fd();
     loop {
         tokio::select! {
             Some(action) = context.rx.recv() => {
@@ -172,51 +176,66 @@
                 let mut buffer: [u8; 1024] = [0; 1024];
                 match fd_ready.try_io(|inner| inner.get_mut().read_events(&mut buffer)) {
                     Ok(Ok(events)) => {
+                        println!("got some events");
                         for event in events {
                             match (event.mask, event.name) {
                                 (inotify::EventMask::CREATE, Some(oss)) => {
-                                    let file_name = oss.to_str().unwrap_or("invalid_file");
-                                    if file_name.contains("/var/run/bluetooth.pid") {
-                                        let read_result = tokio::fs::read("/var/run/bluetooth.pid").await;
-                                        match read_result {
-                                            Ok(v) => {
-                                                let file_string = String::from_utf8(v).expect("invalid pid file");
-                                                let mut iter = file_string.split_ascii_whitespace();
-                                                let pid = match iter.next() {
-                                                    Some(s) => s.parse::<i32>().unwrap(),
-                                                    None => 0
-                                                };
-                                                let hci = match iter.next() {
-                                                    Some(s) => s.parse::<i32>().unwrap(),
-                                                    None => 0
-                                                };
-                                                let _ = context.tx.send(StateMachineActions::BluetoothStarted(pid, hci)).await;
-                                            },
-                                            Err(e) => println!("{}", e)
-                                        }
+                                    let path = std::path::Path::new(PID_DIR).join(oss);
+                                    let file_name = oss.to_str().unwrap_or("invalid file");
+                                    match (get_hci_interface_from_pid_file_name(file_name), tokio::fs::read(path).await.ok()) {
+                                        (Some(hci), Some(s)) => {
+                                            let pid = String::from_utf8(s).expect("invalid pid file").parse::<i32>().unwrap_or(0);
+                                            let _ = context.tx.send(StateMachineActions::BluetoothStarted(pid, hci)).await;
+                                        },
+                                        (hci, s) => println!("invalid file hci={:?} pid_file={:?}", hci, s),
                                     }
                                 },
                                 (inotify::EventMask::DELETE, Some(oss)) => {
-                                    let file_name = oss.to_str().unwrap_or("invalid_file");
-                                    if file_name.contains("bluetooth.pid") {
-                                        let _ = context.tx.send(StateMachineActions::BluetoothStopped()).await;
-                                      }
-                                  },
+                                    let file_name = oss.to_str().unwrap_or("invalid file");
+                                    match get_hci_interface_from_pid_file_name(file_name) {
+                                        Some(hci) => {
+                                            let _ = context.tx.send(StateMachineActions::BluetoothStopped()).await;
+                                        },
+                                        _ => (),
+                                    }
+                                },
                                 _ => println!("Ignored event {:?}", event.mask)
                             }
                         }
-                    }
+                    },
                     Err(_) | Ok(Err(_)) => panic!("why can't we read while the asyncfd is ready?"),
                 }
                 fd_ready.clear_ready();
                 drop(fd_ready);
             },
-            r = config_async_fd.readable_mut() => {
+            r = hci_devices_async_fd.readable_mut() => {
                 let mut fd_ready = r.unwrap();
                 let mut buffer: [u8; 1024] = [0; 1024];
                 match fd_ready.try_io(|inner| inner.get_mut().read_events(&mut buffer)) {
-                    // TODO: Placeholder
-                    _ => (),
+                    Ok(Ok(events)) => {
+                        for event in events {
+                            match (event.mask, event.name) {
+                                (inotify::EventMask::CREATE, Some(oss)) => {
+                                    match get_hci_interface_from_device(oss.to_str().unwrap_or("invalid hci device")) {
+                                        Some(hci) => {
+                                            dbus_callback_util.send_hci_device_change_callback(hci, true).await;
+                                        },
+                                        _ => (),
+                                    }
+                                },
+                                (inotify::EventMask::DELETE, Some(oss)) => {
+                                    match get_hci_interface_from_device(oss.to_str().unwrap_or("invalid hci device")) {
+                                        Some(hci) => {
+                                            dbus_callback_util.send_hci_device_change_callback(hci, false).await;
+                                        },
+                                        _ => (),
+                                    }
+                                },
+                                _ => println!("Ignored event {:?}", event.mask)
+                            }
+                        }
+                    },
+                    Err(_) | Ok(Err(_)) => panic!("why can't we read while the asyncfd is ready?"),
                 }
                 fd_ready.clear_ready();
                 drop(fd_ready);
@@ -243,7 +262,7 @@
 
 impl ProcessManager for NativeSubprocess {
     fn start(&mut self, hci_interface: String) {
-        let new_process = Command::new("/usr/bin/bluetoothd")
+        let new_process = Command::new("/usr/bin/btadapterd")
             .arg(format!("HCI={}", hci_interface))
             .stdout(Stdio::piped())
             .spawn()
@@ -275,18 +294,14 @@
 impl ProcessManager for UpstartInvoker {
     fn start(&mut self, hci_interface: String) {
         Command::new("initctl")
-            .arg("start")
-            .arg("bluetooth")
-            .arg(format!("HCI={}", hci_interface))
+            .args(&["start", "btadapterd", format!("HCI={}", hci_interface).as_str()])
             .output()
             .expect("failed to start bluetooth");
     }
 
     fn stop(&mut self, hci_interface: String) {
         Command::new("initctl")
-            .arg("stop")
-            .arg("bluetooth")
-            .arg(format!("HCI={}", hci_interface))
+            .args(&["stop", "btadapterd", format!("HCI={}", hci_interface).as_str()])
             .output()
             .expect("failed to stop bluetooth");
     }
@@ -295,7 +310,6 @@
 struct ManagerStateMachine<PM> {
     state: Arc<Mutex<State>>,
     process_manager: PM,
-    state_change_observers: Arc<Mutex<Vec<String>>>,
     hci_interface: i32,
     bluetooth_pid: i32,
 }
@@ -306,6 +320,12 @@
     }
 }
 
+impl ManagerStateMachine<UpstartInvoker> {
+    pub fn new_upstart() -> ManagerStateMachine<UpstartInvoker> {
+        ManagerStateMachine::new(UpstartInvoker::new())
+    }
+}
+
 #[derive(Debug, PartialEq)]
 enum StateMachineTimeoutActions {
     RetryStart,
@@ -321,7 +341,6 @@
         ManagerStateMachine {
             state: Arc::new(Mutex::new(State::Off)),
             process_manager: process_manager,
-            state_change_observers: Arc::new(Mutex::new(Vec::new())),
             hci_interface: 0,
             bluetooth_pid: 0,
         }
@@ -334,7 +353,7 @@
             State::Off => {
                 *state = State::TurningOn;
                 self.hci_interface = hci_interface;
-                self.process_manager.start(format!("hci{}", hci_interface));
+                self.process_manager.start(format!("{}", hci_interface));
                 true
             }
             // Otherwise no op
@@ -397,7 +416,7 @@
             State::On => {
                 println!("Bluetooth stopped unexpectedly, try restarting");
                 *state = State::TurningOn;
-                self.process_manager.start(format!("hci{}", self.hci_interface));
+                self.process_manager.start(format!("{}", self.hci_interface));
                 false
             }
             State::TurningOn | State::Off => {
@@ -415,7 +434,7 @@
             State::TurningOn => {
                 println!("Restarting bluetooth");
                 *state = State::TurningOn;
-                self.process_manager.start(format! {"hci{}", self.hci_interface});
+                self.process_manager.start(format! {"{}", self.hci_interface});
                 StateMachineTimeoutActions::RetryStart
             }
             State::TurningOff => {
@@ -601,4 +620,26 @@
         state_machine.action_on_bluetooth_started(0, 0);
         assert_eq!(*state_machine.state.try_lock().unwrap(), State::On);
     }
+
+    #[test]
+    fn path_to_hci_interface() {
+        assert_eq!(
+            get_hci_interface_from_pid_file_name("/var/run/bluetooth/bluetooth0.pid"),
+            Some(0)
+        );
+        assert_eq!(
+            get_hci_interface_from_pid_file_name("/var/run/bluetooth/bluetooth1.pid"),
+            Some(1)
+        );
+        assert_eq!(
+            get_hci_interface_from_pid_file_name("/var/run/bluetooth/bluetooth10.pid"),
+            Some(10)
+        );
+        assert_eq!(get_hci_interface_from_pid_file_name("/var/run/bluetooth/garbage"), None);
+
+        assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/hci0"), Some(0));
+        assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/hci1"), Some(1));
+        assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/hci10"), Some(10));
+        assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/eth0"), None);
+    }
 }