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