Early wake-up for transitions (2/2)

On some devices it's very likely that we fall into GL comp during
app transitions. However, SF offsets are chosen in a way such that
the time to finish a frame is just too tight to be completely jank
free when hitting GL composition in SurfaceFlinger. Thus, we
introduce the concept of a separate early offset, and wakeup
SurfaceFlinger at that time if we think that hitting GL comp is
likely, or we already hit GL comp in the last frame.

Test: Open app, check vsync offsets in systrace
Test: Open many dialogs/apps to fall into GPU comp.
Bug: 75985430
Change-Id: I461fdcd573583f3ea0348c8b23cc9945d33f8976
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d4610a5..5deee11 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -87,6 +87,7 @@
     private static native void nativeMergeTransaction(long transactionObj,
             long otherTransactionObj);
     private static native void nativeSetAnimationTransaction(long transactionObj);
+    private static native void nativeSetEarlyWakeup(long transactionObj);
 
     private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
     private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
@@ -1642,6 +1643,19 @@
         }
 
         /**
+         * Indicate that SurfaceFlinger should wake up earlier than usual as a result of this
+         * transaction. This should be used when the caller thinks that the scene is complex enough
+         * that it's likely to hit GL composition, and thus, SurfaceFlinger needs to more time in
+         * order not to miss frame deadlines.
+         * <p>
+         * Corresponds to setting ISurfaceComposer::eEarlyWakeup
+         */
+        public Transaction setEarlyWakeup() {
+            nativeSetEarlyWakeup(mNativeObject);
+            return this;
+        }
+
+        /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
          */
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b0f68cd..3f58afa 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -327,6 +327,11 @@
     transaction->setAnimationTransaction();
 }
 
+static void nativeSetEarlyWakeup(JNIEnv* env, jclass clazz, jlong transactionObj) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    transaction->setEarlyWakeup();
+}
+
 static void nativeSetLayer(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jint zorder) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -934,6 +939,8 @@
             (void*)nativeMergeTransaction },
     {"nativeSetAnimationTransaction", "(J)V",
             (void*)nativeSetAnimationTransaction },
+    {"nativeSetEarlyWakeup", "(J)V",
+            (void*)nativeSetEarlyWakeup },
     {"nativeSetLayer", "(JJI)V",
             (void*)nativeSetLayer },
     {"nativeSetRelativeLayer", "(JJLandroid/os/IBinder;I)V",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
index c82c519..9975c41 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -101,6 +101,11 @@
         return this;
     }
 
+    public TransactionCompat setEarlyWakeup() {
+        mTransaction.setEarlyWakeup();
+        return this;
+    }
+
     public TransactionCompat setColor(SurfaceControlCompat surfaceControl, float[] color) {
         mTransaction.setColor(surfaceControl.mSurfaceControl, color);
         return this;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 3bbfe3c..b8bce95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -238,6 +238,7 @@
                 t.deferTransactionUntilSurface(app.leash, systemUiSurface,
                         systemUiSurface.getNextFrameNumber());
             }
+            t.setEarlyWakeup();
             t.apply();
         }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 8fa94fb..9a4db65 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1714,7 +1714,8 @@
                     adapter = new LocalAnimationAdapter(
                             new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
                                     mService.mAppTransition.canSkipFirstFrame(),
-                                    mService.mAppTransition.getAppStackClipMode()),
+                                    mService.mAppTransition.getAppStackClipMode(),
+                                    true /* isAppAnimation */),
                             mService.mSurfaceAnimationRunner);
                     if (a.getZAdjustment() == Animation.ZORDER_TOP) {
                         mNeedsZBoost = true;
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 529aacc..d89d6f0 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -146,6 +146,13 @@
             return false;
         }
 
+        /**
+         * @return {@code true} if we need to wake-up SurfaceFlinger earlier during this animation.
+         *
+         * @see Transaction#setEarlyWakeup
+         */
+        default boolean needsEarlyWakeup() { return false; }
+
         void dump(PrintWriter pw, String prefix);
 
         default void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 98fcb0b..7211533 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -220,6 +220,9 @@
     }
 
     private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
+        if (a.mAnimSpec.needsEarlyWakeup()) {
+            t.setEarlyWakeup();
+        }
         a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 7b7cb30..548e23a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -47,21 +47,24 @@
     private final Point mPosition = new Point();
     private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
     private final boolean mCanSkipFirstFrame;
+    private final boolean mIsAppAnimation;
     private final Rect mStackBounds = new Rect();
     private int mStackClipMode;
     private final Rect mTmpRect = new Rect();
 
     public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame)  {
-        this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE);
+        this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE,
+                false /* isAppAnimation */);
     }
 
     public WindowAnimationSpec(Animation animation, Point position, Rect stackBounds,
-            boolean canSkipFirstFrame, int stackClipMode) {
+            boolean canSkipFirstFrame, int stackClipMode, boolean isAppAnimation) {
         mAnimation = animation;
         if (position != null) {
             mPosition.set(position.x, position.y);
         }
         mCanSkipFirstFrame = canSkipFirstFrame;
+        mIsAppAnimation = isAppAnimation;
         mStackClipMode = stackClipMode;
         if (stackBounds != null) {
             mStackBounds.set(stackBounds);
@@ -135,6 +138,11 @@
     }
 
     @Override
+    public boolean needsEarlyWakeup() {
+        return mIsAppAnimation;
+    }
+
+    @Override
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.println(mAnimation);
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java
index 794d033..ca520ed 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -56,7 +56,8 @@
         Rect windowCrop = new Rect(0, 0, 20, 20);
         Animation a = createClipRectAnimation(windowCrop, windowCrop);
         WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
-                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_NONE);
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_NONE,
+                true /* isAppAnimation */);
         windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
         verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
                 argThat(rect -> rect.equals(windowCrop)));
@@ -65,7 +66,8 @@
     @Test
     public void testApply_clipAfter() {
         WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
-                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_AFTER_ANIM);
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_AFTER_ANIM,
+                true /* isAppAnimation */);
         windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
         verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
         verify(mTransaction).setFinalCrop(eq(mSurfaceControl),
@@ -77,7 +79,8 @@
         // Stack bounds is (0, 0, 10, 10) position is (20, 40)
         WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation,
                 new Point(20, 40), mStackBounds, false /* canSkipFirstFrame */,
-                STACK_CLIP_AFTER_ANIM);
+                STACK_CLIP_AFTER_ANIM,
+                true /* isAppAnimation */);
         windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
         verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
         verify(mTransaction).setFinalCrop(eq(mSurfaceControl),
@@ -89,7 +92,8 @@
     public void testApply_clipBeforeNoAnimationBounds() {
         // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0)
         WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
-                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
         windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
         verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
                 argThat(rect -> rect.equals(mStackBounds)));
@@ -102,7 +106,8 @@
         Animation a = createClipRectAnimation(windowCrop, windowCrop);
         a.initialize(0, 0, 0, 0);
         WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
-                null, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+                null, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
         windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
         verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
     }
@@ -113,7 +118,8 @@
         Rect windowCrop = new Rect(0, 0, 5, 5);
         Animation a = createClipRectAnimation(windowCrop, windowCrop);
         WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
-                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
         windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
         verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
                 argThat(rect -> rect.equals(windowCrop)));
@@ -125,7 +131,8 @@
         Rect windowCrop = new Rect(0, 0, 20, 20);
         Animation a = createClipRectAnimation(windowCrop, windowCrop);
         WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
-                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM);
+                mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM,
+                true /* isAppAnimation */);
         windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
         verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
                 argThat(rect -> rect.equals(mStackBounds)));