Force finish pending frame drawn callback once launcher is stopped

- In rare cases, we may schedule to update the taskview with a
  screenshot but Launcher will not finish calling back because
  the activity is stopped (can be the result of the animation
  cancelling back to the app), which prevents Launcher from
  scheduling any frames and properly decrementing the frame
  count.

Bug: 202776119
Test: Launch an app with sharesheet, swipe up and verify that
      screenshot callbacks
Change-Id: I6b7e121fd5d9ef27ef5cccca060a4ff819fe9acc
diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
index 1fef544..1bb95b9 100644
--- a/quickstep/src/com/android/quickstep/ViewUtils.java
+++ b/quickstep/src/com/android/quickstep/ViewUtils.java
@@ -17,6 +17,7 @@
 
 import android.graphics.HardwareRenderer;
 import android.os.Handler;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
 
@@ -45,23 +46,43 @@
         return new FrameHandler(view, onFinishRunnable, canceled).schedule();
     }
 
-    private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback {
+    private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback,
+            ViewRootImpl.SurfaceChangedCallback {
 
         final ViewRootImpl mViewRoot;
         final Runnable mFinishCallback;
         final BooleanSupplier mCancelled;
         final Handler mHandler;
+        boolean mFinished;
 
         int mDeferFrameCount = 1;
 
         FrameHandler(View view, Runnable finishCallback, BooleanSupplier cancelled) {
             mViewRoot = view.getViewRootImpl();
+            mViewRoot.addSurfaceChangedCallback(this);
             mFinishCallback = finishCallback;
             mCancelled = cancelled;
             mHandler = new Handler();
         }
 
         @Override
+        public void surfaceCreated(SurfaceControl.Transaction t) {
+            // Do nothing
+        }
+
+        @Override
+        public void surfaceReplaced(SurfaceControl.Transaction t) {
+            // Do nothing
+        }
+
+        @Override
+        public void surfaceDestroyed() {
+            // If the root view is detached, then the app won't get any scheduled frames so we need
+            // to force-run any pending callbacks
+            finish();
+        }
+
+        @Override
         public void onFrameDraw(long frame) {
             Utilities.postAsyncCallback(mHandler, this::onFrame);
         }
@@ -77,9 +98,7 @@
                 return;
             }
 
-            if (mFinishCallback != null) {
-                mFinishCallback.run();
-            }
+            finish();
         }
 
         private boolean schedule() {
@@ -90,5 +109,17 @@
             }
             return false;
         }
+
+        private void finish() {
+            if (mFinished) {
+                return;
+            }
+            mFinished = true;
+            mDeferFrameCount = 0;
+            if (mFinishCallback != null) {
+                mFinishCallback.run();
+            }
+            mViewRoot.removeSurfaceChangedCallback(this);
+        }
     }
 }