Move VM callback to vmclient

Instead of having clients directly register a callback with VS,
implement a Rust level callback interface in vmclient. This saves an
extra binder call on each notification, a bunch of boilerplate code,
and allows us to provide a slightly better interface (e.g. we can use
the Rust DeathReason enum, as elsewhere in vmclient, for instantly
better logging).

I also replaced all our usages of <some_interface>::binder::{...} with
direct access to binder::{...}. That makes it clearer what depends on
the interface itself and what is just generic binder code. I realise
this should be a separate change, but I only realised that after doing
bits of both.

Test: composd_cmd test-compile, observe logs (on both success & failure)
Test: atest -b (to make sure all our tests build)
Test: Presubmits
Change-Id: Iceda8d7b8f8008f9d7a2c51106c2794f09bb378e
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index b3bb635..129e6c3 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -64,6 +64,32 @@
     _death_recipient: DeathRecipient,
 }
 
+/// A trait to be implemented by clients to handle notification of significant changes to the VM
+/// state. Default implementations of all functions are provided so clients only need to handle the
+/// notifications they are interested in.
+#[allow(unused_variables)]
+pub trait VmCallback {
+    /// Called when the payload has been started within the VM. If present, `stream` is connected
+    /// to the stdin/stdout of the payload.
+    fn on_payload_started(&self, cid: i32, stream: Option<&File>) {}
+
+    /// Callend when the payload has notified Virtualization Service that it is ready to serve
+    /// clients.
+    fn on_payload_ready(&self, cid: i32) {}
+
+    /// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
+    /// process.
+    fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
+
+    /// Called when an error has occurred in the VM. The `error_code` and `message` may give
+    /// further details.
+    fn on_error(&self, cid: i32, error_code: i32, message: &str) {}
+
+    /// Called when the VM has exited, all resources have been freed, and any logs have been
+    /// written. `death_reason` gives an indication why the VM exited.
+    fn on_died(&self, cid: i32, death_reason: DeathReason) {}
+}
+
 impl VmInstance {
     /// Creates (but doesn't start) a new VM with the given configuration.
     pub fn create(
@@ -71,6 +97,7 @@
         config: &VirtualMachineConfig,
         console: Option<File>,
         log: Option<File>,
+        callback: Option<Box<dyn VmCallback + Send + Sync>>,
     ) -> BinderResult<Self> {
         let console = console.map(ParcelFileDescriptor::new);
         let log = log.map(ParcelFileDescriptor::new);
@@ -82,7 +109,7 @@
         // Register callback before starting VM, in case it dies immediately.
         let state = Arc::new(Monitor::new(VmState::default()));
         let callback = BnVirtualMachineCallback::new_binder(
-            VirtualMachineCallback { state: state.clone() },
+            VirtualMachineCallback { state: state.clone(), client_callback: callback },
             BinderFeatures::default(),
         );
         vm.registerCallback(&callback)?;
@@ -219,9 +246,21 @@
     }
 }
 
-#[derive(Debug)]
 struct VirtualMachineCallback {
     state: Arc<Monitor<VmState>>,
+    client_callback: Option<Box<dyn VmCallback + Send + Sync>>,
+}
+
+impl Debug for VirtualMachineCallback {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        fmt.debug_struct("VirtualMachineCallback")
+            .field("state", &self.state)
+            .field(
+                "client_callback",
+                &if self.client_callback.is_some() { "Some(...)" } else { "None" },
+            )
+            .finish()
+    }
 }
 
 impl Interface for VirtualMachineCallback {}
@@ -229,25 +268,37 @@
 impl IVirtualMachineCallback for VirtualMachineCallback {
     fn onPayloadStarted(
         &self,
-        _cid: i32,
-        _stream: Option<&ParcelFileDescriptor>,
+        cid: i32,
+        stream: Option<&ParcelFileDescriptor>,
     ) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::STARTED);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_payload_started(cid, stream.map(ParcelFileDescriptor::as_ref));
+        }
         Ok(())
     }
 
-    fn onPayloadReady(&self, _cid: i32) -> BinderResult<()> {
+    fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::READY);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_payload_ready(cid);
+        }
         Ok(())
     }
 
-    fn onPayloadFinished(&self, _cid: i32, _exit_code: i32) -> BinderResult<()> {
+    fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::FINISHED);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_payload_finished(cid, exit_code);
+        }
         Ok(())
     }
 
-    fn onError(&self, _cid: i32, _error_code: i32, _message: &str) -> BinderResult<()> {
+    fn onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::FINISHED);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_error(cid, error_code, message);
+        }
         Ok(())
     }
 
@@ -257,8 +308,12 @@
         Ok(())
     }
 
-    fn onDied(&self, _cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
-        self.state.notify_death(reason.into());
+    fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
+        let reason = reason.into();
+        self.state.notify_death(reason);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_died(cid, reason);
+        }
         Ok(())
     }
 }