Eliminate a short blackout during the resume of the VM display

Before this CL, there was a short period of blackout (1-2 sec) on the
VM's display when it is brought to foreground. This was because we send
a lid-close even when the VM goes background, and a lid-open event when
it goes foreground, and the guest OS needs some time to respond to the
lid-open event and draw a first frame after the wakeup.

This CL fixes the blackout issue, by saving the last good frame just
before the VM goes background, and re-draws the frame when it goes
foreground.

Note that the saved frame could be very old (ex: a few hours ago), so it
may be incorrect. For example, if the guest OS has a clock, the clock on
the saved frame may point to a very far past. However, this is not a big
issue because the guest OS will soon (within 1-2 sec) react to the
lid-open event and draw a correct frame.

Bug: 348380730
Test: run ferrochrome. go to home, and then come back. There's no
blackout.

Change-Id: I895cb04f3aa32f0b2a65947720e3af417af6296c
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
index 0b93968..a93c173 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -457,10 +457,16 @@
                                                 + holder.getSurface()
                                                 + ")");
                                 runWithDisplayService(
-                                        (service) ->
-                                                service.setSurface(
+                                        s ->
+                                                s.setSurface(
                                                         holder.getSurface(),
                                                         false /* forCursor */));
+                                // TODO  execute the above and the below togther with the same call
+                                // to runWithDisplayService. Currently this doesn't work because
+                                // setSurface somtimes trigger an exception and as a result
+                                // drawSavedFrameForSurface is skipped.
+                                runWithDisplayService(
+                                        s -> s.drawSavedFrameForSurface(false /* forCursor */));
                             }
 
                             @Override
@@ -544,6 +550,12 @@
     }
 
     @Override
+    protected void onPause() {
+        super.onPause();
+        runWithDisplayService(s -> s.saveFrameForSurface(false /* forCursor */));
+    }
+
+    @Override
     protected void onStop() {
         super.onStop();
         if (mVirtualMachine != null) {
diff --git a/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
index e42cdd1..77e3a8c 100644
--- a/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
+++ b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
@@ -27,4 +27,6 @@
     void setSurface(inout Surface surface, boolean forCursor);
     void setCursorStream(in ParcelFileDescriptor stream);
     void removeSurface(boolean forCursor);
+    void saveFrameForSurface(boolean forCursor);
+    void drawSavedFrameForSurface(boolean forCursor);
 }
diff --git a/libs/android_display_backend/crosvm_android_display_client.cpp b/libs/android_display_backend/crosvm_android_display_client.cpp
index 3543540..3802a69 100644
--- a/libs/android_display_backend/crosvm_android_display_client.cpp
+++ b/libs/android_display_backend/crosvm_android_display_client.cpp
@@ -59,6 +59,20 @@
     std::vector<uint8_t> mBufferBits;
 };
 
+static Result<void> copyBuffer(ANativeWindow_Buffer& from, ANativeWindow_Buffer& to) {
+    if (from.width != to.width || from.height != to.height) {
+        return Error() << "dimension mismatch. from=(" << from.width << ", " << from.height << ") "
+                       << "to=(" << to.width << ", " << to.height << ")";
+    }
+    uint32_t* dst = reinterpret_cast<uint32_t*>(to.bits);
+    uint32_t* src = reinterpret_cast<uint32_t*>(from.bits);
+    size_t bytes_on_line = to.width * 4; // 4 bytes per pixel
+    for (int32_t h = 0; h < to.height; h++) {
+        memcpy(dst + (h * to.stride), src + (h * from.stride), bytes_on_line);
+    }
+    return {};
+}
+
 // Wrapper which contains the latest available Surface/ANativeWindow from the DisplayService, if
 // available. A Surface/ANativeWindow may not always be available if, for example, the VmLauncherApp
 // on the other end of the DisplayService is not in the foreground / is paused.
@@ -66,7 +80,7 @@
 public:
     AndroidDisplaySurface(const std::string& name) : mName(name) {}
 
-    void setSurface(Surface* surface) {
+    void setNativeSurface(Surface* surface) {
         {
             std::lock_guard lk(mSurfaceMutex);
             mNativeSurface = std::make_unique<Surface>(surface->release());
@@ -97,11 +111,13 @@
                 .height = height,
         };
 
-        auto ret = mSinkBuffer.configure(width, height, kFormat);
-        if (!ret.ok()) {
+        if (auto ret = mSinkBuffer.configure(width, height, kFormat); !ret.ok()) {
             return Error() << "Failed to configure sink buffer: " << ret.error();
         }
-        return ret;
+        if (auto ret = mSavedFrameBuffer.configure(width, height, kFormat); !ret.ok()) {
+            return Error() << "Failed to configure saved frame buffer: " << ret.error();
+        }
+        return {};
     }
 
     void waitForNativeSurface() {
@@ -142,6 +158,7 @@
         if (ANativeWindow_lock(anw, out_buffer, nullptr) != 0) {
             return Error() << "Failed to lock window";
         }
+        mLastBuffer = *out_buffer;
         return {};
     }
 
@@ -166,6 +183,58 @@
         return {};
     }
 
+    // Saves the last frame drawn
+    Result<void> saveFrame() {
+        std::unique_lock lk(mSurfaceMutex);
+        if (auto ret = copyBuffer(mLastBuffer, mSavedFrameBuffer); !ret.ok()) {
+            return Error() << "Failed to copy frame: " << ret.error();
+        }
+        return {};
+    }
+
+    // Draws the saved frame
+    Result<void> drawSavedFrame() {
+        std::unique_lock lk(mSurfaceMutex);
+        Surface* surface = mNativeSurface.get();
+        if (surface == nullptr) {
+            return Error() << "Surface not ready";
+        }
+
+        ANativeWindow* anw = surface->get();
+        if (anw == nullptr) {
+            return Error() << "Failed to get ANativeWindow";
+        }
+
+        // TODO: dedup this and the one in lock(...)
+        if (mNativeSurfaceNeedsConfiguring) {
+            if (!mRequestedSurfaceDimensions) {
+                return Error() << "Surface dimension is not configured yet!";
+            }
+            const auto& dims = *mRequestedSurfaceDimensions;
+
+            // Ensure locked buffers have our desired format.
+            if (ANativeWindow_setBuffersGeometry(anw, dims.width, dims.height, kFormat) != 0) {
+                return Error() << "Failed to set buffer geometry.";
+            }
+
+            mNativeSurfaceNeedsConfiguring = false;
+        }
+
+        ANativeWindow_Buffer buf;
+        if (ANativeWindow_lock(anw, &buf, nullptr) != 0) {
+            return Error() << "Failed to lock window";
+        }
+
+        if (auto ret = copyBuffer(mSavedFrameBuffer, buf); !ret.ok()) {
+            return Error() << "Failed to copy frame: " << ret.error();
+        }
+
+        if (ANativeWindow_unlockAndPost(anw) != 0) {
+            return Error() << "Failed to unlock and post window";
+        }
+        return {};
+    }
+
     const std::string& name() const { return mName; }
 
 private:
@@ -181,8 +250,20 @@
     std::condition_variable mNativeSurfaceReady;
     bool mNativeSurfaceNeedsConfiguring = true;
 
+    // Buffer which crosvm uses when in background. This is just to not fail crosvm even when
+    // Android-side Surface doesn't exist. The content drawn here is never displayed on the physical
+    // screen.
     SinkANativeWindow_Buffer mSinkBuffer;
 
+    // Buffer which is currently allocated for crosvm to draw onto. This holds the last frame. This
+    // is what gets displayed on the physical screen.
+    ANativeWindow_Buffer mLastBuffer;
+
+    // Copy of mLastBuffer made by the call saveFrameForSurface. This holds the last good (i.e.
+    // non-blank) frame before the VM goes background. When the VM is brought up to foreground,
+    // this is drawn to the physical screen until the VM starts to emit actual frames.
+    SinkANativeWindow_Buffer mSavedFrameBuffer;
+
     struct Rect {
         uint32_t width = 0;
         uint32_t height = 0;
@@ -196,7 +277,7 @@
     virtual ~DisplayService() = default;
 
     ndk::ScopedAStatus setSurface(Surface* surface, bool forCursor) override {
-        getSurface(forCursor).setSurface(surface);
+        getSurface(forCursor).setNativeSurface(surface);
         return ::ndk::ScopedAStatus::ok();
     }
 
@@ -211,6 +292,24 @@
         return ::ndk::ScopedAStatus::ok();
     }
 
+    ndk::ScopedAStatus saveFrameForSurface(bool forCursor) override {
+        if (auto ret = getSurface(forCursor).saveFrame(); !ret.ok()) {
+            std::string msg = std::format("Failed to save frame: {}", ret.error().message());
+            return ::ndk::ScopedAStatus(
+                    AStatus_fromServiceSpecificErrorWithMessage(-1, msg.c_str()));
+        }
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus drawSavedFrameForSurface(bool forCursor) override {
+        if (auto ret = getSurface(forCursor).drawSavedFrame(); !ret.ok()) {
+            std::string msg = std::format("Failed to draw saved frame: {}", ret.error().message());
+            return ::ndk::ScopedAStatus(
+                    AStatus_fromServiceSpecificErrorWithMessage(-1, msg.c_str()));
+        }
+        return ::ndk::ScopedAStatus::ok();
+    }
+
     AndroidDisplaySurface& getSurface(bool forCursor) {
         if (forCursor) {
             return mCursor;