Merge "Notify VS of an instance being removed" into main
diff --git a/authfs/tests/common/src/open_then_run.rs b/authfs/tests/common/src/open_then_run.rs
index a976784..e5e33eb 100644
--- a/authfs/tests/common/src/open_then_run.rs
+++ b/authfs/tests/common/src/open_then_run.rs
@@ -118,6 +118,7 @@
                 .read(true)
                 .write(true)
                 .create(true)
+                .truncate(true)
                 .open(path)
                 .with_context(|| format!("Open {} read-write", path))?,
         ))
diff --git a/libs/android_display_backend/Android.bp b/libs/android_display_backend/Android.bp
new file mode 100644
index 0000000..6ad5fab
--- /dev/null
+++ b/libs/android_display_backend/Android.bp
@@ -0,0 +1,55 @@
+aidl_interface {
+    name: "libcrosvm_android_display_service",
+    srcs: [
+        "aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl",
+    ],
+    include_dirs: [
+        "frameworks/native/aidl/gui",
+    ],
+    local_include_dir: "aidl",
+    unstable: true,
+    backend: {
+        java: {
+            enabled: true,
+        },
+        cpp: {
+            enabled: false,
+        },
+        rust: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: true,
+            // To use Surface
+            additional_shared_libraries: [
+                "libnativewindow",
+            ],
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.virt",
+            ],
+        },
+    },
+}
+
+cc_library_static {
+    name: "libcrosvm_android_display_client",
+    srcs: [
+        "crosvm_android_display_client.cpp",
+    ],
+    whole_static_libs: [
+        "libcrosvm_android_display_service-ndk",
+        "android.system.virtualizationservice_internal-ndk",
+        "android.system.virtualizationcommon-ndk",
+        "android.system.virtualizationservice-ndk",
+        "libyuv",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libnativewindow",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+}
diff --git a/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
new file mode 100644
index 0000000..c7bfc80
--- /dev/null
+++ b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.crosvm;
+
+import android.view.Surface;
+
+/**
+ * Service to provide Crosvm with an Android Surface for showing a guest's
+ * display.
+ */
+interface ICrosvmAndroidDisplayService {
+    void setSurface(inout Surface surface);
+
+    void removeSurface();
+}
diff --git a/libs/android_display_backend/crosvm_android_display_client.cpp b/libs/android_display_backend/crosvm_android_display_client.cpp
new file mode 100644
index 0000000..a16b7f2
--- /dev/null
+++ b/libs/android_display_backend/crosvm_android_display_client.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/crosvm/BnCrosvmAndroidDisplayService.h>
+#include <aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <android/native_window.h>
+#include <android/native_window_aidl.h>
+#include <libyuv.h>
+#include <stdint.h>
+#include <utils/Errors.h>
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+using aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal;
+
+#define LIBEXPORT __attribute__((visibility("default"))) extern "C"
+
+typedef void (*android_display_log_callback_type)(const char* message);
+
+static void android_display_log_callback_stub(const char* message) {
+    (void)message;
+}
+
+namespace {
+
+class DisplayService : public aidl::android::crosvm::BnCrosvmAndroidDisplayService {
+public:
+    DisplayService() = default;
+    virtual ~DisplayService() = default;
+
+    ndk::ScopedAStatus setSurface(aidl::android::view::Surface* surface) override {
+        {
+            std::lock_guard lk(mSurfaceReadyMutex);
+            mSurface = std::make_unique<aidl::android::view::Surface>(surface->release());
+        }
+        mSurfaceReady.notify_one();
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus removeSurface() override {
+        {
+            std::lock_guard lk(mSurfaceReadyMutex);
+            mSurface = nullptr;
+        }
+        mSurfaceReady.notify_one();
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    aidl::android::view::Surface* getSurface() {
+        std::unique_lock lk(mSurfaceReadyMutex);
+        mSurfaceReady.wait(lk, [this] { return mSurface != nullptr; });
+        return mSurface.get();
+    }
+
+private:
+    std::condition_variable mSurfaceReady;
+    std::mutex mSurfaceReadyMutex;
+    std::unique_ptr<aidl::android::view::Surface> mSurface;
+};
+
+void ErrorF(android_display_log_callback_type error_callback, const char* format, ...) {
+    char buffer[1024];
+
+    va_list vararg;
+    va_start(vararg, format);
+    vsnprintf(buffer, sizeof(buffer), format, vararg);
+    va_end(vararg);
+
+    error_callback(buffer);
+}
+
+} // namespace
+
+struct android_display_context {
+    uint32_t width;
+    uint32_t height;
+    std::shared_ptr<DisplayService> displayService;
+};
+
+LIBEXPORT
+struct android_display_context* create_android_display_context(
+        const char* name, size_t name_len, android_display_log_callback_type error_callback) {
+    auto ctx = new android_display_context();
+
+    auto service = ::ndk::SharedRefBase::make<DisplayService>();
+
+    if (strlen(name) != name_len) {
+        ErrorF(error_callback, "Invalid service name length. Expected %u, actual %u", name_len,
+               strlen(name));
+        return nullptr;
+    }
+    ::ndk::SpAIBinder binder(
+            AServiceManager_waitForService("android.system.virtualizationservice"));
+
+    auto virt_service = IVirtualizationServiceInternal::fromBinder(binder);
+    if (virt_service == nullptr) {
+        ErrorF(error_callback, "Failed to find android.system.virtualizationservice");
+        return nullptr;
+    }
+    auto status = virt_service->setDisplayService(service->asBinder());
+    if (!status.isOk()) {
+        ErrorF(error_callback, "Failed to register %s",
+               aidl::android::crosvm::ICrosvmAndroidDisplayService::descriptor);
+        return nullptr;
+    }
+
+    ABinderProcess_startThreadPool();
+
+    auto surface = service->getSurface();
+    ctx->width = static_cast<uint32_t>(ANativeWindow_getWidth(surface->get()));
+    ctx->height = static_cast<uint32_t>(ANativeWindow_getHeight(surface->get()));
+    ctx->displayService = service;
+    return ctx;
+}
+
+LIBEXPORT
+void destroy_android_display_context(android_display_log_callback_type error_callback,
+                                     struct android_display_context* ctx) {
+    auto service = ::ndk::SharedRefBase::make<DisplayService>();
+    ::ndk::SpAIBinder binder(
+            AServiceManager_waitForService("android.system.virtualizationservice"));
+    auto virt_service = IVirtualizationServiceInternal::fromBinder(binder);
+    if (virt_service != nullptr) {
+        auto status = virt_service->clearDisplayService();
+    } else {
+        ErrorF(error_callback, "Failed to find android.system.virtualizationservice");
+    }
+
+    if (!ctx) {
+        ErrorF(error_callback, "Invalid context.");
+        return;
+    }
+
+    delete ctx;
+}
+
+LIBEXPORT
+uint32_t get_android_display_width(android_display_log_callback_type error_callback,
+                                   struct android_display_context* ctx) {
+    if (!ctx) {
+        ErrorF(error_callback, "Invalid context.");
+        return -1;
+    }
+    if (!ctx->displayService->getSurface()) {
+        ErrorF(error_callback, "Invalid context surface for ctx:%p.", ctx);
+        return -1;
+    }
+    return ctx->width;
+}
+
+LIBEXPORT
+uint32_t get_android_display_height(android_display_log_callback_type error_callback,
+                                    struct android_display_context* ctx) {
+    if (!ctx) {
+        ErrorF(error_callback, "Invalid context.");
+        return -1;
+    }
+    if (!ctx->displayService->getSurface()) {
+        ErrorF(error_callback, "Invalid context surface for ctx:%p.", ctx);
+        return -1;
+    }
+    return ctx->height;
+}
+
+uint16_t RGBA8888ToRGB565(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+    (void)a;
+    return (static_cast<uint16_t>(r >> 3) << 11) | (static_cast<uint16_t>(g >> 2) << 5) |
+            (static_cast<uint16_t>(b >> 3) << 0);
+}
+
+LIBEXPORT
+void blit_android_display(android_display_log_callback_type error_callback,
+                          struct android_display_context* ctx, uint32_t width, uint32_t height,
+                          uint8_t* pixels, size_t pixels_num_bytes) {
+    if (!ctx) {
+        ErrorF(error_callback, "Invalid context.");
+        return;
+    }
+    if (!ctx->displayService->getSurface()) {
+        ErrorF(error_callback, "Invalid context surface.");
+        return;
+    }
+    if (pixels_num_bytes != width * height * 4) {
+        ErrorF(error_callback, "Invalid buffer size.");
+        return;
+    }
+    ANativeWindow* anw = ctx->displayService->getSurface()->get();
+    if (!anw) {
+        ErrorF(error_callback, "Invalid context surface.");
+        return;
+    }
+
+    ANativeWindow_Buffer anwBuffer = {};
+    if (ANativeWindow_lock(anw, &anwBuffer, nullptr) != android::OK) {
+        ErrorF(error_callback, "Failed to lock ANativeWindow.");
+        return;
+    }
+
+    // Source is always BGRA8888.
+    auto* src = reinterpret_cast<uint32_t*>(pixels);
+    auto srcWidth = static_cast<uint32_t>(width);
+    auto srcHeight = static_cast<uint32_t>(height);
+    auto srcStrideBytes = srcWidth * 4;
+    auto srcStridePixels = srcWidth;
+
+    auto dstWidth = static_cast<uint32_t>(anwBuffer.width);
+    auto dstHeight = static_cast<uint32_t>(anwBuffer.height);
+
+    // Scale to fit if needed.
+    std::vector<uint32_t> scaledSrc;
+    if (srcWidth != dstWidth || srcHeight != dstHeight) {
+        const float ratioWidth = static_cast<float>(dstWidth) / static_cast<float>(srcWidth);
+        const float ratioHeight = static_cast<float>(dstHeight) / static_cast<float>(srcHeight);
+        const float ratioUsed = std::min(ratioWidth, ratioHeight);
+
+        uint32_t scaledSrcWidth = static_cast<uint32_t>(static_cast<float>(srcWidth) * ratioUsed);
+        uint32_t scaledSrcHeight = static_cast<uint32_t>(static_cast<float>(srcHeight) * ratioUsed);
+        uint32_t scaledSrcStrideBytes = scaledSrcWidth * 4;
+        uint32_t scaledSrcStridePixels = scaledSrcWidth;
+
+        scaledSrc.resize(scaledSrcHeight * scaledSrcStridePixels);
+
+        libyuv::ARGBScale(reinterpret_cast<uint8_t*>(src), srcStrideBytes, srcWidth, srcHeight,
+                          reinterpret_cast<uint8_t*>(scaledSrc.data()), scaledSrcStrideBytes,
+                          scaledSrcWidth, scaledSrcHeight, libyuv::kFilterBilinear);
+
+        src = scaledSrc.data();
+        srcWidth = scaledSrcWidth;
+        srcHeight = scaledSrcHeight;
+        srcStrideBytes = scaledSrcStrideBytes;
+        srcStridePixels = scaledSrcStridePixels;
+    }
+
+    if (anwBuffer.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM) {
+        auto* dst = reinterpret_cast<uint32_t*>(anwBuffer.bits);
+        auto dstStridePixels = static_cast<uint32_t>(anwBuffer.stride);
+
+        for (uint32_t h = 0; h < std::min(srcHeight, dstHeight); h++) {
+            for (uint32_t w = 0; w < std::min(srcWidth, dstWidth); w++) {
+                dst[(h * dstStridePixels) + w] = src[(h * srcStridePixels) + w];
+            }
+        }
+    } else if (anwBuffer.format == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM) {
+        auto* dst = reinterpret_cast<uint16_t*>(anwBuffer.bits);
+        auto dstWidth = static_cast<uint32_t>(anwBuffer.width);
+        auto dstHeight = static_cast<uint32_t>(anwBuffer.height);
+        auto dstStridePixels = static_cast<uint32_t>(anwBuffer.stride);
+
+        for (uint32_t h = 0; h < std::min(srcHeight, dstHeight); h++) {
+            for (uint32_t w = 0; w < std::min(srcWidth, dstWidth); w++) {
+                uint32_t srcPixel = src[(h * srcStridePixels) + w];
+                uint8_t* srcPixelBytes = reinterpret_cast<uint8_t*>(&srcPixel);
+                uint8_t r = srcPixelBytes[2];
+                uint8_t g = srcPixelBytes[1];
+                uint8_t b = srcPixelBytes[0];
+                uint8_t a = srcPixelBytes[3];
+                dst[(h * dstStridePixels) + w] = RGBA8888ToRGB565(r, g, b, a);
+            }
+        }
+    } else {
+        ErrorF(error_callback, "Unhandled format: %d", anwBuffer.format);
+    }
+
+    if (ANativeWindow_unlockAndPost(anw) != android::OK) {
+        ErrorF(error_callback, "Failed to unlock and post ANativeWindow.");
+        return;
+    }
+}
diff --git a/libs/android_display_backend/crosvm_android_display_client.h b/libs/android_display_backend/crosvm_android_display_client.h
new file mode 100644
index 0000000..75b845b
--- /dev/null
+++ b/libs/android_display_backend/crosvm_android_display_client.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" {
+
+typedef void (*android_display_log_callback_type)(const char* message);
+
+static void android_display_log_callback_stub(const char* message) {
+    (void)message;
+}
+
+struct android_display_context {
+    uint32_t test;
+};
+
+__attribute__((visibility("default"))) struct android_display_context*
+create_android_display_context(const char* name, size_t name_len,
+                               android_display_log_callback_type error_callback);
+
+__attribute__((visibility("default"))) void destroy_android_display_context(
+        android_display_log_callback_type error_callback, struct android_display_context* ctx);
+
+} // extern C
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index 112e1cc..c479691 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -12,7 +12,10 @@
     backend: {
         java: {
             sdk_version: "module_current",
-            apex_available: ["com.android.virt"],
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.virt",
+            ],
         },
         cpp: {
             enabled: true,
@@ -44,6 +47,9 @@
     backend: {
         java: {
             sdk_version: "module_current",
+            apex_available: [
+                "//apex_available:platform",
+            ],
         },
         rust: {
             enabled: true,
@@ -51,6 +57,11 @@
                 "com.android.virt",
             ],
         },
+        ndk: {
+            apex_available: [
+                "com.android.virt",
+            ],
+        },
     },
 }
 
@@ -103,7 +114,10 @@
     backend: {
         java: {
             sdk_version: "module_current",
-            apex_available: ["com.android.virt"],
+            apex_available: [
+                "com.android.virt",
+                "//apex_available:platform",
+            ],
         },
         ndk: {
             apex_available: [
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 16975ee..84f8734 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -116,4 +116,9 @@
      * @param instanceId The ID for the VM.
      */
     void claimVmInstance(in byte[64] instanceId);
+
+    // TODO(b/330257000): Remove these functions when a display service is running with binder RPC.
+    void setDisplayService(IBinder ibinder);
+    void clearDisplayService();
+    IBinder waitDisplayService();
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index c6150b2..1fa634d 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -51,7 +51,7 @@
 use std::os::unix::fs::PermissionsExt;
 use std::os::unix::raw::{pid_t, uid_t};
 use std::path::{Path, PathBuf};
-use std::sync::{Arc, Mutex, Weak};
+use std::sync::{Arc, Condvar, Mutex, Weak};
 use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
 use virtualizationcommon::Certificate::Certificate;
 use virtualizationmaintenance::{
@@ -170,12 +170,15 @@
 #[derive(Clone)]
 pub struct VirtualizationServiceInternal {
     state: Arc<Mutex<GlobalState>>,
+    display_service_set: Arc<Condvar>,
 }
 
 impl VirtualizationServiceInternal {
     pub fn init() -> VirtualizationServiceInternal {
-        let service =
-            VirtualizationServiceInternal { state: Arc::new(Mutex::new(GlobalState::new())) };
+        let service = VirtualizationServiceInternal {
+            state: Arc::new(Mutex::new(GlobalState::new())),
+            display_service_set: Arc::new(Condvar::new()),
+        };
 
         std::thread::spawn(|| {
             if let Err(e) = handle_stream_connection_tombstoned() {
@@ -190,6 +193,39 @@
 impl Interface for VirtualizationServiceInternal {}
 
 impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
+    fn setDisplayService(
+        &self,
+        ibinder: &binder::SpIBinder,
+    ) -> std::result::Result<(), binder::Status> {
+        check_manage_access()?;
+        check_use_custom_virtual_machine()?;
+        let state = &mut *self.state.lock().unwrap();
+        state.display_service = Some(ibinder.clone());
+        self.display_service_set.notify_all();
+        Ok(())
+    }
+
+    fn clearDisplayService(&self) -> std::result::Result<(), binder::Status> {
+        check_manage_access()?;
+        check_use_custom_virtual_machine()?;
+        let state = &mut *self.state.lock().unwrap();
+        state.display_service = None;
+        self.display_service_set.notify_all();
+        Ok(())
+    }
+
+    fn waitDisplayService(&self) -> std::result::Result<binder::SpIBinder, binder::Status> {
+        check_manage_access()?;
+        check_use_custom_virtual_machine()?;
+        let state = self
+            .display_service_set
+            .wait_while(self.state.lock().unwrap(), |state| state.display_service.is_none())
+            .unwrap();
+        Ok((state.display_service)
+            .as_ref()
+            .cloned()
+            .expect("Display service cannot be None in this context"))
+    }
     fn removeMemlockRlimit(&self) -> binder::Result<()> {
         let pid = get_calling_pid();
         let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
@@ -588,6 +624,8 @@
 
     /// State relating to secrets held by (optional) Secretkeeper instance on behalf of VMs.
     sk_state: Option<maintenance::State>,
+
+    display_service: Option<binder::SpIBinder>,
 }
 
 impl GlobalState {
@@ -596,6 +634,7 @@
             held_contexts: HashMap::new(),
             dtbo_file: Mutex::new(None),
             sk_state: maintenance::State::new(),
+            display_service: None,
         }
     }
 
diff --git a/vmlauncher_app/Android.bp b/vmlauncher_app/Android.bp
index cd40448..06dcf7a 100644
--- a/vmlauncher_app/Android.bp
+++ b/vmlauncher_app/Android.bp
@@ -10,6 +10,10 @@
         "androidx-constraintlayout_constraintlayout",
         "androidx.appcompat_appcompat",
         "com.google.android.material_material",
+        // TODO(b/330257000): will be removed when binder RPC is used
+        "android.system.virtualizationservice_internal-java",
+        // TODO(b/331708504): will be removed when AVF framework handles surface
+        "libcrosvm_android_display_service-java",
     ],
     libs: [
         "framework-virtualization.impl",
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
index de9d094..860c03f 100644
--- a/vmlauncher_app/AndroidManifest.xml
+++ b/vmlauncher_app/AndroidManifest.xml
@@ -7,7 +7,11 @@
     <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
     <application
         android:label="VmLauncherApp">
-        <activity android:name=".MainActivity" android:exported="true">
+        <activity android:name=".MainActivity"
+                  android:screenOrientation="landscape"
+                  android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
+                  android:theme="@style/MyTheme"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 7c927c9..2af1e2f 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -20,6 +20,10 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.crosvm.ICrosvmAndroidDisplayService;
+import android.system.virtualizationservice_internal.IVirtualizationServiceInternal;
 import android.system.virtualmachine.VirtualMachineCustomImageConfig;
 import android.util.Log;
 import android.system.virtualmachine.VirtualMachine;
@@ -27,6 +31,11 @@
 import android.system.virtualmachine.VirtualMachineConfig;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -108,6 +117,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        getWindow().setDecorFitsSystemWindows(false);
         setContentView(R.layout.activity_main);
         VirtualMachineCallback callback =
                 new VirtualMachineCallback() {
@@ -184,6 +194,70 @@
         } catch (VirtualMachineException e) {
             throw new RuntimeException(e);
         }
+
+        SurfaceView surfaceView = findViewById(R.id.surface_view);
+        surfaceView
+                .getHolder()
+                .addCallback(
+                        // TODO(b/331708504): it should be handled in AVF framework.
+                        new SurfaceHolder.Callback() {
+                            @Override
+                            public void surfaceCreated(SurfaceHolder holder) {
+                                Log.d(
+                                        TAG,
+                                        "surface size: "
+                                                + holder.getSurfaceFrame().flattenToString());
+                                Log.d(
+                                        TAG,
+                                        "ICrosvmAndroidDisplayService.setSurface("
+                                                + holder.getSurface()
+                                                + ")");
+                                runWithDisplayService(
+                                        (service) -> service.setSurface(holder.getSurface()));
+                            }
+
+                            @Override
+                            public void surfaceChanged(
+                                    SurfaceHolder holder, int format, int width, int height) {
+                                Log.d(TAG, "width: " + width + ", height: " + height);
+                            }
+
+                            @Override
+                            public void surfaceDestroyed(SurfaceHolder holder) {
+                                Log.d(TAG, "ICrosvmAndroidDisplayService.removeSurface()");
+                                runWithDisplayService((service) -> service.removeSurface());
+                            }
+                        });
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        // Fullscreen:
+        WindowInsetsController windowInsetsController = surfaceView.getWindowInsetsController();
+        windowInsetsController.setSystemBarsBehavior(
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+        windowInsetsController.hide(WindowInsets.Type.systemBars());
+    }
+
+    @FunctionalInterface
+    public interface RemoteExceptionCheckedFunction<T> {
+        void apply(T t) throws RemoteException;
+    }
+
+    private void runWithDisplayService(
+            RemoteExceptionCheckedFunction<ICrosvmAndroidDisplayService> func) {
+        IVirtualizationServiceInternal vs =
+                IVirtualizationServiceInternal.Stub.asInterface(
+                        ServiceManager.getService("android.system.virtualizationservice"));
+        try {
+            assert vs != null;
+            Log.d(TAG, "wait for the service");
+            ICrosvmAndroidDisplayService service =
+                    ICrosvmAndroidDisplayService.Stub.asInterface(vs.waitDisplayService());
+            assert service != null;
+            func.apply(service);
+            Log.d(TAG, "job done");
+        } catch (Exception e) {
+            Log.d(TAG, "error", e);
+        }
     }
 
     /** Reads data from an input stream and posts it to the output data */
diff --git a/vmlauncher_app/res/layout/activity_main.xml b/vmlauncher_app/res/layout/activity_main.xml
index 5cbda78..6cc899f 100644
--- a/vmlauncher_app/res/layout/activity_main.xml
+++ b/vmlauncher_app/res/layout/activity_main.xml
@@ -7,12 +7,10 @@
     android:scrollbars="horizontal|vertical"
     android:textAlignment="textStart"
     tools:context=".MainActivity">
-
-    <LinearLayout
+    <SurfaceView
+        android:id="@+id/surface_view"
+        android:focusable="true"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-    </LinearLayout>
+        android:layout_height="match_parent" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vmlauncher_app/res/values/themes.xml b/vmlauncher_app/res/values/themes.xml
new file mode 100644
index 0000000..395f089
--- /dev/null
+++ b/vmlauncher_app/res/values/themes.xml
@@ -0,0 +1,14 @@
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <style name="MyTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+        <item name="android:navigationBarColor">
+            @android:color/transparent
+        </item>
+        <item name="android:statusBarColor">
+            @android:color/transparent
+        </item>
+        <item name="android:windowLayoutInDisplayCutoutMode">
+            shortEdges
+        </item>
+    </style>
+</resources>