Send syncStatus and return FrameCommitCallback in FrameCallback

FrameCallback sends back the syncStatus and allows the caller to return
a FrameCommitCallback. This is so the caller can evaluate the results of
the sync and determine if it wants to continue waiting for a commit
callback. In cases, where the sync has failed, there will never be a
commit callback so the caller can avoid waiting.

This is partically helpful for VRI because it wants to sync with a frame
that will draw, but also doesn't want to get stuck indefinitely waiting
on the callback. VRI can check the syncStatus and only handle blast sync
and/or reportDraw when its determined that a draw will happen.

This allows VRI to remove frameCompleteCallback since it can now wait
until a buffer has drawn instead of when the vsync has completed. This makes
sure it waits on FrameDropped since that result indicates HWUI will draw on
the next vsync

Test: BlastSync
Bug: 200284684
Change-Id: Ic6e2f08ea21ac8a1634a3389c927fcb68ede3f7b
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 00754af..1566f9e 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -478,6 +478,19 @@
     }
 
     /**
+     * Remove a frame drawing callback that was added via
+     * {@link #registerRtFrameCallback(FrameDrawingCallback)}
+     *
+     * @param callback The callback to unregister.
+     */
+    void unregisterRtFrameCallback(@NonNull FrameDrawingCallback callback) {
+        if (mNextRtFrameCallbacks == null) {
+            return;
+        }
+        mNextRtFrameCallbacks.remove(callback);
+    }
+
+    /**
      * Destroys all hardware rendering resources associated with the specified
      * view hierarchy.
      *
@@ -679,9 +692,31 @@
         if (mNextRtFrameCallbacks != null) {
             final ArrayList<FrameDrawingCallback> frameCallbacks = mNextRtFrameCallbacks;
             mNextRtFrameCallbacks = null;
-            setFrameCallback(frame -> {
-                for (int i = 0; i < frameCallbacks.size(); ++i) {
-                    frameCallbacks.get(i).onFrameDraw(frame);
+            setFrameCallback(new FrameDrawingCallback() {
+                @Override
+                public void onFrameDraw(long frame) {
+                }
+
+                @Override
+                public FrameCommitCallback onFrameDraw(int syncResult, long frame) {
+                    ArrayList<FrameCommitCallback> frameCommitCallbacks = new ArrayList<>();
+                    for (int i = 0; i < frameCallbacks.size(); ++i) {
+                        FrameCommitCallback frameCommitCallback = frameCallbacks.get(i)
+                                .onFrameDraw(syncResult, frame);
+                        if (frameCommitCallback != null) {
+                            frameCommitCallbacks.add(frameCommitCallback);
+                        }
+                    }
+
+                    if (frameCommitCallbacks.isEmpty()) {
+                        return null;
+                    }
+
+                    return didProduceBuffer -> {
+                        for (int i = 0; i < frameCommitCallbacks.size(); ++i) {
+                            frameCommitCallbacks.get(i).onFrameCommit(didProduceBuffer);
+                        }
+                    };
                 }
             });
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8109197..12e914f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
+import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
 import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -1395,11 +1397,20 @@
      */
     public void registerRtFrameCallback(@NonNull FrameDrawingCallback callback) {
         if (mAttachInfo.mThreadedRenderer != null) {
-            mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frame -> {
-                try {
-                    callback.onFrameDraw(frame);
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception while executing onFrameDraw", e);
+            mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() {
+                @Override
+                public void onFrameDraw(long frame) {
+                }
+
+                @Override
+                public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult,
+                        long frame) {
+                    try {
+                        return callback.onFrameDraw(syncResult, frame);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception while executing onFrameDraw", e);
+                    }
+                    return null;
                 }
             });
         }
@@ -4026,61 +4037,6 @@
         return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
     }
 
-    private boolean addFrameCompleteCallbackIfNeeded(boolean useBlastSync,
-            boolean reportNextDraw) {
-        if (!isHardwareEnabled()) {
-            return false;
-        }
-
-        if (!useBlastSync && !reportNextDraw) {
-            return false;
-        }
-
-        if (DEBUG_BLAST) {
-            Log.d(mTag, "Creating frameCompleteCallback");
-        }
-
-        final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer;
-        mBLASTDrawConsumer = null;
-
-        mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(() -> {
-            long frameNr = mBlastBufferQueue.getLastAcquiredFrameNum();
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "Received frameCompleteCallback "
-                        + " lastAcquiredFrameNum=" + frameNr
-                        + " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum);
-            }
-
-            boolean frameWasNotDrawn = frameNr != mRtLastAttemptedDrawFrameNum;
-            // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
-            // draw attempt. The next transaction and transaction complete callback were only set
-            // for the current draw attempt.
-            if (frameWasNotDrawn) {
-                mBlastBufferQueue.setSyncTransaction(null);
-                // Apply the transactions that were sent to mergeWithNextTransaction since the
-                // frame didn't draw on this vsync. It's possible the frame will draw later, but
-                // it's better to not be sync than to block on a frame that may never come.
-                mBlastBufferQueue.applyPendingTransactions(mRtLastAttemptedDrawFrameNum);
-            }
-
-            Transaction tmpTransaction = new Transaction();
-            tmpTransaction.merge(mRtBLASTSyncTransaction);
-            mHandler.postAtFrontOfQueue(() -> {
-                if (useBlastSync) {
-                    mSurfaceChangedTransaction.merge(tmpTransaction);
-                    if (blastSyncConsumer != null) {
-                        blastSyncConsumer.accept(mSurfaceChangedTransaction);
-                    }
-                }
-
-                if (reportNextDraw) {
-                    pendingDrawFinished();
-                }
-            });
-        });
-        return true;
-    }
-
     private void addFrameCommitCallbackIfNeeded() {
         if (!isHardwareEnabled()) {
             return;
@@ -4111,51 +4067,131 @@
         });
     }
 
-    private void addFrameCallbackIfNeeded(boolean useBlastSync) {
+    private HardwareRenderer.FrameCommitCallback createFrameCommitCallbackForSync(
+            boolean useBlastSync, boolean reportNextDraw, Consumer<Transaction> blastSyncConsumer) {
+        return didProduceBuffer -> {
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Received frameCommittedCallback "
+                        + " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum
+                        + " didProduceBuffer=" + didProduceBuffer);
+            }
+
+            // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
+            // draw attempt. The next transaction and transaction complete callback were only set
+            // for the current draw attempt.
+            if (!didProduceBuffer) {
+                mBlastBufferQueue.setSyncTransaction(null);
+                // Apply the transactions that were sent to mergeWithNextTransaction since the
+                // frame didn't draw on this vsync. It's possible the frame will draw later, but
+                // it's better to not be sync than to block on a frame that may never come.
+                mBlastBufferQueue.applyPendingTransactions(mRtLastAttemptedDrawFrameNum);
+            }
+
+            Transaction tmpTransaction = new Transaction();
+            tmpTransaction.merge(mRtBLASTSyncTransaction);
+            // Post at front of queue so the buffer can be processed immediately and allow RT
+            // to continue processing new buffers. If RT tries to process buffers before the sync
+            // buffer is applied, the new buffers will not get acquired and could result in a
+            // deadlock. UI thread would wait on RT, but RT would be blocked waiting for a free
+            // buffer.
+            mHandler.postAtFrontOfQueue(() -> {
+                if (useBlastSync) {
+                    mSurfaceChangedTransaction.merge(tmpTransaction);
+                    if (blastSyncConsumer != null) {
+                        blastSyncConsumer.accept(mSurfaceChangedTransaction);
+                    }
+                }
+
+                if (reportNextDraw) {
+                    pendingDrawFinished();
+                }
+            });
+        };
+    }
+
+    @Nullable
+    private FrameDrawingCallback createFrameDrawingCallbackIfNeeded(boolean useBlastSync,
+            boolean reportNextDraw) {
+        if (!isHardwareEnabled()) {
+            return null;
+        }
         final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
         final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
 
-        if (!useBlastSync && !needsCallbackForBlur) {
-            return;
+        if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw) {
+            return null;
         }
 
+        final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer;
+        mBLASTDrawConsumer = null;
+
         if (DEBUG_BLAST) {
             Log.d(mTag, "Creating frameDrawingCallback"
                     + " nextDrawUseBlastSync=" + useBlastSync
-                    + " hasBlurUpdates=" + hasBlurUpdates);
+                    + " reportNextDraw=" + reportNextDraw
+                    + " hasBlurUpdates=" + hasBlurUpdates
+                    + " hasBlastSyncConsumer=" + (blastSyncConsumer != null));
         }
+
         final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
                 needsCallbackForBlur ?  mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
 
         // The callback will run on the render thread.
-        HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> {
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "."
-                        + " Creating transactionCompleteCallback=" + useBlastSync);
+        return new FrameDrawingCallback() {
+            @Override
+            public void onFrameDraw(long frame) {
             }
 
-            mRtLastAttemptedDrawFrameNum = frame;
+            @Override
+            public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) {
+                if (DEBUG_BLAST) {
+                    Log.d(mTag,
+                            "Received frameDrawingCallback syncResult=" + syncResult + " frameNum="
+                                    + frame + ".");
+                }
 
-            if (needsCallbackForBlur) {
-                mBlurRegionAggregator
-                    .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates);
-            }
+                mRtLastAttemptedDrawFrameNum = frame;
 
-            if (mBlastBufferQueue == null) {
-                return;
-            }
+                if (needsCallbackForBlur) {
+                    mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame,
+                            blurRegionsForFrame, hasBlurUpdates);
+                }
 
-            if (useBlastSync) {
-                // Frame callbacks will always occur after submitting draw requests and before
-                // the draw actually occurs. This will ensure that we set the next transaction
-                // for the frame that's about to get drawn and not on a previous frame that.
+                if (mBlastBufferQueue == null) {
+                    return null;
+                }
 
-                // We don't need to synchronize mRtBLASTSyncTransaction here since it's not
-                // being modified and only sent to BlastBufferQueue.
-                mBlastBufferQueue.setSyncTransaction(mRtBLASTSyncTransaction);
+                if (!useBlastSync && !reportNextDraw) {
+                    return null;
+                }
+
+                // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or
+                // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up
+                // any blast sync or commit callback, and the code should directly call
+                // pendingDrawFinished.
+                if ((syncResult
+                        & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
+                    if (reportNextDraw) {
+                        mHandler.postAtFrontOfQueue(() -> pendingDrawFinished());
+                    }
+                    return null;
+                }
+
+                if (DEBUG_BLAST) {
+                    Log.d(mTag, "Setting up sync and frameCommitCallback");
+                }
+
+                if (useBlastSync) {
+                    // Frame callbacks will always occur after submitting draw requests and before
+                    // the draw actually occurs. This will ensure that we set the next transaction
+                    // for the frame that's about to get drawn and not on a previous frame.
+                    mBlastBufferQueue.setSyncTransaction(mRtBLASTSyncTransaction);
+                }
+
+                return createFrameCommitCallbackForSync(useBlastSync, reportNextDraw,
+                        blastSyncConsumer);
             }
         };
-        registerRtFrameCallback(frameDrawingCallback);
     }
 
     private void performDraw(boolean useBlastSync) {
@@ -4171,15 +4207,20 @@
         mIsDrawing = true;
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
 
-        addFrameCallbackIfNeeded(useBlastSync);
+        FrameDrawingCallback frameDrawingCallback = createFrameDrawingCallbackIfNeeded(useBlastSync,
+                mReportNextDraw);
+        if (frameDrawingCallback != null) {
+            mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frameDrawingCallback);
+        }
         addFrameCommitCallbackIfNeeded();
-        boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(useBlastSync, mReportNextDraw);
+        boolean usingAsyncReport = isHardwareEnabled() && (useBlastSync || mReportNextDraw);
 
         try {
             boolean canUseAsync = draw(fullRedrawNeeded);
             if (usingAsyncReport && !canUseAsync) {
-                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
+                mAttachInfo.mThreadedRenderer.setFrameCallback(null);
                 usingAsyncReport = false;
+                mAttachInfo.mThreadedRenderer.unregisterRtFrameCallback(frameDrawingCallback);
             }
         } finally {
             mIsDrawing = false;
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 2b18350..fd4bed1 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -902,6 +902,20 @@
          * @param frame The id of the frame being drawn.
          */
         void onFrameDraw(long frame);
+
+        /**
+         * Invoked during a frame drawing.
+         *
+         * @param syncResult The result of the draw. Should be a value or a combination of values
+         *                   from {@link SyncAndDrawResult}
+         * @param frame The id of the frame being drawn.
+         *
+         * @return A {@link FrameCommitCallback} that will report back if the current vsync draws.
+         */
+        default FrameCommitCallback onFrameDraw(@SyncAndDrawResult int syncResult, long frame) {
+            onFrameDraw(frame);
+            return null;
+        }
     }
 
     /**
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index bd93a4f..27865b3 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -609,10 +609,19 @@
         LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
         auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
                 env->NewGlobalRef(frameCallback));
-        proxy->setFrameCallback([globalCallbackRef](int64_t frameNr) {
+        proxy->setFrameCallback([globalCallbackRef](int32_t syncResult,
+                                                    int64_t frameNr) -> std::function<void(bool)> {
             JNIEnv* env = getenv(globalCallbackRef->vm());
-            env->CallVoidMethod(globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
-                    static_cast<jlong>(frameNr));
+            ScopedLocalRef<jobject> frameCommitCallback(
+                    env, env->CallObjectMethod(
+                                 globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
+                                 static_cast<jint>(syncResult), static_cast<jlong>(frameNr)));
+            if (frameCommitCallback == nullptr) {
+                return nullptr;
+            }
+            sp<FrameCommitWrapper> wrapper =
+                    sp<FrameCommitWrapper>::make(env, frameCommitCallback.get());
+            return [wrapper](bool didProduceBuffer) { wrapper->onFrameCommit(didProduceBuffer); };
         });
     }
 }
@@ -623,7 +632,7 @@
     if (!callback) {
         proxy->setFrameCommitCallback(nullptr);
     } else {
-        sp<FrameCommitWrapper> wrapper = new FrameCommitWrapper{env, callback};
+        sp<FrameCommitWrapper> wrapper = sp<FrameCommitWrapper>::make(env, callback);
         proxy->setFrameCommitCallback(
                 [wrapper](bool didProduceBuffer) { wrapper->onFrameCommit(didProduceBuffer); });
     }
@@ -1003,8 +1012,9 @@
 
     jclass frameCallbackClass = FindClassOrDie(env,
             "android/graphics/HardwareRenderer$FrameDrawingCallback");
-    gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
-            "onFrameDraw", "(J)V");
+    gFrameDrawingCallback.onFrameDraw =
+            GetMethodIDOrDie(env, frameCallbackClass, "onFrameDraw",
+                             "(IJ)Landroid/graphics/HardwareRenderer$FrameCommitCallback;");
 
     jclass frameCommitClass =
             FindClassOrDie(env, "android/graphics/HardwareRenderer$FrameCommitCallback");
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 94aedd0..8c98c72 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -158,7 +158,8 @@
 
     // Grab a copy of everything we need
     CanvasContext* context = mContext;
-    std::function<void(int64_t)> frameCallback = std::move(mFrameCallback);
+    std::function<std::function<void(bool)>(int32_t, int64_t)> frameCallback =
+            std::move(mFrameCallback);
     std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback);
     mFrameCallback = nullptr;
     mFrameCompleteCallback = nullptr;
@@ -173,8 +174,13 @@
 
     // Even if we aren't drawing this vsync pulse the next frame number will still be accurate
     if (CC_UNLIKELY(frameCallback)) {
-        context->enqueueFrameWork(
-                [frameCallback, frameNr = context->getFrameNumber()]() { frameCallback(frameNr); });
+        context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
+                                   frameNr = context->getFrameNumber()]() {
+            auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr));
+            if (frameCommitCallback) {
+                context->addFrameCommitListener(std::move(frameCommitCallback));
+            }
+        });
     }
 
     nsecs_t dequeueBufferDuration = 0;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index e3ea802..8ad8abc 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -77,7 +77,7 @@
 
     void run();
 
-    void setFrameCallback(std::function<void(int64_t)>&& callback) {
+    void setFrameCallback(std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback) {
         mFrameCallback = std::move(callback);
     }
 
@@ -126,7 +126,7 @@
 
     int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
 
-    std::function<void(int64_t)> mFrameCallback;
+    std::function<std::function<void(bool)>(int32_t, int64_t)> mFrameCallback;
     std::function<void(bool)> mFrameCommitCallback;
     std::function<void()> mFrameCompleteCallback;
 
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 430c4d3..026699c 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -327,7 +327,8 @@
             [this, cb = callback]() { mContext->setPrepareSurfaceControlForWebviewCallback(cb); });
 }
 
-void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
+void RenderProxy::setFrameCallback(
+        std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback) {
     mDrawFrameTask.setFrameCallback(std::move(callback));
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 6d46be4..491dbd7 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -124,7 +124,7 @@
     void setASurfaceTransactionCallback(
             const std::function<bool(int64_t, int64_t, int64_t)>& callback);
     void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback);
-    void setFrameCallback(std::function<void(int64_t)>&& callback);
+    void setFrameCallback(std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback);
     void setFrameCommitCallback(std::function<void(bool)>&& callback);
     void setFrameCompleteCallback(std::function<void()>&& callback);