Suspend the VM when it's not visible on the screen

... and resume it when it becomes visible again

Bug: 348552343
Bug: 348380730
Test: play a youtube video in a vm. go back to Android. take some time
and return to the vm. the video resumes at the point it was.
Change-Id: I517c8cf242866df99fe68396417f30bec28ff7dc

Change-Id: I462590eb1cc67b82ef0ef2283e4647f9163af4d0
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index bca36a4..5febed2 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -1471,6 +1471,38 @@
         }
     }
 
+    /** @hide */
+    public void suspend() throws VirtualMachineException {
+        synchronized (mLock) {
+            if (mVirtualMachine == null) {
+                throw new VirtualMachineException("VM is not running");
+            }
+            try {
+                mVirtualMachine.suspend();
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            } catch (ServiceSpecificException e) {
+                throw new VirtualMachineException(e);
+            }
+        }
+    }
+
+    /** @hide */
+    public void resume() throws VirtualMachineException {
+        synchronized (mLock) {
+            if (mVirtualMachine == null) {
+                throw new VirtualMachineException("VM is not running");
+            }
+            try {
+                mVirtualMachine.resume();
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            } catch (ServiceSpecificException e) {
+                throw new VirtualMachineException(e);
+            }
+        }
+    }
+
     /**
      * Stops this virtual machine, if it is running.
      *
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 9df376a..575af6b 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -1244,6 +1244,22 @@
     fn setHostConsoleName(&self, ptsname: &str) -> binder::Result<()> {
         self.instance.vm_context.global_context.setHostConsoleName(ptsname)
     }
+
+    fn suspend(&self) -> binder::Result<()> {
+        self.instance
+            .suspend()
+            .with_context(|| format!("Error suspending VM with CID {}", self.instance.cid))
+            .with_log()
+            .or_service_specific_exception(-1)
+    }
+
+    fn resume(&self) -> binder::Result<()> {
+        self.instance
+            .resume()
+            .with_context(|| format!("Error resuming VM with CID {}", self.instance.cid))
+            .with_log()
+            .or_service_specific_exception(-1)
+    }
 }
 
 impl Drop for VirtualMachine {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 47ef91a..102ac51 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -682,6 +682,28 @@
         conn.notify_completion()?;
         Ok(())
     }
+
+    /// Suspends the VM
+    pub fn suspend(&self) -> Result<(), Error> {
+        match vm_control::client::handle_request(
+            &VmRequest::SuspendVcpus,
+            &self.crosvm_control_socket_path,
+        ) {
+            Ok(VmResponse::Ok) => Ok(()),
+            e => bail!("Failed to suspend VM: {e:?}"),
+        }
+    }
+
+    /// Resumes the suspended VM
+    pub fn resume(&self) -> Result<(), Error> {
+        match vm_control::client::handle_request(
+            &VmRequest::ResumeVcpus,
+            &self.crosvm_control_socket_path,
+        ) {
+            Ok(VmResponse::Ok) => Ok(()),
+            e => bail!("Failed to resume: {e:?}"),
+        }
+    }
 }
 
 impl Rss {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
index d4001c8..9d1d5d5 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
@@ -50,4 +50,10 @@
 
     /** Set the name of the peer end (ptsname) of the host console. */
     void setHostConsoleName(in @utf8InCpp String pathname);
+
+    /** Suspends the VM. */
+    void suspend();
+
+    /** Resumes the suspended VM. */
+    void resume();
 }
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index c2f218a..054be51 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -416,6 +416,30 @@
     }
 
     @Override
+    protected void onStop() {
+        super.onStop();
+        if (mVirtualMachine != null) {
+            try {
+                mVirtualMachine.suspend();
+            } catch (VirtualMachineException e) {
+                Log.e(TAG, "Failed to suspend VM" + e);
+            }
+        }
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        if (mVirtualMachine != null) {
+            try {
+                mVirtualMachine.resume();
+            } catch (VirtualMachineException e) {
+                Log.e(TAG, "Failed to resume VM" + e);
+            }
+        }
+    }
+
+    @Override
     protected void onDestroy() {
         super.onDestroy();
         if (mExecutorService != null) {