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