API: add smooth switch in setFrameRateCategory

This allows the platform to have a different behavior depending on
whether a device is MRR or dVRR. When the bool is `true`, MRR devices
(those with DisplayModes that do not have vrr config) will not change
frame rates if it would cause jank. The expected usage is to mark the
bool true when an animation is running.

Bug: 300491171
Test: atest libsurfaceflinger_unittest
Change-Id: I4b28e5b100b7deecda27d4c9134e734bc9c51b47
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 139c0be..ed19d1e 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -263,7 +263,7 @@
     private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj,
             long nativeObject, int compatibility);
     private static native void nativeSetFrameRateCategory(
-            long transactionObj, long nativeObject, int category);
+            long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly);
     private static native void nativeSetFrameRateSelectionStrategy(
             long transactionObj, long nativeObject, int strategy);
     private static native long nativeGetHandle(long nativeObject);
@@ -3701,6 +3701,10 @@
          * @param sc The SurfaceControl to specify the frame rate category of.
          * @param category The frame rate category of this surface. The category value may influence
          * the system's choice of display frame rate.
+         * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not
+         * change if changing it would cause jank. Else {@code false}.
+         * This parameter is ignored when {@code category} is
+         * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}.
          *
          * @return This transaction object.
          *
@@ -3709,10 +3713,10 @@
          * @hide
          */
         @NonNull
-        public Transaction setFrameRateCategory(
-                @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) {
+        public Transaction setFrameRateCategory(@NonNull SurfaceControl sc,
+                @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) {
             checkPreconditions(sc);
-            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category);
+            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly);
             return this;
         }
 
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index 07ac292..2736b68 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -211,7 +211,11 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(
+                    displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
             transactionChanged = true;
             Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
                     + session.reason, session.traceCookie);
@@ -251,7 +255,11 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_SELF);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
+                    /* smoothSwitchOnly= */ false);
             transactionChanged = true;
             Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
                     session.traceCookie);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f79dbe7..8d97aa6 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -962,10 +962,11 @@
 }
 
 static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj,
-                                       jlong nativeObject, jint category) {
+                                       jlong nativeObject, jint category,
+                                       jboolean smoothSwitchOnly) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
-    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category));
+    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly);
 }
 
 static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2179,7 +2180,7 @@
             (void*)nativeSetFrameRate },
     {"nativeSetDefaultFrameRateCompatibility", "(JJI)V",
             (void*)nativeSetDefaultFrameRateCompatibility},
-    {"nativeSetFrameRateCategory", "(JJI)V",
+    {"nativeSetFrameRateCategory", "(JJIZ)V",
             (void*)nativeSetFrameRateCategory},
     {"nativeSetFrameRateSelectionStrategy", "(JJI)V",
             (void*)nativeSetFrameRateSelectionStrategy},
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 263e563..6229530 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -154,7 +155,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -171,7 +173,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -241,7 +244,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -261,7 +265,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -281,7 +286,8 @@
                     eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
             verify(mTransaction).setFrameRateCategory(
                     eq(mDefaultDisplayRoot),
-                    eq(FRAME_RATE_CATEGORY_DEFAULT));
+                    eq(FRAME_RATE_CATEGORY_DEFAULT),
+                    eq(false));
             verify(mTransaction).setEarlyWakeupEnd();
             verify(mTransaction).applyAsyncUnsafe();
             verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -299,7 +305,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -310,7 +317,7 @@
                 mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON);
         // Verify we never call SF and perf manager since session1 is already running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -318,7 +325,7 @@
         session2.close();
         // Verify we have not cleaned up because session1 is still running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -330,7 +337,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -348,7 +356,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -363,7 +372,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -378,13 +388,15 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction, never()).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
                 anyInt());
         verify(mTransaction, never()).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                anyInt());
+                anyInt(),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -401,7 +413,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 79348d1..ae7c2a9 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -247,7 +247,7 @@
 
             int rc = 0;
             try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
-                transaction.setFrameRateCategory(mSurfaceControl, category);
+                transaction.setFrameRateCategory(mSurfaceControl, category, false);
                 transaction.apply();
             }
             return rc;