Set up timeout for graceful shutdown

Set up 3 seconds as timeout for graceful shutdown.
After the timeout, stop the vm luancher service.

Bug: 397138554
Test: click the close button in notification
Test: resize works well
Change-Id: Ib4bf5a92cdd9d9876a01ceb85545c9d645e539c2
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index d43a8d1..84168e5 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -463,15 +463,32 @@
 
     @WorkerThread
     private fun doShutdown(resultReceiver: ResultReceiver?) {
-        stopForeground(STOP_FOREGROUND_REMOVE)
+        runner?.exitStatus?.thenAcceptAsync { success: Boolean ->
+            resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
+            stopSelf()
+        }
         if (debianService != null && debianService!!.shutdownDebian()) {
             // During shutdown, change the notification content to indicate that it's closing
             val notification = createNotificationForTerminalClose()
             getSystemService<NotificationManager?>(NotificationManager::class.java)
                 .notify(this.hashCode(), notification)
-            runner?.exitStatus?.thenAcceptAsync { success: Boolean ->
-                resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
-                stopSelf()
+
+            runner?.also { r ->
+                // For the case that shutdown from the guest agent fails.
+                // When timeout is set, the original CompletableFuture's every `thenAcceptAsync` is
+                // canceled as well. So add empty `thenAcceptAsync` to avoid interference.
+                r.exitStatus
+                    .thenAcceptAsync {}
+                    .orTimeout(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+                    .exceptionally {
+                        Log.e(
+                            TAG,
+                            "Stop the service directly because the VM instance isn't stopped with " +
+                                "graceful shutdown",
+                        )
+                        r.vm.stop()
+                        null
+                    }
             }
             runner = null
         } else {
@@ -498,6 +515,7 @@
         stopDebianServer()
         bgThreads.shutdownNow()
         mainWorkerThread.shutdown()
+        stopForeground(STOP_FOREGROUND_REMOVE)
         super.onDestroy()
     }
 
@@ -517,6 +535,8 @@
         private const val KEY_TERMINAL_IPADDRESS = "address"
         private const val KEY_TERMINAL_PORT = "port"
 
+        private const val SHUTDOWN_TIMEOUT_SECONDS = 3L
+
         private const val GUEST_SPARSE_DISK_SIZE_PERCENTAGE = 95
         private const val EXPECTED_PHYSICAL_SIZE_PERCENTAGE_FOR_NON_SPARSE = 90