• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics.cts;
18 
19 import static android.system.OsConstants.EINVAL;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertNotSame;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 
27 import android.app.Activity;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.Rect;
31 import android.hardware.display.DisplayManager;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.util.Log;
36 import android.view.Display;
37 import android.view.Surface;
38 import android.view.SurfaceControl;
39 import android.view.SurfaceHolder;
40 import android.view.SurfaceView;
41 import android.view.ViewGroup;
42 
43 import com.android.compatibility.common.util.DisplayUtil;
44 
45 import com.google.common.primitives.Floats;
46 
47 import java.io.PrintWriter;
48 import java.io.StringWriter;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.List;
52 
53 /**
54  * An Activity to help with frame rate testing.
55  */
56 public class FrameRateCtsActivity extends Activity {
57     static {
58         System.loadLibrary("ctsgraphics_jni");
59     }
60 
61     private static final String TAG = "FrameRateCtsActivity";
62     private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS = 4;
63     private static final long STABLE_FRAME_RATE_WAIT_SECONDS = 1;
64     private static final long POST_BUFFER_INTERVAL_MILLIS = 500;
65     private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
66     private static final long PRECONDITION_WAIT_TIMEOUT_SECONDS = 20;
67     private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS = 3;
68     private static final float FRAME_RATE_TOLERANCE_STRICT = 0.01f;
69     // Keep this value in sync with RefreshRateSelector::MARGIN_FOR_PERIOD_CALCULATION.
70     private static final long MARGIN_FOR_PERIOD_CALCULATION_NS = 800000;
71 
72     // Tolerance which doesn't differentiate between the fractional refresh rate pairs, e.g.
73     // 59.94 and 60 will be considered the same refresh rate.
74     // Use this tolerance to verify the refresh rate after calling setFrameRate with
75     // {@Surface.FRAME_RATE_COMPATIBILITY_DEFAULT}.
76     private static final float FRAME_RATE_TOLERANCE_RELAXED = 0.1f;
77 
78     private DisplayManager mDisplayManager;
79     private SurfaceView mSurfaceView;
80     private Handler mHandler = new Handler(Looper.getMainLooper());
81     private final Object mLock = new Object();
82     private Surface mSurface = null;
83     private float mDeviceFrameRate;
84     private ModeChangedEvents mModeChangedEvents = new ModeChangedEvents();
85 
86     private enum ActivityState { RUNNING, PAUSED, DESTROYED }
87 
88     private ActivityState mActivityState;
89 
90     SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
91         @Override
92         public void surfaceCreated(SurfaceHolder holder) {
93             synchronized (mLock) {
94                 mSurface = holder.getSurface();
95                 mLock.notify();
96             }
97         }
98 
99         @Override
100         public void surfaceDestroyed(SurfaceHolder holder) {
101             synchronized (mLock) {
102                 mSurface = null;
103                 mLock.notify();
104             }
105         }
106 
107         @Override
108         public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
109     };
110 
111     DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
112         @Override
113         public void onDisplayAdded(int displayId) {}
114 
115         @Override
116         public void onDisplayChanged(int displayId) {
117             if (displayId != Display.DEFAULT_DISPLAY) {
118                 return;
119             }
120             synchronized (mLock) {
121                 Display.Mode mode = mDisplayManager.getDisplay(displayId).getMode();
122                 mModeChangedEvents.add(mode);
123                 float frameRate =  mode.getRefreshRate();
124                 if (frameRate != mDeviceFrameRate) {
125                     Log.i(TAG,
126                             String.format("Frame rate changed: %.2f --> %.2f", mDeviceFrameRate,
127                                     frameRate));
128                     mDeviceFrameRate = frameRate;
129                     mLock.notify();
130                 }
131             }
132         }
133 
134         @Override
135         public void onDisplayRemoved(int displayId) {}
136     };
137 
138     // Wrapper around ArrayList for which the only allowed mutable operation is add().
139     // We use this to store all mode change events during a test. When we need to iterate over
140     // all mode changes during a certain operation, we use the number of events in the beginning
141     // and in the end. It's important to never clear or modify the elements in this list hence the
142     // wrapper.
143     private static class ModeChangedEvents {
144         private List<Display.Mode> mEvents = new ArrayList<>();
145 
add(Display.Mode mode)146         public void add(Display.Mode mode) {
147             mEvents.add(mode);
148         }
149 
get(int i)150         public Display.Mode get(int i) {
151             return mEvents.get(i);
152         }
153 
size()154         public int size() {
155             return mEvents.size();
156         }
157     }
158 
159     private static class PreconditionViolatedException extends RuntimeException {
PreconditionViolatedException()160         PreconditionViolatedException() {}
161     }
162 
163     private static class FrameRateTimeoutException extends RuntimeException {
FrameRateTimeoutException(float expectedFrameRate, float deviceFrameRate)164         FrameRateTimeoutException(float expectedFrameRate, float deviceFrameRate) {
165             this.expectedFrameRate = expectedFrameRate;
166             this.deviceFrameRate = deviceFrameRate;
167         }
168 
169         public float expectedFrameRate;
170         public float deviceFrameRate;
171     }
172 
173     public enum Api {
174         SURFACE("Surface"),
175         ANATIVE_WINDOW("ANativeWindow"),
176         SURFACE_CONTROL("SurfaceControl"),
177         NATIVE_SURFACE_CONTROL("ASurfaceControl");
178 
179         private final String mName;
Api(String name)180         Api(String name) {
181             mName = name;
182         }
183 
toString()184         public String toString() {
185             return mName;
186         }
187     }
188 
189     private static class TestSurface {
190         private Api mApi;
191         private String mName;
192         private SurfaceControl mSurfaceControl;
193         private Surface mSurface;
194         private long mNativeSurfaceControl;
195         private int mColor;
196         private boolean mLastBufferPostTimeValid;
197         private long mLastBufferPostTime;
198         private boolean mUseArrVersionApi;
199 
TestSurface(Api api, SurfaceControl parentSurfaceControl, Surface parentSurface, String name, Rect destFrame, boolean visible, int color, boolean useArrVersionApi)200         TestSurface(Api api, SurfaceControl parentSurfaceControl, Surface parentSurface,
201                 String name, Rect destFrame, boolean visible, int color, boolean useArrVersionApi) {
202             mApi = api;
203             mName = name;
204             mColor = color;
205             mUseArrVersionApi = useArrVersionApi;
206 
207             if (mApi == Api.SURFACE || mApi == Api.ANATIVE_WINDOW || mApi == Api.SURFACE_CONTROL) {
208                 assertNotNull("No parent surface", parentSurfaceControl);
209                 mSurfaceControl = new SurfaceControl.Builder()
210                                           .setParent(parentSurfaceControl)
211                                           .setName(mName)
212                                           .setBufferSize(destFrame.right - destFrame.left,
213                                                   destFrame.bottom - destFrame.top)
214                                           .build();
215                 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
216                     transaction.setGeometry(mSurfaceControl, null, destFrame, Surface.ROTATION_0)
217                             .apply();
218                 }
219                 mSurface = new Surface(mSurfaceControl);
220             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
221                 assertNotNull("No parent surface", parentSurface);
222                 mNativeSurfaceControl = nativeSurfaceControlCreate(parentSurface, mName,
223                         destFrame.left, destFrame.top, destFrame.right, destFrame.bottom);
224                 assertTrue("Failed to create a native SurfaceControl", mNativeSurfaceControl != 0);
225             }
226 
227             setVisibility(visible);
228             postBuffer();
229         }
230 
createFrameRateParams( float frameRate, int compatibility, int changeFrameRateStrategy)231         private Surface.FrameRateParams createFrameRateParams(
232                 float frameRate, int compatibility, int changeFrameRateStrategy) {
233             Surface.FrameRateParams.Builder frameRateParamsBuilder =
234                     new Surface.FrameRateParams.Builder().setChangeFrameRateStrategy(
235                             changeFrameRateStrategy);
236             if (compatibility == Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) {
237                 frameRateParamsBuilder.setDesiredRateRange(frameRate, Float.MAX_VALUE);
238             } else if (compatibility == Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) {
239                 frameRateParamsBuilder.setFixedSourceRate(frameRate);
240             } else {
241                 fail("Invalid compatibility for test: " + compatibility);
242             }
243             return frameRateParamsBuilder.build();
244         }
245 
setFrameRate(float frameRate, int compatibility, int changeFrameRateStrategy)246         public int setFrameRate(float frameRate, int compatibility, int changeFrameRateStrategy) {
247             Log.i(TAG,
248                     String.format("Setting frame rate for %s: fps=%.2f compatibility=%s", mName,
249                             frameRate, frameRateCompatibilityToString(compatibility)));
250 
251             int rc = 0;
252             if (mApi == Api.SURFACE) {
253                 if (mUseArrVersionApi) {
254                     mSurface.setFrameRate(createFrameRateParams(
255                             frameRate, compatibility, changeFrameRateStrategy));
256                 } else {
257                     if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
258                         mSurface.setFrameRate(frameRate, compatibility);
259                     } else {
260                         mSurface.setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
261                     }
262                 }
263             } else if (mApi == Api.ANATIVE_WINDOW) {
264                 rc = nativeWindowSetFrameRate(mSurface, frameRate, compatibility,
265                         changeFrameRateStrategy);
266             } else if (mApi == Api.SURFACE_CONTROL) {
267                 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
268                     if (mUseArrVersionApi) {
269                         Surface.FrameRateParams params = createFrameRateParams(
270                                 frameRate, compatibility, changeFrameRateStrategy);
271                         transaction.setFrameRate(mSurfaceControl, params);
272                     } else {
273                         if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
274                             transaction
275                                     .setFrameRate(mSurfaceControl, frameRate, compatibility);
276                         } else {
277                             transaction
278                                     .setFrameRate(mSurfaceControl, frameRate, compatibility,
279                                             changeFrameRateStrategy);
280                         }
281                     }
282                     transaction.apply();
283                 }
284             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
285                 nativeSurfaceControlSetFrameRate(mNativeSurfaceControl, frameRate, compatibility,
286                         changeFrameRateStrategy);
287             }
288             return rc;
289         }
290 
clearFrameRate()291         public int clearFrameRate() {
292             Log.i(TAG,
293                     String.format("Clearing frame rate for %s", mName));
294             int rc = 0;
295             if (mApi == Api.SURFACE) {
296                 mSurface.clearFrameRate();
297             } else if (mApi == Api.ANATIVE_WINDOW) {
298                 rc = nativeWindowClearFrameRate(mSurface);
299             } else if (mApi == Api.SURFACE_CONTROL) {
300                 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
301                     transaction.clearFrameRate(mSurfaceControl);
302                     transaction.apply();
303                 }
304             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
305                 nativeSurfaceControlClearFrameRate(mNativeSurfaceControl);
306             }
307             return rc;
308         }
309 
setInvalidFrameRate(float frameRate, int compatibility, int changeFrameRateStrategy)310         public void setInvalidFrameRate(float frameRate, int compatibility,
311                 int changeFrameRateStrategy) {
312             if (mApi == Api.SURFACE) {
313                 boolean caughtIllegalArgException = false;
314                 try {
315                     setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
316                 } catch (IllegalArgumentException exc) {
317                     caughtIllegalArgException = true;
318                 }
319                 assertTrue("Expected an IllegalArgumentException from invalid call to"
320                                 + " Surface.setFrameRate()",
321                         caughtIllegalArgException);
322             } else {
323                 int rc = setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
324                 if (mApi == Api.ANATIVE_WINDOW) {
325                     assertEquals("Expected -EINVAL return value from invalid call to"
326                             + " ANativeWindow_setFrameRate()", rc, -EINVAL);
327                 }
328             }
329         }
330 
setVisibility(boolean visible)331         public void setVisibility(boolean visible) {
332             Log.i(TAG,
333                     String.format("Setting visibility for %s: %s", mName,
334                             visible ? "visible" : "hidden"));
335             if (mApi == Api.SURFACE || mApi == Api.ANATIVE_WINDOW || mApi == Api.SURFACE_CONTROL) {
336                 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
337                     transaction.setVisibility(mSurfaceControl, visible).apply();
338                 }
339             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
340                 nativeSurfaceControlSetVisibility(mNativeSurfaceControl, visible);
341             }
342         }
343 
postBuffer()344         public void postBuffer() {
345             mLastBufferPostTimeValid = true;
346             mLastBufferPostTime = System.nanoTime();
347             if (mApi == Api.SURFACE || mApi == Api.ANATIVE_WINDOW || mApi == Api.SURFACE_CONTROL) {
348                 Canvas canvas = mSurface.lockHardwareCanvas();
349                 canvas.drawColor(mColor);
350                 mSurface.unlockCanvasAndPost(canvas);
351             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
352                 assertTrue("Posting a buffer failed",
353                         nativeSurfaceControlPostBuffer(mNativeSurfaceControl, mColor));
354             }
355         }
356 
getLastBufferPostTime()357         public long getLastBufferPostTime() {
358             assertTrue("No buffer posted yet", mLastBufferPostTimeValid);
359             return mLastBufferPostTime;
360         }
361 
release()362         public void release() {
363             if (mSurface != null) {
364                 mSurface.release();
365                 mSurface = null;
366             }
367             if (mSurfaceControl != null) {
368                 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
369                     transaction.reparent(mSurfaceControl, null).apply();
370                 }
371                 mSurfaceControl.release();
372                 mSurfaceControl = null;
373             }
374             if (mNativeSurfaceControl != 0) {
375                 nativeSurfaceControlDestroy(mNativeSurfaceControl);
376                 mNativeSurfaceControl = 0;
377             }
378         }
379 
380         @Override
finalize()381         protected void finalize() throws Throwable {
382             try {
383                 release();
384             } finally {
385                 super.finalize();
386             }
387         }
388     }
389 
frameRateCompatibilityToString(int compatibility)390     private static String frameRateCompatibilityToString(int compatibility) {
391         switch (compatibility) {
392             case Surface.FRAME_RATE_COMPATIBILITY_DEFAULT:
393                 return "default";
394             case Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
395                 return "fixed_source";
396             default:
397                 return "invalid(" + compatibility + ")";
398         }
399     }
400 
401     @Override
onCreate(Bundle savedInstanceState)402     protected void onCreate(Bundle savedInstanceState) {
403         super.onCreate(savedInstanceState);
404         synchronized (mLock) {
405             mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
406             Display.Mode mode = getDisplay().getMode();
407             mDeviceFrameRate = mode.getRefreshRate();
408             // Insert the initial mode so we have the full display mode history.
409             mModeChangedEvents.add(mode);
410             mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
411             mSurfaceView = new SurfaceView(this);
412             mSurfaceView.setWillNotDraw(false);
413             mSurfaceView.setZOrderOnTop(true);
414             setContentView(mSurfaceView,
415                     new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
416                             ViewGroup.LayoutParams.MATCH_PARENT));
417             mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
418         }
419     }
420 
421     @Override
onDestroy()422     protected void onDestroy() {
423         super.onDestroy();
424         mDisplayManager.unregisterDisplayListener(mDisplayListener);
425         synchronized (mLock) {
426             mActivityState = ActivityState.DESTROYED;
427             mLock.notify();
428         }
429     }
430 
431     @Override
onPause()432     public void onPause() {
433         super.onPause();
434         synchronized (mLock) {
435             mActivityState = ActivityState.PAUSED;
436             mLock.notify();
437         }
438     }
439 
440     @Override
onResume()441     public void onResume() {
442         super.onResume();
443         synchronized (mLock) {
444             mActivityState = ActivityState.RUNNING;
445             mLock.notify();
446         }
447     }
448 
449     // Returns the refresh rates with the same resolution as "mode".
getRefreshRates(Display.Mode mode, Display display)450     private ArrayList<Float> getRefreshRates(Display.Mode mode, Display display) {
451         Display.Mode[] modes = display.getSupportedModes();
452         ArrayList<Float> frameRates = new ArrayList<>();
453         for (Display.Mode supportedMode : modes) {
454             if (hasSameResolution(supportedMode, mode)) {
455                 frameRates.add(supportedMode.getRefreshRate());
456             }
457         }
458         Collections.sort(frameRates);
459         ArrayList<Float> uniqueFrameRates = new ArrayList<>();
460         for (float frameRate : frameRates) {
461             if (uniqueFrameRates.isEmpty()
462                     || frameRate - uniqueFrameRates.get(uniqueFrameRates.size() - 1)
463                             >= FRAME_RATE_TOLERANCE_STRICT) {
464                 uniqueFrameRates.add(frameRate);
465             }
466         }
467         return uniqueFrameRates;
468     }
469 
getSeamedRefreshRates(Display.Mode mode, Display display)470     private List<Float> getSeamedRefreshRates(Display.Mode mode, Display display) {
471         List<Float> seamedRefreshRates = new ArrayList<>();
472         Display.Mode[] modes = display.getSupportedModes();
473         for (Display.Mode otherMode : modes) {
474             if (hasSameResolution(mode, otherMode)
475                     && !DisplayUtil.isModeSwitchSeamless(mode, otherMode)) {
476                 seamedRefreshRates.add(otherMode.getRefreshRate());
477             }
478         }
479         return seamedRefreshRates;
480     }
481 
hasSameResolution(Display.Mode mode1, Display.Mode mode2)482     private boolean hasSameResolution(Display.Mode mode1, Display.Mode mode2) {
483         return mode1.getPhysicalHeight() == mode2.getPhysicalHeight()
484                 && mode1.getPhysicalWidth() == mode2.getPhysicalWidth();
485     }
486 
isFrameRateMultiple( float higherFrameRate, float lowerFrameRate, float tolerance)487     private boolean isFrameRateMultiple(
488             float higherFrameRate, float lowerFrameRate, float tolerance) {
489         float multiple = higherFrameRate / lowerFrameRate;
490         int roundedMultiple = Math.round(multiple);
491         return roundedMultiple > 0
492                 && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate) <= tolerance;
493     }
494 
495     // Returns two device-supported frame rates that aren't multiples of each other, or null if no
496     // such incompatible frame rates are available. This is useful for testing behavior where we
497     // have layers with conflicting frame rates.
getIncompatibleFrameRates(Display display)498     private float[] getIncompatibleFrameRates(Display display) {
499         ArrayList<Float> frameRates = getRefreshRates(display.getMode(), display);
500         for (int i = 0; i < frameRates.size(); i++) {
501             for (int j = i + 1; j < frameRates.size(); j++) {
502                 if (!isFrameRateMultiple(Math.max(frameRates.get(i), frameRates.get(j)),
503                             Math.min(frameRates.get(i), frameRates.get(j)),
504                             FRAME_RATE_TOLERANCE_RELAXED)) {
505                     return new float[] {frameRates.get(i), frameRates.get(j)};
506                 }
507             }
508         }
509         return null;
510     }
511 
512     // Waits until our SurfaceHolder has a surface and the activity is resumed.
waitForPreconditions()513     private void waitForPreconditions() throws InterruptedException {
514         assertNotSame("Activity was unexpectedly destroyed", mActivityState,
515                 ActivityState.DESTROYED);
516         if (mSurface == null || mActivityState != ActivityState.RUNNING) {
517             Log.i(TAG,
518                     String.format(
519                             "Waiting for preconditions. Have surface? %b. Activity resumed? %b.",
520                             mSurface != null, mActivityState == ActivityState.RUNNING));
521         }
522         long nowNanos = System.nanoTime();
523         long endTimeNanos = nowNanos + PRECONDITION_WAIT_TIMEOUT_SECONDS * 1_000_000_000L;
524         while (mSurface == null || mActivityState != ActivityState.RUNNING) {
525             long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
526             assertTrue(String.format("Timed out waiting for preconditions. Have surface? %b."
527                                        + " Activity resumed? %b.",
528                                mSurface != null, mActivityState == ActivityState.RUNNING),
529                     timeRemainingMillis > 0);
530             mLock.wait(timeRemainingMillis);
531             assertNotSame("Activity was unexpectedly destroyed", mActivityState,
532                     ActivityState.DESTROYED);
533             nowNanos = System.nanoTime();
534         }
535         // Make sure any previous mode changes are completed.
536         waitForStableFrameRate();
537     }
538 
539     // Returns true if we encounter a precondition violation, false otherwise.
waitForPreconditionViolation()540     private boolean waitForPreconditionViolation() throws InterruptedException {
541         assertNotSame("Activity was unexpectedly destroyed", mActivityState,
542                 ActivityState.DESTROYED);
543         long nowNanos = System.nanoTime();
544         long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS * 1_000_000_000L;
545         while (mSurface != null && mActivityState == ActivityState.RUNNING) {
546             long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
547             if (timeRemainingMillis <= 0) {
548                 break;
549             }
550             mLock.wait(timeRemainingMillis);
551             assertNotSame("Activity was unexpectedly destroyed", mActivityState,
552                     ActivityState.DESTROYED);
553             nowNanos = System.nanoTime();
554         }
555         return mSurface == null || mActivityState != ActivityState.RUNNING;
556     }
557 
verifyPreconditions()558     private void verifyPreconditions() {
559         if (mSurface == null || mActivityState != ActivityState.RUNNING) {
560             throw new PreconditionViolatedException();
561         }
562     }
563 
564     // Returns true if we reached waitUntilNanos, false if some other event occurred.
waitForEvents(long waitUntilNanos, TestSurface[] surfaces)565     private boolean waitForEvents(long waitUntilNanos, TestSurface[] surfaces)
566             throws InterruptedException {
567         int numModeChangedEvents = mModeChangedEvents.size();
568         long nowNanos = System.nanoTime();
569         while (nowNanos < waitUntilNanos) {
570             long surfacePostTime = Long.MAX_VALUE;
571             for (TestSurface surface : surfaces) {
572                 surfacePostTime = Math.min(surfacePostTime,
573                         surface.getLastBufferPostTime()
574                                 + (POST_BUFFER_INTERVAL_MILLIS * 1_000_000L));
575             }
576             long timeoutNs = Math.min(waitUntilNanos, surfacePostTime) - nowNanos;
577             long timeoutMs = timeoutNs / 1_000_000L;
578             int remainderNs = (int) (timeoutNs % 1_000_000L);
579             // Don't call wait(0, 0) - it blocks indefinitely.
580             if (timeoutMs > 0 || remainderNs > 0) {
581                 mLock.wait(timeoutMs, remainderNs);
582             }
583             nowNanos = System.nanoTime();
584             verifyPreconditions();
585             if (mModeChangedEvents.size() > numModeChangedEvents) {
586                 return false;
587             }
588             if (nowNanos >= surfacePostTime) {
589                 for (TestSurface surface : surfaces) {
590                     surface.postBuffer();
591                 }
592             }
593         }
594         return true;
595     }
596 
waitForStableFrameRate(TestSurface... surfaces)597     private void waitForStableFrameRate(TestSurface... surfaces) throws InterruptedException {
598         verifyCompatibleAndStableFrameRate(
599                 0, new IsMultipleWithTolerance(FRAME_RATE_TOLERANCE_STRICT), surfaces);
600     }
601 
602     // Used to test whether the frame rates are compatible, such as they are equal or multiples of
603     // each other.
604     private interface FrameRateTester {
apply(float frameRate1, float frameRate2)605         boolean apply(float frameRate1, float frameRate2);
606     }
607 
608     class IsMultipleWithTolerance implements FrameRateTester {
609         private float mTolerance;
610 
IsMultipleWithTolerance(float tolerance)611         IsMultipleWithTolerance(float tolerance) {
612             mTolerance = tolerance;
613         }
614 
615         @Override
apply(float deviceFrameRate, float expectedFrameRate)616         public boolean apply(float deviceFrameRate, float expectedFrameRate) {
617             return isFrameRateMultiple(deviceFrameRate, expectedFrameRate, mTolerance);
618         }
619     }
620 
621     // Use this FrameRateTester for fixed source tests.
622     // The logic mimics RefreshRateSelector::getDisplayFrames-related logic used for fixed source
623     // votes, which gives a relatively high score even if the deviceRate is not an exact multiple
624     // of the fixedSourceFrameRate.
625     class IsFixedSourceMultiple implements FrameRateTester {
626         @Override
apply(float deviceFrameRate, float fixedSourceFrameRate)627         public boolean apply(float deviceFrameRate, float fixedSourceFrameRate) {
628             long devicePeriodNs = (long) (1e9f / deviceFrameRate);
629             long fixedSourcePeriodNs = (long) (1e9f / fixedSourceFrameRate);
630             long remainder = devicePeriodNs % fixedSourcePeriodNs;
631 
632             if (remainder <= MARGIN_FOR_PERIOD_CALCULATION_NS
633                     || Math.abs(remainder - devicePeriodNs) <= MARGIN_FOR_PERIOD_CALCULATION_NS) {
634                 return true;
635             }
636 
637             return remainder == 0;
638         }
639     }
640 
641     // Use this FrameRateTester to check if frame rate is at least
642     // the expectedFrameRate.
643     private static class IsAtLeastFrameRateTester implements FrameRateTester {
644         @Override
apply(float deviceFrameRate, float expectedFrameRate)645         public boolean apply(float deviceFrameRate, float expectedFrameRate) {
646             return deviceFrameRate >= expectedFrameRate;
647         }
648     }
649 
650     // Set expectedFrameRate to 0.0 to verify only stable frame rate.
verifyCompatibleAndStableFrameRate(float expectedFrameRate, FrameRateTester isCompatible, TestSurface... surfaces)651     private void verifyCompatibleAndStableFrameRate(float expectedFrameRate,
652             FrameRateTester isCompatible, TestSurface... surfaces) throws InterruptedException {
653         Log.i(TAG, "Verifying compatible and stable frame rate");
654         long nowNanos = System.nanoTime();
655         long gracePeriodEndTimeNanos =
656                 nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS * 1_000_000_000L;
657         while (true) {
658             if (expectedFrameRate > 0.f) {
659                 // Wait until we switch to a compatible frame rate.
660                 while (!isCompatible.apply(mDeviceFrameRate, expectedFrameRate)
661                         && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) {
662                     // Empty
663                 }
664                 nowNanos = System.nanoTime();
665                 if (nowNanos >= gracePeriodEndTimeNanos) {
666                     throw new FrameRateTimeoutException(expectedFrameRate, mDeviceFrameRate);
667                 }
668             }
669 
670             // We've switched to a compatible frame rate. Now wait for a while to see if we stay at
671             // that frame rate.
672             long endTimeNanos = nowNanos + STABLE_FRAME_RATE_WAIT_SECONDS * 1_000_000_000L;
673             while (endTimeNanos > nowNanos) {
674                 int numModeChangedEvents = mModeChangedEvents.size();
675                 if (waitForEvents(endTimeNanos, surfaces)) {
676                     Log.i(TAG, String.format("Stable frame rate %.2f verified", mDeviceFrameRate));
677                     return;
678                 }
679                 nowNanos = System.nanoTime();
680                 if (mModeChangedEvents.size() > numModeChangedEvents) {
681                     break;
682                 }
683             }
684         }
685     }
686 
verifyModeSwitchesDontChangeResolution(int fromId, int toId)687     private void verifyModeSwitchesDontChangeResolution(int fromId, int toId) {
688         assertTrue(fromId <= toId);
689         for (int eventId = fromId; eventId < toId; eventId++) {
690             Display.Mode fromMode = mModeChangedEvents.get(eventId - 1);
691             Display.Mode toMode = mModeChangedEvents.get(eventId);
692             assertTrue("Resolution change was not expected, but there was such from "
693                     + fromMode + " to " + toMode + ".", hasSameResolution(fromMode, toMode));
694         }
695     }
696 
verifyModeSwitchesAreSeamless(int fromId, int toId)697     private void verifyModeSwitchesAreSeamless(int fromId, int toId) {
698         assertTrue(fromId <= toId);
699         for (int eventId = fromId; eventId < toId; eventId++) {
700             Display.Mode fromMode = mModeChangedEvents.get(eventId - 1);
701             Display.Mode toMode = mModeChangedEvents.get(eventId);
702             assertTrue("Non-seamless mode switch was not expected, but there was a "
703                             + "non-seamless switch from from " + fromMode + " to " + toMode + ".",
704                     DisplayUtil.isModeSwitchSeamless(fromMode, toMode));
705         }
706     }
707 
708     // Unfortunately, we can't just use Consumer<Api> for this, because we need to declare that it
709     // throws InterruptedException.
710     private interface TestInterface {
run(Api api)711         void run(Api api) throws InterruptedException;
712     }
713 
714     private interface OneSurfaceTestInterface {
run(TestSurface surface)715         void run(TestSurface surface) throws InterruptedException;
716     }
717 
718     // Runs the given test for each api, waiting for the preconditions to be satisfied before
719     // running the test. Includes retry logic when the test fails because the preconditions are
720     // violated. E.g. if we lose the SurfaceHolder's surface, or the activity is paused/resumed,
721     // we'll retry the test. The activity being intermittently paused/resumed has been observed to
722     // cause test failures in practice.
runTestsWithPreconditions(TestInterface test, String testName)723     private void runTestsWithPreconditions(TestInterface test, String testName)
724             throws InterruptedException {
725         synchronized (mLock) {
726             for (Api api : Api.values()) {
727                 Log.i(TAG, String.format("Testing %s %s", api, testName));
728                 int attempts = 0;
729                 boolean testPassed = false;
730                 try {
731                     while (!testPassed) {
732                         waitForPreconditions();
733                         try {
734                             test.run(api);
735                             testPassed = true;
736                         } catch (PreconditionViolatedException exc) {
737                             // The logic below will retry if we're below max attempts.
738                         } catch (FrameRateTimeoutException exc) {
739                             StringWriter stringWriter = new StringWriter();
740                             PrintWriter printWriter = new PrintWriter(stringWriter);
741                             exc.printStackTrace(printWriter);
742                             String stackTrace = stringWriter.toString();
743 
744                             // Sometimes we get a test timeout failure before we get the
745                             // notification that the activity was paused, and it was the pause that
746                             // caused the timeout failure. Wait for a bit to see if we get notified
747                             // of a precondition violation, and if so, retry the test. Otherwise
748                             // fail.
749                             assertTrue(
750                                     String.format(
751                                             "Timed out waiting for a stable and compatible frame"
752                                                     + " rate. expected=%.2f received=%.2f."
753                                                     + " Stack trace: " + stackTrace,
754                                             exc.expectedFrameRate, exc.deviceFrameRate),
755                                     waitForPreconditionViolation());
756                         }
757 
758                         if (!testPassed) {
759                             Log.i(TAG,
760                                     String.format("Preconditions violated while running the test."
761                                                     + " Have surface? %b. Activity resumed? %b.",
762                                             mSurface != null,
763                                             mActivityState == ActivityState.RUNNING));
764                             attempts++;
765                             assertTrue(String.format(
766                                     "Exceeded %d precondition wait attempts. Giving up.",
767                                     PRECONDITION_WAIT_MAX_ATTEMPTS),
768                                     attempts < PRECONDITION_WAIT_MAX_ATTEMPTS);
769                         }
770                     }
771                 } finally {
772                     String passFailMessage = String.format(
773                             "%s %s %s", testPassed ? "Passed" : "Failed", api, testName);
774                     if (testPassed) {
775                         Log.i(TAG, passFailMessage);
776                     } else {
777                         Log.e(TAG, passFailMessage);
778                     }
779                 }
780             }
781         }
782     }
783 
784     public void testClearFrameRate() throws InterruptedException {
785         runTestsWithPreconditions(this::testClearFrameRate, "clear frame rate");
786     }
787 
788     public void testFrameRateMatch(int compatibility, int changeFrameRateStrategy,
789             boolean useArrVersionApi) throws InterruptedException {
790         String type = changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
791                 ? "seamless" : "always";
792         runTestsWithPreconditions(api
793                 -> testFrameRateMatch(
794                         api, compatibility, changeFrameRateStrategy, useArrVersionApi),
795                 type + (compatibility == Surface.FRAME_RATE_COMPATIBILITY_DEFAULT ? "exact" : "GTE")
796                         + " frame rate match" + (useArrVersionApi ? " (ARR)" : ""));
797     }
798 
testFrameRateMatch(Api api, int compatibility, int changeFrameRateStrategy, boolean useArrVersionApi)799     private void testFrameRateMatch(Api api, int compatibility, int changeFrameRateStrategy,
800             boolean useArrVersionApi) throws InterruptedException {
801         if (useArrVersionApi && api == Api.SURFACE_CONTROL
802                 && !com.android.graphics.surfaceflinger.flags.Flags
803                         .arrSurfacecontrolSetframerateApi()) {
804             Log.w(TAG,
805                     "Skipping ARR SurfaceControl test due to flag "
806                     + "arr_surfacecontrol_setframerate_api disabled");
807             return;
808         }
809         runOneSurfaceTest(api, useArrVersionApi, (TestSurface surface) -> {
810             Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
811             Display.Mode currentMode = display.getMode();
812 
813             final FrameRateTester frameRateTester =
814                     compatibility == Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST
815                             ? new IsAtLeastFrameRateTester()
816                             : new IsMultipleWithTolerance(FRAME_RATE_TOLERANCE_RELAXED);
817 
818             if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
819                 // Seamless rates should be seamlessly achieved with no resolution changes.
820                 List<Float> seamlessRefreshRates =
821                         Floats.asList(currentMode.getAlternativeRefreshRates());
822                 for (float frameRate : seamlessRefreshRates) {
823                     int initialNumEvents = mModeChangedEvents.size();
824                     surface.setFrameRate(
825                             frameRate, compatibility, Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
826                     verifyCompatibleAndStableFrameRate(frameRate, frameRateTester, surface);
827                     verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
828                     verifyModeSwitchesDontChangeResolution(initialNumEvents,
829                             mModeChangedEvents.size());
830                 }
831                 // Reset to default
832                 surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
833                         Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
834                 // Wait for potential mode switches
835                 waitForStableFrameRate(surface);
836                 currentMode = display.getMode();
837 
838                 // Seamed rates should never generate a seamed switch.
839                 List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
840                 for (float frameRate : seamedRefreshRates) {
841                     int initialNumEvents = mModeChangedEvents.size();
842                     surface.setFrameRate(
843                             frameRate, compatibility, Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
844                     // Mode switch can occur, since we could potentially switch to a multiple
845                     // that happens to be seamless.
846                     verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
847                 }
848             } else if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ALWAYS) {
849                 // All rates should be seamfully achieved with no resolution changes.
850                 List<Float> allRefreshRates = getRefreshRates(currentMode, display);
851                 for (float frameRate : allRefreshRates) {
852                     int initialNumEvents = mModeChangedEvents.size();
853                     surface.setFrameRate(
854                             frameRate, compatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
855                     verifyCompatibleAndStableFrameRate(frameRate, frameRateTester, surface);
856                     verifyModeSwitchesDontChangeResolution(initialNumEvents,
857                             mModeChangedEvents.size());
858                 }
859             } else {
860                 Log.e(TAG, "Invalid changeFrameRateStrategy = " + changeFrameRateStrategy);
861             }
862         });
863     }
864 
modeSwitchesToString(int fromId, int toId)865     private String modeSwitchesToString(int fromId, int toId) {
866         assertTrue(fromId <= toId);
867         String string = "";
868         for (int eventId = fromId; eventId < toId; eventId++) {
869             Display.Mode fromMode = mModeChangedEvents.get(eventId - 1);
870             Display.Mode toMode = mModeChangedEvents.get(eventId);
871             string += fromMode + " -> " + toMode + "; ";
872         }
873         return string;
874     }
875 
testFixedSource(Api api, int changeFrameRateStrategy, boolean useArrVersionApi)876     private void testFixedSource(Api api, int changeFrameRateStrategy, boolean useArrVersionApi)
877             throws InterruptedException {
878         if (useArrVersionApi && api == Api.SURFACE_CONTROL
879                 && !com.android.graphics.surfaceflinger.flags.Flags
880                         .arrSurfacecontrolSetframerateApi()) {
881             Log.w(TAG,
882                     "Skipping ARR SurfaceControl test due to flag "
883                     + "arr_surfacecontrol_setframerate_api disabled");
884             return;
885         }
886 
887         Display display = getDisplay();
888         float[] incompatibleFrameRates = getIncompatibleFrameRates(display);
889         if (incompatibleFrameRates == null) {
890             Log.i(TAG, "No incompatible frame rates to use for testing fixed_source behavior");
891             return;
892         }
893 
894         float frameRateA = incompatibleFrameRates[0];
895         float frameRateB = incompatibleFrameRates[1];
896         Log.i(TAG,
897                 String.format("Testing with incompatible frame rates: surfaceA=%.2f surfaceB=%.2f",
898                         frameRateA, frameRateB));
899         TestSurface surfaceA = null;
900         TestSurface surfaceB = null;
901 
902         try {
903             int width = mSurfaceView.getHolder().getSurfaceFrame().width();
904             int height = mSurfaceView.getHolder().getSurfaceFrame().height() / 2;
905             Rect destFrameA = new Rect(/*left=*/0, /*top=*/0, /*right=*/width, /*bottom=*/height);
906             surfaceA = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface, "surfaceA",
907                     destFrameA, /*visible=*/true, Color.RED, useArrVersionApi);
908             Rect destFrameB = new Rect(
909                     /*left=*/0, /*top=*/height, /*right=*/width, /*bottom=*/height * 2);
910             surfaceB = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface, "surfaceB",
911                     destFrameB, /*visible=*/false, Color.GREEN, useArrVersionApi);
912 
913             int initialNumEvents = mModeChangedEvents.size();
914             surfaceA.setFrameRate(frameRateA, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
915                     changeFrameRateStrategy);
916             surfaceB.setFrameRate(frameRateB, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
917                     changeFrameRateStrategy);
918 
919             if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
920                 verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
921             } else {
922                 verifyCompatibleAndStableFrameRate(
923                         frameRateA, new IsFixedSourceMultiple(), surfaceA, surfaceB);
924             }
925 
926             verifyModeSwitchesDontChangeResolution(initialNumEvents,
927                     mModeChangedEvents.size());
928             initialNumEvents = mModeChangedEvents.size();
929 
930             surfaceB.setVisibility(true);
931 
932             if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
933                 verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
934             } else {
935                 verifyCompatibleAndStableFrameRate(
936                         frameRateB, new IsFixedSourceMultiple(), surfaceA, surfaceB);
937             }
938             verifyModeSwitchesDontChangeResolution(initialNumEvents,
939                     mModeChangedEvents.size());
940         } finally {
941             if (surfaceA != null) {
942                 surfaceA.release();
943             }
944             if (surfaceB != null) {
945                 surfaceB.release();
946             }
947         }
948     }
949 
testFixedSource(int changeFrameRateStrategy, boolean useArrVersionApi)950     public void testFixedSource(int changeFrameRateStrategy, boolean useArrVersionApi)
951             throws InterruptedException {
952         String type = changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
953                 ? "seamless" : "always";
954         runTestsWithPreconditions(api
955                 -> testFixedSource(api, changeFrameRateStrategy, useArrVersionApi),
956                 type + " fixed source behavior");
957     }
958 
testInvalidParams(Api api)959     private void testInvalidParams(Api api) {
960         TestSurface surface = null;
961         final int changeStrategy = Surface.CHANGE_FRAME_RATE_ALWAYS;
962         try {
963             surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
964                     "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
965                     /*visible=*/true, Color.RED, /*useArrVersionApi=*/false);
966             int initialNumEvents = mModeChangedEvents.size();
967             surface.setInvalidFrameRate(-100.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
968                     changeStrategy);
969             assertEquals(initialNumEvents, mModeChangedEvents.size());
970             surface.setInvalidFrameRate(Float.POSITIVE_INFINITY,
971                     Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, changeStrategy);
972             assertEquals(initialNumEvents, mModeChangedEvents.size());
973             surface.setInvalidFrameRate(Float.NaN, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
974                     changeStrategy);
975             assertEquals(initialNumEvents, mModeChangedEvents.size());
976             surface.setInvalidFrameRate(0.f, -10, changeStrategy);
977             assertEquals(initialNumEvents, mModeChangedEvents.size());
978             surface.setInvalidFrameRate(0.f, 50, changeStrategy);
979             assertEquals(initialNumEvents, mModeChangedEvents.size());
980         } finally {
981             if (surface != null) {
982                 surface.release();
983             }
984         }
985     }
986 
testInvalidParams()987     public void testInvalidParams() throws InterruptedException {
988         runTestsWithPreconditions(this::testInvalidParams, "invalid params behavior");
989     }
990 
runOneSurfaceTest(Api api, OneSurfaceTestInterface test)991     private void runOneSurfaceTest(Api api, OneSurfaceTestInterface test)
992             throws InterruptedException {
993         runOneSurfaceTest(api, false, test);
994     }
995 
runOneSurfaceTest(Api api, boolean useArrVersionApi, OneSurfaceTestInterface test)996     private void runOneSurfaceTest(Api api, boolean useArrVersionApi, OneSurfaceTestInterface test)
997             throws InterruptedException {
998         TestSurface surface = null;
999         try {
1000             surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
1001                     "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
1002                     /*visible=*/true, Color.RED, useArrVersionApi);
1003 
1004             test.run(surface);
1005         } finally {
1006             if (surface != null) {
1007                 surface.release();
1008             }
1009         }
1010     }
1011 
testMatchContentFramerate_None(Api api)1012     private void testMatchContentFramerate_None(Api api) throws InterruptedException {
1013         runOneSurfaceTest(api, (TestSurface surface) -> {
1014             Display display = getDisplay();
1015             Display.Mode currentMode = display.getMode();
1016             List<Float> frameRates = getRefreshRates(currentMode, display);
1017 
1018             for (float frameRate : frameRates) {
1019                 int initialNumEvents = mModeChangedEvents.size();
1020                 surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
1021                         Surface.CHANGE_FRAME_RATE_ALWAYS);
1022 
1023                 assertEquals("Mode switches are not expected but these were detected "
1024                                 + modeSwitchesToString(initialNumEvents, mModeChangedEvents.size()),
1025                         mModeChangedEvents.size(), initialNumEvents);
1026             }
1027         });
1028     }
1029 
testMatchContentFramerate_None()1030     public void testMatchContentFramerate_None() throws InterruptedException {
1031         runTestsWithPreconditions(this::testMatchContentFramerate_None,
1032                 "testMatchContentFramerate_None");
1033     }
1034 
testMatchContentFramerate_Auto(Api api)1035     private void testMatchContentFramerate_Auto(Api api)
1036             throws InterruptedException {
1037         runOneSurfaceTest(api, (TestSurface surface) -> {
1038             Display display = getDisplay();
1039             Display.Mode currentMode = display.getMode();
1040             List<Float> frameRatesToTest = Floats.asList(currentMode.getAlternativeRefreshRates());
1041 
1042             for (float frameRate : frameRatesToTest) {
1043                 int initialNumEvents = mModeChangedEvents.size();
1044                 surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
1045                         Surface.CHANGE_FRAME_RATE_ALWAYS);
1046 
1047                 verifyCompatibleAndStableFrameRate(frameRate,
1048                         new IsMultipleWithTolerance(FRAME_RATE_TOLERANCE_STRICT), surface);
1049                 verifyModeSwitchesDontChangeResolution(initialNumEvents,
1050                         mModeChangedEvents.size());
1051             }
1052 
1053             // Reset to default
1054             surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
1055                     Surface.CHANGE_FRAME_RATE_ALWAYS);
1056 
1057             // Wait for potential mode switches.
1058             waitForStableFrameRate(surface);
1059 
1060             currentMode = display.getMode();
1061             List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
1062 
1063             for (float frameRate : seamedRefreshRates) {
1064                 int initialNumEvents = mModeChangedEvents.size();
1065                 surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
1066                         Surface.CHANGE_FRAME_RATE_ALWAYS);
1067 
1068                 // Mode switches may have occurred, make sure they were all seamless.
1069                 verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
1070                 verifyModeSwitchesDontChangeResolution(initialNumEvents,
1071                         mModeChangedEvents.size());
1072             }
1073         });
1074     }
1075 
testMatchContentFramerate_Auto()1076     public void testMatchContentFramerate_Auto() throws InterruptedException {
1077         runTestsWithPreconditions(this::testMatchContentFramerate_Auto,
1078                 "testMatchContentFramerate_Auto");
1079     }
1080 
testMatchContentFramerate_Always(Api api)1081     private void testMatchContentFramerate_Always(Api api) throws InterruptedException {
1082         runOneSurfaceTest(api, (TestSurface surface) -> {
1083             Display display = getDisplay();
1084             List<Float> frameRates = getRefreshRates(display.getMode(), display);
1085             for (float frameRate : frameRates) {
1086                 int initialNumEvents = mModeChangedEvents.size();
1087                 surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
1088                         Surface.CHANGE_FRAME_RATE_ALWAYS);
1089 
1090                 verifyCompatibleAndStableFrameRate(frameRate,
1091                         new IsMultipleWithTolerance(FRAME_RATE_TOLERANCE_STRICT), surface);
1092                 verifyModeSwitchesDontChangeResolution(initialNumEvents,
1093                         mModeChangedEvents.size());
1094             }
1095         });
1096     }
1097 
testClearFrameRate(Api api)1098     private void testClearFrameRate(Api api) throws InterruptedException {
1099         Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
1100         Display.Mode initialMode = display.getMode();
1101 
1102         int width = mSurfaceView.getHolder().getSurfaceFrame().width();
1103         int height = mSurfaceView.getHolder().getSurfaceFrame().height() / 2;
1104         Rect destFrameA = new Rect(/*left=*/0, /*top=*/0, /*right=*/width, /*bottom=*/height);
1105         TestSurface surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
1106                 "surface", destFrameA, /*visible=*/true, Color.RED, /*useArrVersionApi=*/false);
1107 
1108         // Verify clear frame rate if set frame rate is seamless
1109         List<Float> seamlessRefreshRates =
1110                 Floats.asList(initialMode.getAlternativeRefreshRates());
1111         verifyClearFrameRate(surface, seamlessRefreshRates, initialMode.getRefreshRate(),
1112                 Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
1113 
1114         // Verify clear frame rate if set frame rate is non-seamless
1115         List<Float> seamedRefreshRates = getSeamedRefreshRates(initialMode, display);
1116         verifyClearFrameRate(surface, seamedRefreshRates, initialMode.getRefreshRate(),
1117                 Surface.CHANGE_FRAME_RATE_ALWAYS);
1118     }
1119 
verifyClearFrameRate(TestSurface surface, List<Float> refreshRates, float initialRefreshRate, int changeFrameRateStrategy)1120     private void verifyClearFrameRate(TestSurface surface, List<Float> refreshRates,
1121             float initialRefreshRate, int changeFrameRateStrategy) throws InterruptedException {
1122         for (float frameRate : refreshRates) {
1123             if (initialRefreshRate != frameRate) {
1124                 surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
1125                         changeFrameRateStrategy);
1126                 verifyCompatibleAndStableFrameRate(frameRate,
1127                         new IsMultipleWithTolerance(FRAME_RATE_TOLERANCE_RELAXED), surface);
1128 
1129                 // Clear the frame-rate
1130                 surface.clearFrameRate();
1131                 waitForStableFrameRate(surface);
1132                 break;
1133             }
1134         }
1135     }
1136 
testMatchContentFramerate_Always()1137     public void testMatchContentFramerate_Always() throws InterruptedException {
1138         runTestsWithPreconditions(this::testMatchContentFramerate_Always,
1139                 "testMatchContentFramerate_Always");
1140     }
1141 
1142     private static native int nativeWindowSetFrameRate(
1143             Surface surface, float frameRate, int compatibility, int changeFrameRateStrategy);
1144     private static native long nativeSurfaceControlCreate(
1145             Surface parentSurface, String name, int left, int top, int right, int bottom);
1146     private static native void nativeSurfaceControlDestroy(long surfaceControl);
1147     private static native void nativeSurfaceControlSetFrameRate(
1148             long surfaceControl, float frameRate, int compatibility, int changeFrameRateStrategy);
1149     private static native void nativeSurfaceControlSetVisibility(
1150             long surfaceControl, boolean visible);
1151     private static native boolean nativeSurfaceControlPostBuffer(long surfaceControl, int color);
1152     private static native int nativeWindowClearFrameRate(Surface surface);
1153     private static native void nativeSurfaceControlClearFrameRate(long surfaceControl);
1154 }
1155