Add setFrameRate compatibility value "GTE"

Allows opinionated users to request a frame rate greater than or equal
to the frameRate value. This is also useful for toolkit scroll.

Bug: 306080972
Test: atest SetFrameRateTest
Test: atest CtsSurfaceControlTestsStaging
Change-Id: I72cf29e14b7b8ddc9b3dba4da4218b1dd90b3bba
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index d0a4d1a..ad0bf7c 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -197,7 +197,9 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
-            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
+            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    FRAME_RATE_COMPATIBILITY_EXACT, FRAME_RATE_COMPATIBILITY_NO_VOTE,
+                    FRAME_RATE_COMPATIBILITY_MIN, FRAME_RATE_COMPATIBILITY_GTE})
     public @interface FrameRateCompatibility {}
 
     // From native_window.h. Keep these in sync.
@@ -242,6 +244,13 @@
      */
     public static final int FRAME_RATE_COMPATIBILITY_MIN = 102;
 
+    // From window.h. Keep these in sync.
+    /**
+     * The surface requests a frame rate that is greater than or equal to {@code frameRate}.
+     * @hide
+     */
+    public static final int FRAME_RATE_COMPATIBILITY_GTE = 103;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CHANGE_FRAME_RATE_"},
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 1e5f33f..60b5ce7 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -235,13 +235,17 @@
         }
 
         public int setFrameRate(float frameRate) {
+            return setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+        }
+
+        public int setFrameRate(
+                float frameRate, @Surface.FrameRateCompatibility int compatibility) {
             Log.i(TAG,
                     String.format("Setting frame rate for %s: frameRate=%.2f", mName, frameRate));
 
             int rc = 0;
             try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
-                transaction.setFrameRate(
-                        mSurfaceControl, frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+                transaction.setFrameRate(mSurfaceControl, frameRate, compatibility);
                 transaction.apply();
             }
             return rc;
@@ -668,12 +672,34 @@
         }
     }
 
-    private void testSurfaceControlFrameRateCategoryInternal(int category)
-            throws InterruptedException {
+    private void testSurfaceControlFrameRateCompatibilityInternal(
+            @Surface.FrameRateCompatibility int compatibility) throws InterruptedException {
+        runOneSurfaceTest((TestSurface surface) -> {
+            Log.i(TAG,
+                    "**** Running testSurfaceControlFrameRateCompatibility with compatibility "
+                            + compatibility);
+
+            float expectedFrameRate = getExpectedFrameRateForCompatibility(compatibility);
+            int initialNumEvents = mModeChangedEvents.size();
+            surface.setFrameRate(30.f, compatibility);
+            verifyExactAndStableFrameRate(expectedFrameRate, surface);
+            verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
+        });
+    }
+
+    public void testSurfaceControlFrameRateCompatibility(
+            @Surface.FrameRateCompatibility int compatibility) throws InterruptedException {
+        runTestsWithPreconditions(
+                () -> testSurfaceControlFrameRateCompatibilityInternal(compatibility),
+                "frame rate compatibility=" + compatibility);
+    }
+
+    private void testSurfaceControlFrameRateCategoryInternal(
+            @Surface.FrameRateCategory int category) throws InterruptedException {
         runOneSurfaceTest((TestSurface surface) -> {
             Log.i(TAG, "**** Running testSurfaceControlFrameRateCategory for category " + category);
 
-            float expectedFrameRate = getExpectedFrameRate(category);
+            float expectedFrameRate = getExpectedFrameRateForCategory(category);
             int initialNumEvents = mModeChangedEvents.size();
             surface.setFrameRateCategory(category);
             verifyCompatibleAndStableFrameRate(expectedFrameRate, surface);
@@ -681,7 +707,8 @@
         });
     }
 
-    public void testSurfaceControlFrameRateCategory(int category) throws InterruptedException {
+    public void testSurfaceControlFrameRateCategory(@Surface.FrameRateCategory int category)
+            throws InterruptedException {
         runTestsWithPreconditions(()
                 -> testSurfaceControlFrameRateCategoryInternal(category),
                 "frame rate category=" + category);
@@ -744,7 +771,24 @@
                 "frame rate strategy=" + parentStrategy);
     }
 
-    private float getExpectedFrameRate(int category) {
+    private float getExpectedFrameRateForCompatibility(int compatibility) {
+        assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility "
+                        + compatibility,
+                compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE);
+
+        Display display = getDisplay();
+        Optional<Float> expectedFrameRate = getRefreshRates(display.getMode(), display)
+                                                    .stream()
+                                                    .filter(rate -> rate >= 30.f)
+                                                    .min(Comparator.naturalOrder());
+
+        assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate "
+                        + "is >= 30",
+                expectedFrameRate.isPresent());
+        return expectedFrameRate.get();
+    }
+
+    private float getExpectedFrameRateForCategory(int category) {
         Display display = getDisplay();
         List<Float> frameRates = getRefreshRates(display.getMode(), display);
 
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index 29f6879..4b56c10 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -81,6 +81,12 @@
     }
 
     @Test
+    public void testSurfaceControlFrameRateCompatibilityGte() throws InterruptedException {
+        GraphicsActivity activity = mActivityRule.getActivity();
+        activity.testSurfaceControlFrameRateCompatibility(Surface.FRAME_RATE_COMPATIBILITY_GTE);
+    }
+
+    @Test
     public void testSurfaceControlFrameRateCategoryHigh() throws InterruptedException {
         GraphicsActivity activity = mActivityRule.getActivity();
         activity.testSurfaceControlFrameRateCategory(Surface.FRAME_RATE_CATEGORY_HIGH);