1 /* 2 * Copyright 2023 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.view.surfacecontroltests; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.assertNotSame; 21 import static org.junit.Assert.assertTrue; 22 import static org.junit.Assert.fail; 23 import static org.junit.Assume.assumeTrue; 24 25 import android.app.Activity; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Rect; 29 import android.hardware.display.DisplayManager; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.sysprop.SurfaceFlingerProperties; 34 import android.util.Log; 35 import android.view.Display; 36 import android.view.Surface; 37 import android.view.SurfaceControl; 38 import android.view.SurfaceHolder; 39 import android.view.SurfaceView; 40 import android.view.ViewGroup; 41 42 import java.io.PrintWriter; 43 import java.io.StringWriter; 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.stream.Collectors; 48 49 /** 50 * An Activity to help with frame rate testing. 51 */ 52 public class GraphicsActivity extends Activity { 53 private static final String TAG = "GraphicsActivity"; 54 private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS = 2; 55 private static final long STABLE_FRAME_RATE_WAIT_SECONDS = 1; 56 private static final long POST_BUFFER_INTERVAL_MILLIS = 500; 57 private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5; 58 private static final long PRECONDITION_WAIT_TIMEOUT_SECONDS = 20; 59 private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS = 3; 60 private static final float FRAME_RATE_TOLERANCE = 0.01f; 61 62 static class FpsRange { 63 // The max difference between refresh rates in order to be considered equal. 64 private static final double FPS_THRESHOLD = 0.001; 65 double mMin; 66 double mMax; FpsRange(double min, double max)67 FpsRange(double min, double max) { 68 mMin = min; 69 mMax = max; 70 } includes(double fps)71 public boolean includes(double fps) { 72 return fps >= mMin - FPS_THRESHOLD && mMax + FPS_THRESHOLD >= fps; 73 } 74 } 75 76 // TODO(b/293651105): Unhardcode category fps range mapping 77 private static final FpsRange FRAME_RATE_CATEGORY_HIGH = new FpsRange(90, 120); 78 private static final FpsRange FRAME_RATE_CATEGORY_NORMAL = new FpsRange(60, 90); 79 private static final FpsRange FRAME_RATE_CATEGORY_LOW = new FpsRange(30, 30); 80 81 private DisplayManager mDisplayManager; 82 private SurfaceView mSurfaceView; 83 private Handler mHandler = new Handler(Looper.getMainLooper()); 84 private final Object mLock = new Object(); 85 private Surface mSurface = null; 86 private float mDisplayModeRefreshRate; 87 private float mDisplayRefreshRate; 88 private ModeChangedEvents mModeChangedEvents = new ModeChangedEvents(); 89 90 private enum ActivityState { RUNNING, PAUSED, DESTROYED } 91 92 private ActivityState mActivityState; 93 94 SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { 95 @Override 96 public void surfaceCreated(SurfaceHolder holder) { 97 synchronized (mLock) { 98 mSurface = holder.getSurface(); 99 mLock.notify(); 100 } 101 } 102 103 @Override 104 public void surfaceDestroyed(SurfaceHolder holder) { 105 synchronized (mLock) { 106 mSurface = null; 107 mLock.notify(); 108 } 109 } 110 111 @Override 112 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} 113 }; 114 115 DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { 116 @Override 117 public void onDisplayAdded(int displayId) {} 118 119 @Override 120 public void onDisplayChanged(int displayId) { 121 if (displayId != Display.DEFAULT_DISPLAY) { 122 return; 123 } 124 synchronized (mLock) { 125 Display display = mDisplayManager.getDisplay(displayId); 126 Display.Mode mode = display.getMode(); 127 mModeChangedEvents.add(mode); 128 float displayModeRefreshRate = mode.getRefreshRate(); 129 float displayRefreshRate = display.getRefreshRate(); 130 if (displayModeRefreshRate != mDisplayModeRefreshRate 131 || displayRefreshRate != mDisplayRefreshRate) { 132 Log.i(TAG, 133 String.format("Refresh rate changed: (mode) %.2f --> %.2f, " 134 + "(display) %.2f --> %.2f", 135 mDisplayModeRefreshRate, displayModeRefreshRate, 136 mDisplayRefreshRate, displayRefreshRate)); 137 mDisplayModeRefreshRate = displayModeRefreshRate; 138 mDisplayRefreshRate = displayRefreshRate; 139 mLock.notify(); 140 } 141 } 142 } 143 144 @Override 145 public void onDisplayRemoved(int displayId) {} 146 }; 147 148 // Wrapper around ArrayList for which the only allowed mutable operation is add(). 149 // We use this to store all mode change events during a test. When we need to iterate over 150 // all mode changes during a certain operation, we use the number of events in the beginning 151 // and in the end. It's important to never clear or modify the elements in this list hence the 152 // wrapper. 153 private static class ModeChangedEvents { 154 private List<Display.Mode> mEvents = new ArrayList<>(); 155 add(Display.Mode mode)156 public void add(Display.Mode mode) { 157 mEvents.add(mode); 158 } 159 get(int i)160 public Display.Mode get(int i) { 161 return mEvents.get(i); 162 } 163 size()164 public int size() { 165 return mEvents.size(); 166 } 167 } 168 169 private static class PreconditionViolatedException extends RuntimeException { PreconditionViolatedException()170 PreconditionViolatedException() {} 171 } 172 173 private static class FrameRateTimeoutException extends RuntimeException { FrameRateTimeoutException(float expectedFrameRate, float deviceFrameRate)174 FrameRateTimeoutException(float expectedFrameRate, float deviceFrameRate) { 175 this.expectedFrameRate = expectedFrameRate; 176 this.deviceFrameRate = deviceFrameRate; 177 } 178 FrameRateTimeoutException(List<Float> expectedFrameRates, float deviceFrameRate)179 FrameRateTimeoutException(List<Float> expectedFrameRates, float deviceFrameRate) { 180 this.expectedFrameRates = expectedFrameRates; 181 this.deviceFrameRate = deviceFrameRate; 182 } 183 184 public float expectedFrameRate; 185 public float deviceFrameRate; 186 public List<Float> expectedFrameRates; 187 } 188 189 public enum Api { 190 // Much of the code is copied from the SetFrameRate cts test. Add APIs as support grows. 191 SURFACE_CONTROL("SurfaceControl"); 192 193 private final String mName; Api(String name)194 Api(String name) { 195 mName = name; 196 } 197 toString()198 public String toString() { 199 return mName; 200 } 201 } 202 203 private static class TestSurface { 204 private String mName; 205 private SurfaceControl mSurfaceControl; 206 private Surface mSurface; 207 private int mColor; 208 private boolean mLastBufferPostTimeValid; 209 private long mLastBufferPostTime; 210 TestSurface(SurfaceControl parentSurfaceControl, Surface parentSurface, String name, Rect destFrame, boolean visible, int color)211 TestSurface(SurfaceControl parentSurfaceControl, Surface parentSurface, String name, 212 Rect destFrame, boolean visible, int color) { 213 mName = name; 214 mColor = color; 215 216 assertNotNull("No parent surface", parentSurfaceControl); 217 mSurfaceControl = new SurfaceControl.Builder() 218 .setParent(parentSurfaceControl) 219 .setName(mName) 220 .setBufferSize(destFrame.right - destFrame.left, 221 destFrame.bottom - destFrame.top) 222 .build(); 223 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { 224 transaction.setGeometry(mSurfaceControl, null, destFrame, Surface.ROTATION_0) 225 .apply(); 226 } 227 mSurface = new Surface(mSurfaceControl); 228 229 setVisibility(visible); 230 postBuffer(); 231 } 232 getSurface()233 Surface getSurface() { 234 return mSurface; 235 } 236 getSurfaceControl()237 SurfaceControl getSurfaceControl() { 238 return mSurfaceControl; 239 } 240 setFrameRate(float frameRate)241 public int setFrameRate(float frameRate) { 242 return setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); 243 } 244 setFrameRate( float frameRate, @Surface.FrameRateCompatibility int compatibility)245 public int setFrameRate( 246 float frameRate, @Surface.FrameRateCompatibility int compatibility) { 247 Log.i(TAG, 248 String.format("Setting frame rate for %s: frameRate=%.2f", mName, frameRate)); 249 250 int rc = 0; 251 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { 252 transaction.setFrameRate(mSurfaceControl, frameRate, compatibility); 253 transaction.apply(); 254 } 255 return rc; 256 } 257 setFrameRateCategory(int category)258 public int setFrameRateCategory(int category) { 259 Log.i(TAG, 260 String.format( 261 "Setting frame rate category for %s: category=%d", mName, category)); 262 263 int rc = 0; 264 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { 265 transaction.setFrameRateCategory(mSurfaceControl, category, false); 266 transaction.apply(); 267 } 268 return rc; 269 } 270 setFrameRateSelectionStrategy(int strategy)271 public int setFrameRateSelectionStrategy(int strategy) { 272 Log.i(TAG, 273 String.format("Setting frame rate selection strategy for %s: strategy=%d", 274 mName, strategy)); 275 276 int rc = 0; 277 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { 278 transaction.setFrameRateSelectionStrategy(mSurfaceControl, strategy); 279 transaction.apply(); 280 } 281 return rc; 282 } 283 setVisibility(boolean visible)284 public void setVisibility(boolean visible) { 285 Log.i(TAG, 286 String.format("Setting visibility for %s: %s", mName, 287 visible ? "visible" : "hidden")); 288 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { 289 transaction.setVisibility(mSurfaceControl, visible).apply(); 290 } 291 } 292 postBuffer()293 public void postBuffer() { 294 mLastBufferPostTimeValid = true; 295 mLastBufferPostTime = System.nanoTime(); 296 Canvas canvas = mSurface.lockHardwareCanvas(); 297 canvas.drawColor(mColor); 298 mSurface.unlockCanvasAndPost(canvas); 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 try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { 313 transaction.reparent(mSurfaceControl, null).apply(); 314 } 315 mSurfaceControl.release(); 316 mSurfaceControl = null; 317 } 318 } 319 320 @Override finalize()321 protected void finalize() throws Throwable { 322 try { 323 release(); 324 } finally { 325 super.finalize(); 326 } 327 } 328 } 329 330 @Override onCreate(Bundle savedInstanceState)331 protected void onCreate(Bundle savedInstanceState) { 332 super.onCreate(savedInstanceState); 333 synchronized (mLock) { 334 mDisplayManager = getSystemService(DisplayManager.class); 335 Display display = getDisplay(); 336 Display.Mode mode = display.getMode(); 337 mDisplayModeRefreshRate = mode.getRefreshRate(); 338 mDisplayRefreshRate = display.getRefreshRate(); 339 // Insert the initial mode so we have the full display mode history. 340 mModeChangedEvents.add(mode); 341 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); 342 mSurfaceView = new SurfaceView(this); 343 mSurfaceView.setWillNotDraw(false); 344 mSurfaceView.setZOrderOnTop(true); 345 setContentView(mSurfaceView, 346 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 347 ViewGroup.LayoutParams.MATCH_PARENT)); 348 mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); 349 } 350 } 351 352 @Override onDestroy()353 protected void onDestroy() { 354 super.onDestroy(); 355 mDisplayManager.unregisterDisplayListener(mDisplayListener); 356 synchronized (mLock) { 357 mActivityState = ActivityState.DESTROYED; 358 mLock.notify(); 359 } 360 } 361 362 @Override onPause()363 public void onPause() { 364 super.onPause(); 365 synchronized (mLock) { 366 mActivityState = ActivityState.PAUSED; 367 mLock.notify(); 368 } 369 } 370 371 @Override onResume()372 public void onResume() { 373 super.onResume(); 374 synchronized (mLock) { 375 mActivityState = ActivityState.RUNNING; 376 mLock.notify(); 377 } 378 } 379 380 // Returns the refresh rates with the same resolution as "mode". getRefreshRates(Display.Mode mode, Display display)381 private ArrayList<Float> getRefreshRates(Display.Mode mode, Display display) { 382 Display.Mode[] modes = display.getSupportedModes(); 383 ArrayList<Float> frameRates = new ArrayList<>(); 384 for (Display.Mode supportedMode : modes) { 385 if (hasSameResolution(supportedMode, mode)) { 386 frameRates.add(supportedMode.getRefreshRate()); 387 } 388 } 389 Collections.sort(frameRates); 390 ArrayList<Float> uniqueFrameRates = new ArrayList<>(); 391 for (float frameRate : frameRates) { 392 if (uniqueFrameRates.isEmpty() 393 || frameRate - uniqueFrameRates.get(uniqueFrameRates.size() - 1) 394 >= FRAME_RATE_TOLERANCE) { 395 uniqueFrameRates.add(frameRate); 396 } 397 } 398 Log.i(TAG, 399 "**** Available display refresh rates: " 400 + uniqueFrameRates.stream() 401 .map(Object::toString) 402 .collect(Collectors.joining(", "))); 403 return uniqueFrameRates; 404 } 405 hasSameResolution(Display.Mode mode1, Display.Mode mode2)406 private boolean hasSameResolution(Display.Mode mode1, Display.Mode mode2) { 407 return mode1.getPhysicalHeight() == mode2.getPhysicalHeight() 408 && mode1.getPhysicalWidth() == mode2.getPhysicalWidth(); 409 } 410 isFrameRateMultiple(float higherFrameRate, float lowerFrameRate)411 private boolean isFrameRateMultiple(float higherFrameRate, float lowerFrameRate) { 412 float multiple = higherFrameRate / lowerFrameRate; 413 int roundedMultiple = Math.round(multiple); 414 return roundedMultiple > 0 415 && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate) 416 <= FRAME_RATE_TOLERANCE; 417 } 418 frameRateEquals(float frameRate1, float frameRate2)419 private boolean frameRateEquals(float frameRate1, float frameRate2) { 420 return Math.abs(frameRate1 - frameRate2) <= FRAME_RATE_TOLERANCE; 421 } 422 423 // Waits until our SurfaceHolder has a surface and the activity is resumed. waitForPreconditions()424 private void waitForPreconditions() throws InterruptedException { 425 assertNotSame( 426 "Activity was unexpectedly destroyed", mActivityState, ActivityState.DESTROYED); 427 if (mSurface == null || mActivityState != ActivityState.RUNNING) { 428 Log.i(TAG, 429 String.format( 430 "Waiting for preconditions. Have surface? %b. Activity resumed? %b.", 431 mSurface != null, mActivityState == ActivityState.RUNNING)); 432 } 433 long nowNanos = System.nanoTime(); 434 long endTimeNanos = nowNanos + PRECONDITION_WAIT_TIMEOUT_SECONDS * 1_000_000_000L; 435 while (mSurface == null || mActivityState != ActivityState.RUNNING) { 436 long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000; 437 assertTrue(String.format("Timed out waiting for preconditions. Have surface? %b." 438 + " Activity resumed? %b.", 439 mSurface != null, mActivityState == ActivityState.RUNNING), 440 timeRemainingMillis > 0); 441 mLock.wait(timeRemainingMillis); 442 assertNotSame( 443 "Activity was unexpectedly destroyed", mActivityState, ActivityState.DESTROYED); 444 nowNanos = System.nanoTime(); 445 } 446 // Make sure any previous mode changes are completed. 447 waitForStableFrameRate(); 448 } 449 450 // Returns true if we encounter a precondition violation, false otherwise. waitForPreconditionViolation()451 private boolean waitForPreconditionViolation() throws InterruptedException { 452 assertNotSame( 453 "Activity was unexpectedly destroyed", mActivityState, ActivityState.DESTROYED); 454 long nowNanos = System.nanoTime(); 455 long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS * 1_000_000_000L; 456 while (mSurface != null && mActivityState == ActivityState.RUNNING) { 457 long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000; 458 if (timeRemainingMillis <= 0) { 459 break; 460 } 461 mLock.wait(timeRemainingMillis); 462 assertNotSame( 463 "Activity was unexpectedly destroyed", mActivityState, ActivityState.DESTROYED); 464 nowNanos = System.nanoTime(); 465 } 466 return mSurface == null || mActivityState != ActivityState.RUNNING; 467 } 468 verifyPreconditions()469 private void verifyPreconditions() { 470 if (mSurface == null || mActivityState != ActivityState.RUNNING) { 471 throw new PreconditionViolatedException(); 472 } 473 } 474 475 // Returns true if we reached waitUntilNanos, false if some other event occurred. waitForEvents(long waitUntilNanos, TestSurface[] surfaces)476 private boolean waitForEvents(long waitUntilNanos, TestSurface[] surfaces) 477 throws InterruptedException { 478 int numModeChangedEvents = mModeChangedEvents.size(); 479 long nowNanos = System.nanoTime(); 480 while (nowNanos < waitUntilNanos) { 481 long surfacePostTime = Long.MAX_VALUE; 482 for (TestSurface surface : surfaces) { 483 surfacePostTime = Math.min(surfacePostTime, 484 surface.getLastBufferPostTime() 485 + (POST_BUFFER_INTERVAL_MILLIS * 1_000_000L)); 486 } 487 long timeoutNs = Math.min(waitUntilNanos, surfacePostTime) - nowNanos; 488 long timeoutMs = timeoutNs / 1_000_000L; 489 int remainderNs = (int) (timeoutNs % 1_000_000L); 490 // Don't call wait(0, 0) - it blocks indefinitely. 491 if (timeoutMs > 0 || remainderNs > 0) { 492 mLock.wait(timeoutMs, remainderNs); 493 } 494 nowNanos = System.nanoTime(); 495 verifyPreconditions(); 496 if (mModeChangedEvents.size() > numModeChangedEvents) { 497 return false; 498 } 499 if (nowNanos >= surfacePostTime) { 500 for (TestSurface surface : surfaces) { 501 surface.postBuffer(); 502 } 503 } 504 } 505 return true; 506 } 507 waitForStableFrameRate(TestSurface... surfaces)508 private void waitForStableFrameRate(TestSurface... surfaces) throws InterruptedException { 509 verifyFrameRates(List.of(), surfaces); 510 } 511 verifyExactAndStableFrameRate( float expectedFrameRate, TestSurface... surfaces)512 private void verifyExactAndStableFrameRate( 513 float expectedFrameRate, 514 TestSurface... surfaces) throws InterruptedException { 515 verifyFrameRate(List.of(expectedFrameRate), false, surfaces); 516 } 517 verifyCompatibleAndStableFrameRate( float expectedFrameRate, TestSurface... surfaces)518 private void verifyCompatibleAndStableFrameRate( 519 float expectedFrameRate, 520 TestSurface... surfaces) throws InterruptedException { 521 verifyFrameRate(List.of(expectedFrameRate), true, surfaces); 522 } 523 524 /** Verify stable frame rate at one of the expectedFrameRates. */ verifyFrameRates(List<Float> expectedFrameRates, TestSurface... surfaces)525 private void verifyFrameRates(List<Float> expectedFrameRates, TestSurface... surfaces) 526 throws InterruptedException { 527 verifyFrameRate(expectedFrameRates, true, surfaces); 528 } 529 530 // Set expectedFrameRates to empty to verify only stable frame rate. verifyFrameRate(List<Float> expectedFrameRates, boolean multiplesAllowed, TestSurface... surfaces)531 private void verifyFrameRate(List<Float> expectedFrameRates, boolean multiplesAllowed, 532 TestSurface... surfaces) throws InterruptedException { 533 Log.i(TAG, "Verifying compatible and stable frame rate"); 534 long nowNanos = System.nanoTime(); 535 long gracePeriodEndTimeNanos = 536 nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS * 1_000_000_000L; 537 while (true) { 538 if (expectedFrameRates.size() == 1) { 539 float expectedFrameRate = expectedFrameRates.get(0); 540 // Wait until we switch to a compatible frame rate. 541 Log.i(TAG, 542 String.format( 543 "Verifying expected frame rate: actual=%.2f, expected=%.2f", 544 multiplesAllowed ? mDisplayModeRefreshRate : mDisplayRefreshRate, 545 expectedFrameRate)); 546 if (multiplesAllowed) { 547 while (!isFrameRateMultiple(mDisplayModeRefreshRate, expectedFrameRate) 548 && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) { 549 // Empty 550 } 551 } else { 552 while (!frameRateEquals(mDisplayRefreshRate, expectedFrameRate) 553 && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) { 554 // Empty 555 } 556 } 557 nowNanos = System.nanoTime(); 558 if (nowNanos >= gracePeriodEndTimeNanos) { 559 throw new FrameRateTimeoutException(expectedFrameRate, 560 multiplesAllowed ? mDisplayModeRefreshRate : mDisplayRefreshRate); 561 } 562 } 563 564 // We've switched to a compatible frame rate. Now wait for a while to see if we stay at 565 // that frame rate. 566 long endTimeNanos = nowNanos + STABLE_FRAME_RATE_WAIT_SECONDS * 1_000_000_000L; 567 while (endTimeNanos > nowNanos) { 568 int numModeChangedEvents = mModeChangedEvents.size(); 569 if (waitForEvents(endTimeNanos, surfaces)) { 570 // Verify any expected frame rate since there are multiple that will suffice. 571 // Mainly to account for running tests on real devices, where other non-test 572 // layers may affect the outcome. 573 if (expectedFrameRates.size() > 1) { 574 for (float expectedFrameRate : expectedFrameRates) { 575 if (isFrameRateMultiple(mDisplayModeRefreshRate, expectedFrameRate)) { 576 return; 577 } 578 } 579 // The frame rate is stable but it is not one of the expected frame rates. 580 throw new FrameRateTimeoutException( 581 expectedFrameRates, mDisplayModeRefreshRate); 582 } else { 583 Log.i(TAG, 584 String.format("Stable frame rate %.2f verified", 585 multiplesAllowed ? mDisplayModeRefreshRate 586 : mDisplayRefreshRate)); 587 return; 588 } 589 } 590 nowNanos = System.nanoTime(); 591 if (mModeChangedEvents.size() > numModeChangedEvents) { 592 break; 593 } 594 } 595 } 596 } 597 verifyModeSwitchesDontChangeResolution(int fromId, int toId)598 private void verifyModeSwitchesDontChangeResolution(int fromId, int toId) { 599 assertTrue(fromId <= toId); 600 for (int eventId = fromId; eventId < toId; eventId++) { 601 Display.Mode fromMode = mModeChangedEvents.get(eventId - 1); 602 Display.Mode toMode = mModeChangedEvents.get(eventId); 603 assertTrue("Resolution change was not expected, but there was such from " + fromMode 604 + " to " + toMode + ".", 605 hasSameResolution(fromMode, toMode)); 606 } 607 } 608 609 // Unfortunately, we can't just use Consumer<Api> for this, because we need to declare that it 610 // throws InterruptedException. 611 private interface TestInterface { run()612 void run() throws InterruptedException; 613 } 614 615 private interface OneSurfaceTestInterface { run(TestSurface surface)616 void run(TestSurface surface) throws InterruptedException; 617 } 618 619 // Runs the given test for each api, waiting for the preconditions to be satisfied before 620 // running the test. Includes retry logic when the test fails because the preconditions are 621 // violated. E.g. if we lose the SurfaceHolder's surface, or the activity is paused/resumed, 622 // we'll retry the test. The activity being intermittently paused/resumed has been observed to 623 // cause test failures in practice. runTestsWithPreconditions(TestInterface test, String testName)624 private void runTestsWithPreconditions(TestInterface test, String testName) 625 throws InterruptedException { 626 synchronized (mLock) { 627 for (Api api : Api.values()) { 628 Log.i(TAG, String.format("Testing %s %s", api, testName)); 629 int attempts = 0; 630 boolean testPassed = false; 631 try { 632 while (!testPassed) { 633 waitForPreconditions(); 634 try { 635 test.run(); 636 testPassed = true; 637 } catch (PreconditionViolatedException exc) { 638 // The logic below will retry if we're below max attempts. 639 } catch (FrameRateTimeoutException exc) { 640 StringWriter stringWriter = new StringWriter(); 641 PrintWriter printWriter = new PrintWriter(stringWriter); 642 exc.printStackTrace(printWriter); 643 String stackTrace = stringWriter.toString(); 644 645 // Sometimes we get a test timeout failure before we get the 646 // notification that the activity was paused, and it was the pause that 647 // caused the timeout failure. Wait for a bit to see if we get notified 648 // of a precondition violation, and if so, retry the test. Otherwise 649 // fail. 650 if (exc.expectedFrameRates.isEmpty()) { 651 assertTrue( 652 String.format( 653 "Timed out waiting for a stable and compatible" 654 + " frame rate." 655 + " expected=%.2f received=%.2f." 656 + " Stack trace: " + stackTrace, 657 exc.expectedFrameRate, exc.deviceFrameRate), 658 waitForPreconditionViolation()); 659 } else { 660 assertTrue( 661 String.format( 662 "Timed out waiting for a stable and compatible" 663 + " frame rate." 664 + " expected={%.2f} received=%.2f." 665 + " Stack trace: " + stackTrace, 666 exc.expectedFrameRates.stream() 667 .map(Object::toString) 668 .collect(Collectors.joining(", ")), 669 exc.deviceFrameRate), 670 waitForPreconditionViolation()); 671 } 672 } 673 674 if (!testPassed) { 675 Log.i(TAG, 676 String.format("Preconditions violated while running the test." 677 + " Have surface? %b. Activity resumed? %b.", 678 mSurface != null, 679 mActivityState == ActivityState.RUNNING)); 680 attempts++; 681 assertTrue(String.format( 682 "Exceeded %d precondition wait attempts. Giving up.", 683 PRECONDITION_WAIT_MAX_ATTEMPTS), 684 attempts < PRECONDITION_WAIT_MAX_ATTEMPTS); 685 } 686 } 687 } finally { 688 String passFailMessage = String.format( 689 "%s %s %s", testPassed ? "Passed" : "Failed", api, testName); 690 if (testPassed) { 691 Log.i(TAG, passFailMessage); 692 } else { 693 Log.e(TAG, passFailMessage); 694 } 695 } 696 } 697 } 698 } 699 700 private void runOneSurfaceTest(OneSurfaceTestInterface test) throws InterruptedException { 701 TestSurface surface = null; 702 try { 703 surface = new TestSurface(mSurfaceView.getSurfaceControl(), mSurface, "testSurface", 704 mSurfaceView.getHolder().getSurfaceFrame(), 705 /*visible=*/true, Color.RED); 706 707 test.run(surface); 708 } finally { 709 if (surface != null) { 710 surface.release(); 711 } 712 } 713 } 714 715 private void testSurfaceControlFrameRateCompatibilityInternal( 716 @Surface.FrameRateCompatibility int compatibility) throws InterruptedException { 717 runOneSurfaceTest((TestSurface surface) -> { 718 Log.i(TAG, 719 "**** Running testSurfaceControlFrameRateCompatibility with compatibility " 720 + compatibility); 721 722 List<Float> expectedFrameRates = getExpectedFrameRateForCompatibility(compatibility); 723 Log.i(TAG, 724 "Expected frame rates: " 725 + expectedFrameRates.stream() 726 .map(Object::toString) 727 .collect(Collectors.joining(", "))); 728 int initialNumEvents = mModeChangedEvents.size(); 729 surface.setFrameRate(70.f, compatibility); 730 verifyFrameRates(expectedFrameRates, surface); 731 verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size()); 732 }); 733 } 734 testSurfaceControlFrameRateCompatibility( @urface.FrameRateCompatibility int compatibility)735 public void testSurfaceControlFrameRateCompatibility( 736 @Surface.FrameRateCompatibility int compatibility) throws InterruptedException { 737 runTestsWithPreconditions( 738 () -> testSurfaceControlFrameRateCompatibilityInternal(compatibility), 739 "frame rate compatibility=" + compatibility); 740 } 741 testSurfaceControlFrameRateCategoryInternal( @urface.FrameRateCategory int category)742 private void testSurfaceControlFrameRateCategoryInternal( 743 @Surface.FrameRateCategory int category) throws InterruptedException { 744 runOneSurfaceTest((TestSurface surface) -> { 745 Log.i(TAG, "**** Running testSurfaceControlFrameRateCategory for category " + category); 746 747 List<Float> expectedFrameRates = getExpectedFrameRateForCategory(category); 748 int initialNumEvents = mModeChangedEvents.size(); 749 surface.setFrameRateCategory(category); 750 verifyFrameRates(expectedFrameRates, surface); 751 verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size()); 752 }); 753 } 754 testSurfaceControlFrameRateCategory(@urface.FrameRateCategory int category)755 public void testSurfaceControlFrameRateCategory(@Surface.FrameRateCategory int category) 756 throws InterruptedException { 757 runTestsWithPreconditions(() 758 -> testSurfaceControlFrameRateCategoryInternal(category), 759 "frame rate category=" + category); 760 } 761 testSurfaceControlFrameRateSelectionStrategyInternal(int parentStrategy)762 private void testSurfaceControlFrameRateSelectionStrategyInternal(int parentStrategy) 763 throws InterruptedException { 764 Log.i(TAG, 765 "**** Running testSurfaceControlFrameRateSelectionStrategy for strategy " 766 + parentStrategy); 767 TestSurface parent = null; 768 TestSurface child = null; 769 try { 770 parent = new TestSurface(mSurfaceView.getSurfaceControl(), mSurface, 771 "testSurfaceParent", mSurfaceView.getHolder().getSurfaceFrame(), 772 /*visible=*/true, Color.RED); 773 child = new TestSurface(parent.getSurfaceControl(), parent.getSurface(), 774 "testSurfaceChild", mSurfaceView.getHolder().getSurfaceFrame(), 775 /*visible=*/true, Color.BLUE); 776 777 // Test 778 Display display = getDisplay(); 779 List<Float> frameRates = getRefreshRates(display.getMode(), display); 780 assumeTrue("**** SKIPPED due to frame rate override disabled", 781 SurfaceFlingerProperties.enable_frame_rate_override().orElse(true)); 782 float childFrameRate = Collections.max(frameRates); 783 float parentFrameRate = childFrameRate / 2; 784 int initialNumEvents = mModeChangedEvents.size(); 785 parent.setFrameRateSelectionStrategy(parentStrategy); 786 787 // For Self case, we want to test that child gets default behavior 788 if (parentStrategy == SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF) { 789 parent.setFrameRateCategory(Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE); 790 } else { 791 parent.setFrameRate(parentFrameRate); 792 child.setFrameRate(childFrameRate); 793 } 794 795 // Verify 796 float expectedFrameRate = 797 parentStrategy == SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN 798 ? parentFrameRate 799 : childFrameRate; 800 verifyExactAndStableFrameRate(expectedFrameRate, parent, child); 801 verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size()); 802 } finally { 803 if (parent != null) { 804 parent.release(); 805 } 806 if (child != null) { 807 child.release(); 808 } 809 } 810 } 811 testSurfaceControlFrameRateSelectionStrategy(int parentStrategy)812 public void testSurfaceControlFrameRateSelectionStrategy(int parentStrategy) 813 throws InterruptedException { 814 runTestsWithPreconditions( 815 () -> testSurfaceControlFrameRateSelectionStrategyInternal(parentStrategy), 816 "frame rate strategy=" + parentStrategy); 817 } 818 getExpectedFrameRateForCompatibility(int compatibility)819 private List<Float> getExpectedFrameRateForCompatibility(int compatibility) { 820 assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility " 821 + compatibility, 822 compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE); 823 824 Display display = getDisplay(); 825 List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display) 826 .stream() 827 .filter(rate -> rate >= 70.f) 828 .collect(Collectors.toList()); 829 830 assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate " 831 + "is >= 30", 832 !expectedFrameRates.isEmpty()); 833 return expectedFrameRates; 834 } 835 getExpectedFrameRateForCategory(int category)836 private List<Float> getExpectedFrameRateForCategory(int category) { 837 Display display = getDisplay(); 838 List<Float> frameRates = getRefreshRates(display.getMode(), display); 839 840 if (category == Surface.FRAME_RATE_CATEGORY_DEFAULT) { 841 // Max due to default vote and no other frame rate specifications. 842 return List.of(Collections.max(frameRates)); 843 } else if (category == Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE) { 844 return frameRates; 845 } 846 847 FpsRange categoryRange = convertCategory(category); 848 List<Float> expectedFrameRates = frameRates.stream() 849 .filter(fps -> categoryRange.includes(fps)) 850 .collect(Collectors.toList()); 851 assumeTrue("**** testSurfaceControlFrameRateCategory SKIPPED for category " + category, 852 !expectedFrameRates.isEmpty()); 853 return expectedFrameRates; 854 } 855 convertCategory(int category)856 private FpsRange convertCategory(int category) { 857 switch (category) { 858 case Surface.FRAME_RATE_CATEGORY_HIGH_HINT: 859 case Surface.FRAME_RATE_CATEGORY_HIGH: 860 return FRAME_RATE_CATEGORY_HIGH; 861 case Surface.FRAME_RATE_CATEGORY_NORMAL: 862 return FRAME_RATE_CATEGORY_NORMAL; 863 case Surface.FRAME_RATE_CATEGORY_LOW: 864 return FRAME_RATE_CATEGORY_LOW; 865 case Surface.FRAME_RATE_CATEGORY_DEFAULT: 866 case Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE: 867 fail("Should not get range for category=" + category); 868 } 869 return new FpsRange(0, 0); 870 } 871 } 872