Force draw during a blast sync and disable rt animations

When ViewRootImpl is going to draw a frame due to a blast sync request,
we call forceDrawNextFrame to ensure we get a draw this vsync, even if
RT animation rendered a frame. This will ensure that we aren't required
to reschedule a draw either in rt thread or ui thread since we'll get a
frame back as long as there's an available buffer.

We also disable rt animations if a sync set has started with Syncer and
then re-enable when the sync set is complete. This ensures we don't
continue to acquire buffers for a render thread scheduled animation
during a UI animation since we want the UI request to get the buffer.

Test: Open internet dialog from notification shade on 60hz device
Fixes: 217621394
Bug: 200284684
Change-Id: Ic1534d21b0ee2ad82f20ff34df4cbbbad87557fd
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0df8a0b..9a25be8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -816,12 +816,7 @@
     private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
     private int mLastSyncId = -1;
     private SurfaceSyncer.SyncBufferCallback mSyncBufferCallback;
-
-    /**
-     * Keeps track of the last frame number that was attempted to draw. Should only be accessed on
-     * the RenderThread.
-     */
-    private long mRtLastAttemptedDrawFrameNum = 0;
+    private int mNumSyncsInProgress = 0;
 
     private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
 
@@ -4249,7 +4244,7 @@
         mHasPendingTransactions = false;
 
         try {
-            boolean canUseAsync = draw(fullRedrawNeeded);
+            boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
             if (usingAsyncReport && !canUseAsync) {
                 mAttachInfo.mThreadedRenderer.setFrameCallback(null);
                 usingAsyncReport = false;
@@ -4409,7 +4404,7 @@
         }
     }
 
-    private boolean draw(boolean fullRedrawNeeded) {
+    private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
         Surface surface = mSurface;
         if (!surface.isValid()) {
             return false;
@@ -4546,6 +4541,9 @@
 
                 useAsyncReport = true;
 
+                if (forceDraw) {
+                    mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
+                }
                 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
             } else {
                 // If we get here with a disabled & requested hardware renderer, something went
@@ -10864,9 +10862,28 @@
         });
     }
 
-    public final SurfaceSyncer.SyncTarget mSyncTarget = this::readyToSync;
+    public final SurfaceSyncer.SyncTarget mSyncTarget = new SurfaceSyncer.SyncTarget() {
+        @Override
+        public void onReadyToSync(SurfaceSyncer.SyncBufferCallback syncBufferCallback) {
+            readyToSync(syncBufferCallback);
+        }
+
+        @Override
+        public void onSyncComplete() {
+            mHandler.postAtFrontOfQueue(() -> {
+                if (--mNumSyncsInProgress == 0 && mAttachInfo.mThreadedRenderer != null) {
+                    HardwareRenderer.setRtAnimationsEnabled(true);
+                }
+            });
+        }
+    };
 
     private void readyToSync(SurfaceSyncer.SyncBufferCallback syncBufferCallback) {
+        mNumSyncsInProgress++;
+        if (mAttachInfo.mThreadedRenderer != null) {
+            HardwareRenderer.setRtAnimationsEnabled(false);
+        }
+
         if (mSyncBufferCallback != null) {
             Log.d(mTag, "Already set sync for the next draw.");
             mSyncBufferCallback.onBufferReady(null);
diff --git a/core/java/android/window/SurfaceSyncer.java b/core/java/android/window/SurfaceSyncer.java
index 0c32219..0e011bb 100644
--- a/core/java/android/window/SurfaceSyncer.java
+++ b/core/java/android/window/SurfaceSyncer.java
@@ -21,15 +21,16 @@
 import android.annotation.UiThread;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceView;
 import android.view.View;
+import android.view.ViewRootImpl;
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -99,7 +100,9 @@
         Handler handler = new Handler(Looper.myLooper());
         return setupSync(transaction -> {
             transaction.apply();
-            handler.post(onComplete);
+            if (onComplete != null) {
+                handler.post(onComplete);
+            }
         });
     }
 
@@ -171,7 +174,11 @@
      */
     @UiThread
     public boolean addToSync(int syncId, @NonNull View view) {
-        return addToSync(syncId, view.getViewRootImpl().mSyncTarget);
+        ViewRootImpl viewRoot = view.getViewRootImpl();
+        if (viewRoot == null) {
+            return false;
+        }
+        return addToSync(syncId, viewRoot.mSyncTarget);
     }
 
     /**
@@ -232,9 +239,17 @@
          * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable
          * to be marked as complete.
          *
+         * Always invoked on the thread that initiated the call to
+         * {@link #addToSync(int, SyncTarget)}
+         *
          * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady
          */
         void onReadyToSync(SyncBufferCallback syncBufferCallback);
+
+        /**
+         * There's no guarantee about the thread this callback is invoked on.
+         */
+        default void onSyncComplete() {}
     }
 
     /**
@@ -260,11 +275,13 @@
         private final Object mLock = new Object();
 
         @GuardedBy("mLock")
-        private final Set<Integer> mPendingSyncs = new HashSet<>();
+        private final Set<Integer> mPendingSyncs = new ArraySet<>();
         @GuardedBy("mLock")
         private final Transaction mTransaction = sTransactionFactory.get();
         @GuardedBy("mLock")
         private boolean mSyncReady;
+        @GuardedBy("mLock")
+        private final Set<SyncTarget> mSyncTargets = new ArraySet<>();
 
         private final int mSyncId;
         private final Consumer<Transaction> mSyncRequestCompleteCallback;
@@ -290,6 +307,7 @@
 
             synchronized (mLock) {
                 mPendingSyncs.add(syncBufferCallback.hashCode());
+                mSyncTargets.add(syncTarget);
             }
             syncTarget.onReadyToSync(syncBufferCallback);
         }
@@ -314,6 +332,11 @@
             if (DEBUG) {
                 Log.d(TAG, "Successfully finished sync id=" + mSyncId);
             }
+
+            for (SyncTarget syncTarget : mSyncTargets) {
+                syncTarget.onSyncComplete();
+            }
+            mSyncTargets.clear();
             mSyncRequestCompleteCallback.accept(mTransaction);
         }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
index 35b4166..76de7b5 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
@@ -1,14 +1,11 @@
 package com.android.systemui.animation
 
-import android.app.ActivityManager
 import android.view.View
 import android.window.SurfaceSyncer
 
 /** A util class to synchronize 2 view roots. */
 // TODO(b/200284684): Remove this class.
 object ViewRootSync {
-    // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed
-    private val forceDisableSynchronization = ActivityManager.isLowRamDeviceStatic()
     private var surfaceSyncer: SurfaceSyncer? = null
 
     /**
@@ -23,8 +20,7 @@
         otherView: View,
         then: () -> Unit
     ) {
-        if (forceDisableSynchronization ||
-            !view.isAttachedToWindow || view.viewRootImpl == null ||
+        if (!view.isAttachedToWindow || view.viewRootImpl == null ||
             !otherView.isAttachedToWindow || otherView.viewRootImpl == null ||
             view.viewRootImpl == otherView.viewRootImpl) {
             // No need to synchronize if either the touch surface or dialog view is not attached