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.gameframerate.cts; 18 19 import static org.junit.Assert.assertTrue; 20 21 import android.app.Activity; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.hardware.display.DisplayManager; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.support.test.uiautomator.UiDevice; 29 import android.sysprop.SurfaceFlingerProperties; 30 import android.util.Log; 31 import android.view.Choreographer; 32 import android.view.Surface; 33 import android.view.SurfaceHolder; 34 import android.view.SurfaceView; 35 import android.view.ViewGroup; 36 37 import java.io.IOException; 38 import java.util.ArrayList; 39 40 /** 41 * An Activity to help with frame rate testing. 42 */ 43 public class GameFrameRateCtsActivity extends Activity { 44 private static final String TAG = "GameFrameRateCtsActivity"; 45 private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS = 2 * 1_000_000_000L; 46 private static final long STABLE_FRAME_RATE_WAIT_NANOSECONDS = 1 * 1_000_000_000L; 47 private static final long POST_BUFFER_INTERVAL_NANOSECONDS = 500_000_000L; 48 private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5; 49 private static final long PRECONDITION_WAIT_TIMEOUT_NANOSECONDS = 20 * 1_000_000_000L; 50 private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS = 3 * 1_000_000_000L; 51 private static final float FRAME_RATE_TOLERANCE = 1.01f; 52 private static final float FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE = 5; 53 private static final long FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS = 1 * 1_000_000_000L; 54 private static final long FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS = 10 * 1_000_000_000L; 55 56 // Default game frame rate sets the frame rate to 60 by default, even if the system properties 57 // "ro.surface_flinger.game_default_frame_rate_override" is not set. Ref: GameManagerService 58 // {@link com.android.server.app.GameManagerService#onBootCompleted()} 59 private static final Integer GAME_DEFAULT_FRAMERATE_INT = 60 SurfaceFlingerProperties.game_default_frame_rate_override().orElse(60); 61 62 private DisplayManager mDisplayManager; 63 private SurfaceView mSurfaceView; 64 private Handler mHandler = new Handler(Looper.getMainLooper()); 65 private Object mLock = new Object(); 66 private Surface mSurface = null; 67 private float mReportedDisplayRefreshRate; 68 private float mReportedDisplayModeRefreshRate; 69 private ArrayList<Float> mRefreshRateChangedEvents = new ArrayList<Float>(); 70 71 private long mLastBufferPostTime; 72 73 private enum ActivityState { RUNNING, PAUSED, DESTROYED } 74 private ActivityState mActivityState; 75 76 SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { 77 @Override 78 public void surfaceCreated(SurfaceHolder holder) { 79 synchronized (mLock) { 80 mSurface = holder.getSurface(); 81 mLock.notify(); 82 } 83 } 84 85 @Override 86 public void surfaceDestroyed(SurfaceHolder holder) { 87 synchronized (mLock) { 88 mSurface = null; 89 mLock.notify(); 90 } 91 } 92 93 @Override 94 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 95 } 96 }; 97 98 DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { 99 @Override 100 public void onDisplayAdded(int displayId) { 101 } 102 103 @Override 104 public void onDisplayChanged(int displayId) { 105 synchronized (mLock) { 106 float refreshRate = getDisplay().getRefreshRate(); 107 float displayModeRefreshRate = getDisplay().getMode().getRefreshRate(); 108 if (refreshRate != mReportedDisplayRefreshRate 109 || displayModeRefreshRate != mReportedDisplayModeRefreshRate) { 110 Log.i(TAG, String.format("Frame rate changed: (%.2f, %.2f) --> (%.2f, %.2f)", 111 mReportedDisplayModeRefreshRate, 112 mReportedDisplayRefreshRate, 113 displayModeRefreshRate, 114 refreshRate)); 115 mReportedDisplayRefreshRate = refreshRate; 116 mReportedDisplayModeRefreshRate = displayModeRefreshRate; 117 mRefreshRateChangedEvents.add(refreshRate); 118 mLock.notify(); 119 } 120 } 121 } 122 123 @Override 124 public void onDisplayRemoved(int displayId) { 125 } 126 }; 127 128 private static class PreconditionViolatedException extends RuntimeException { } 129 130 private static class FrameRateTimeoutException extends RuntimeException { FrameRateTimeoutException(FrameRateRange appRequestedFrameRate, float deviceRefreshRate)131 FrameRateTimeoutException(FrameRateRange appRequestedFrameRate, float deviceRefreshRate) { 132 this.appRequestedFrameRate = appRequestedFrameRate; 133 this.deviceRefreshRate = deviceRefreshRate; 134 } 135 136 public FrameRateRange appRequestedFrameRate; 137 public float deviceRefreshRate; 138 } 139 140 private static class FrameRateRange { FrameRateRange(float min, float max)141 FrameRateRange(float min, float max) { 142 this.min = min; 143 this.max = max; 144 } 145 public float min; 146 public float max; 147 } 148 postBufferToSurface(int color)149 public void postBufferToSurface(int color) { 150 mLastBufferPostTime = System.nanoTime(); 151 Canvas canvas = mSurface.lockCanvas(null); 152 canvas.drawColor(color); 153 mSurface.unlockCanvasAndPost(canvas); 154 } 155 156 @Override onCreate(Bundle savedInstanceState)157 protected void onCreate(Bundle savedInstanceState) { 158 super.onCreate(savedInstanceState); 159 synchronized (mLock) { 160 mDisplayManager = getSystemService(DisplayManager.class); 161 mReportedDisplayRefreshRate = getDisplay().getRefreshRate(); 162 mReportedDisplayModeRefreshRate = getDisplay().getMode().getRefreshRate(); 163 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); 164 mSurfaceView = new SurfaceView(this); 165 mSurfaceView.setWillNotDraw(false); 166 setContentView(mSurfaceView, 167 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 168 ViewGroup.LayoutParams.MATCH_PARENT)); 169 mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); 170 } 171 } 172 173 @Override onDestroy()174 protected void onDestroy() { 175 super.onDestroy(); 176 mDisplayManager.unregisterDisplayListener(mDisplayListener); 177 synchronized (mLock) { 178 mActivityState = ActivityState.DESTROYED; 179 mLock.notify(); 180 } 181 } 182 183 @Override onPause()184 public void onPause() { 185 super.onPause(); 186 synchronized (mLock) { 187 mActivityState = ActivityState.PAUSED; 188 mLock.notify(); 189 } 190 } 191 192 @Override onResume()193 public void onResume() { 194 super.onResume(); 195 synchronized (mLock) { 196 mActivityState = ActivityState.RUNNING; 197 mLock.notify(); 198 } 199 } 200 frameRatesEqual(float frameRate1, float frameRate2)201 private static boolean frameRatesEqual(float frameRate1, float frameRate2) { 202 return Math.abs(frameRate1 - frameRate2) <= FRAME_RATE_TOLERANCE; 203 } 204 frameRatesMatchesOverride(float frameRate1, float frameRate2)205 private static boolean frameRatesMatchesOverride(float frameRate1, float frameRate2) { 206 return Math.abs(frameRate1 - frameRate2) <= FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE; 207 } 208 frameRatesMatchesOverride( float fps, FrameRateRange expectedFrameRateRange)209 private static boolean frameRatesMatchesOverride( 210 float fps, FrameRateRange expectedFrameRateRange) { 211 return (fps + FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE >= expectedFrameRateRange.min) 212 && (fps - FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE <= expectedFrameRateRange.max); 213 } 214 215 // Waits until our SurfaceHolder has a surface and the activity is resumed. waitForPreconditions()216 private void waitForPreconditions() throws InterruptedException { 217 assertTrue( 218 "Activity was unexpectedly destroyed", !isDestroyed()); 219 if (mSurface == null || mActivityState != ActivityState.RUNNING) { 220 Log.i(TAG, String.format( 221 "Waiting for preconditions. Have surface? %b. Activity resumed? %b.", 222 mSurface != null, mActivityState == ActivityState.RUNNING)); 223 } 224 long nowNanos = System.nanoTime(); 225 long endTimeNanos = nowNanos + PRECONDITION_WAIT_TIMEOUT_NANOSECONDS; 226 while (mSurface == null || mActivityState != ActivityState.RUNNING) { 227 long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000; 228 assertTrue(String.format("Timed out waiting for preconditions. Have surface? %b." 229 + " Activity resumed? %b.", 230 mSurface != null, mActivityState == ActivityState.RUNNING), 231 timeRemainingMillis > 0); 232 mLock.wait(timeRemainingMillis); 233 assertTrue( 234 "Activity was unexpectedly destroyed", !isDestroyed()); 235 nowNanos = System.nanoTime(); 236 } 237 } 238 239 // Returns true if we encounter a precondition violation, false otherwise. waitForPreconditionViolation()240 private boolean waitForPreconditionViolation() throws InterruptedException { 241 assertTrue( 242 "Activity was unexpectedly destroyed", !isDestroyed()); 243 long nowNanos = System.nanoTime(); 244 long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS; 245 while (mSurface != null && mActivityState == ActivityState.RUNNING) { 246 long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000; 247 if (timeRemainingMillis <= 0) { 248 break; 249 } 250 mLock.wait(timeRemainingMillis); 251 assertTrue( 252 "Activity was unexpectedly destroyed", !isDestroyed()); 253 nowNanos = System.nanoTime(); 254 } 255 return mSurface == null || mActivityState != ActivityState.RUNNING; 256 } 257 verifyPreconditions()258 private void verifyPreconditions() { 259 if (mSurface == null || mActivityState != ActivityState.RUNNING) { 260 throw new PreconditionViolatedException(); 261 } 262 } 263 264 // Returns true if we reached waitUntilNanos, false if some other event occurred. waitForEvents(long waitUntilNanos)265 private boolean waitForEvents(long waitUntilNanos) 266 throws InterruptedException { 267 mRefreshRateChangedEvents.clear(); 268 long nowNanos = System.nanoTime(); 269 while (nowNanos < waitUntilNanos) { 270 long surfacePostTime = mLastBufferPostTime + POST_BUFFER_INTERVAL_NANOSECONDS; 271 long timeoutNs = Math.min(waitUntilNanos, surfacePostTime) - nowNanos; 272 long timeoutMs = timeoutNs / 1_000_000L; 273 int remainderNs = (int) (timeoutNs % 1_000_000L); 274 // Don't call wait(0, 0) - it blocks indefinitely. 275 if (timeoutMs > 0 || remainderNs > 0) { 276 mLock.wait(timeoutMs, remainderNs); 277 } 278 nowNanos = System.nanoTime(); 279 verifyPreconditions(); 280 if (!mRefreshRateChangedEvents.isEmpty()) { 281 return false; 282 } 283 if (nowNanos >= surfacePostTime) { 284 postBufferToSurface(Color.RED); 285 } 286 } 287 return true; 288 } 289 waitForRefreshRateChange(FrameRateRange expectedRefreshRate)290 private void waitForRefreshRateChange(FrameRateRange expectedRefreshRate) 291 throws InterruptedException { 292 Log.i(TAG, "Waiting for the refresh rate to change"); 293 long nowNanos = System.nanoTime(); 294 long gracePeriodEndTimeNanos = 295 nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS; 296 while (true) { 297 // Wait until we switch to the expected refresh rate 298 while (!frameRatesMatchesOverride(mReportedDisplayRefreshRate, expectedRefreshRate) 299 && !waitForEvents(gracePeriodEndTimeNanos)) { 300 // Empty 301 } 302 nowNanos = System.nanoTime(); 303 if (nowNanos >= gracePeriodEndTimeNanos) { 304 throw new FrameRateTimeoutException(expectedRefreshRate, 305 mReportedDisplayRefreshRate); 306 } 307 308 // We've switched to a compatible frame rate. Now wait for a while to see if we stay at 309 // that frame rate. 310 long endTimeNanos = nowNanos + STABLE_FRAME_RATE_WAIT_NANOSECONDS; 311 while (endTimeNanos > nowNanos) { 312 if (waitForEvents(endTimeNanos)) { 313 Log.i(TAG, String.format("Stable frame rate %.2f verified", 314 mReportedDisplayRefreshRate)); 315 return; 316 } 317 nowNanos = System.nanoTime(); 318 if (!mRefreshRateChangedEvents.isEmpty()) { 319 break; 320 } 321 } 322 } 323 } 324 325 // Returns a range of frame rate that is accepted to 326 // make this test more flexible for VRR devices. 327 // For example, a frame rate of 80 is valid for a 120 Hz VRR display, 328 // but only 60 is available when 80 is requested on non-VRR display. 329 // Here we return the range of the requested override +- the gap between the closest divisor. getExpectedFrameRate(float refreshRate, int frameRateOverride)330 private FrameRateRange getExpectedFrameRate(float refreshRate, int frameRateOverride) { 331 float divisorOverrideGap = Float.MAX_VALUE; 332 FrameRateRange expectedFrameRateRange = new FrameRateRange(0.f, 0.f); 333 final float refreshRateRounded = Math.round(refreshRate); 334 for (int divisor = 1; refreshRateRounded / divisor >= 30; ++divisor) { 335 float frameRate = refreshRateRounded / divisor; 336 if (frameRate > frameRateOverride) { 337 continue; 338 } 339 if (Math.abs(frameRateOverride - frameRate) <= divisorOverrideGap) { 340 divisorOverrideGap = Math.abs(frameRateOverride - frameRate); 341 } 342 } 343 expectedFrameRateRange.min = frameRateOverride - divisorOverrideGap; 344 expectedFrameRateRange.max = frameRateOverride + divisorOverrideGap; 345 Log.i(TAG, String.format("getExpectedFrameRate expectedFrameRate %.2f %.2f", 346 expectedFrameRateRange.min, expectedFrameRateRange.max)); 347 return expectedFrameRateRange; 348 } 349 350 interface FrameRateObserver { observe(float initialRefreshRate, FrameRateRange expectedFrameRate, String condition)351 void observe(float initialRefreshRate, FrameRateRange expectedFrameRate, String condition) 352 throws InterruptedException; 353 } 354 355 class BackpressureFrameRateObserver implements FrameRateObserver { 356 @Override observe(float initialRefreshRate, FrameRateRange expectedFrameRate, String condition)357 public void observe(float initialRefreshRate, 358 FrameRateRange expectedFrameRate, String condition) { 359 long startTime = System.nanoTime(); 360 int totalBuffers = 0; 361 float fps = 0; 362 while (System.nanoTime() - startTime <= FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) { 363 postBufferToSurface(Color.BLACK + totalBuffers); 364 totalBuffers++; 365 if (System.nanoTime() - startTime >= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) { 366 float testDuration = (System.nanoTime() - startTime) / 1e9f; 367 fps = totalBuffers / testDuration; 368 if (frameRatesMatchesOverride(fps, expectedFrameRate)) { 369 Log.i(TAG, 370 String.format("%s: backpressure observed refresh rate %.2f", 371 condition, 372 fps)); 373 return; 374 } 375 } 376 } 377 378 assertTrue(String.format( 379 "%s: backpressure observed refresh rate doesn't match the current" 380 + "refresh rate. " 381 + "expected: (%.2f, %.2f) observed: %.2f", 382 condition, expectedFrameRate.min, expectedFrameRate.max, fps), 383 frameRatesMatchesOverride(fps, expectedFrameRate)); 384 } 385 } 386 387 class ChoreographerFrameRateObserver implements FrameRateObserver { 388 class ChoreographerThread extends Thread implements Choreographer.FrameCallback { 389 Choreographer mChoreographer; 390 long mStartTime; 391 public Handler mHandler; 392 Looper mLooper; 393 int mTotalCallbacks = 0; 394 long mEndTime; 395 FrameRateRange mExpectedRefreshRate; 396 String mCondition; 397 ChoreographerThread(FrameRateRange expectedRefreshRate, String condition)398 ChoreographerThread(FrameRateRange expectedRefreshRate, String condition) 399 throws InterruptedException { 400 mExpectedRefreshRate = expectedRefreshRate; 401 mCondition = condition; 402 } 403 404 @Override run()405 public void run() { 406 Looper.prepare(); 407 mChoreographer = Choreographer.getInstance(); 408 mHandler = new Handler(); 409 mLooper = Looper.myLooper(); 410 mStartTime = System.nanoTime(); 411 mChoreographer.postFrameCallback(this); 412 Looper.loop(); 413 } 414 415 @Override doFrame(long frameTimeNanos)416 public void doFrame(long frameTimeNanos) { 417 mTotalCallbacks++; 418 mEndTime = System.nanoTime(); 419 if (mEndTime - mStartTime <= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) { 420 mChoreographer.postFrameCallback(this); 421 return; 422 } else if (frameRatesMatchesOverride(getFps(), mExpectedRefreshRate) 423 || mEndTime - mStartTime > FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) { 424 mLooper.quitSafely(); 425 return; 426 } 427 mChoreographer.postFrameCallback(this); 428 } 429 verifyFrameRate()430 public void verifyFrameRate() throws InterruptedException { 431 float fps = getFps(); 432 Log.i(TAG, 433 String.format("%s: choreographer observed refresh rate %.2f", 434 mCondition, 435 fps)); 436 assertTrue(String.format( 437 "%s: choreographer observed refresh rate doesn't match the current " 438 + "refresh rate. expected: (%.2f, %.2f) observed: %.2f", 439 mCondition, 440 mExpectedRefreshRate.min, 441 mExpectedRefreshRate.max, 442 fps), 443 frameRatesMatchesOverride(fps, mExpectedRefreshRate)); 444 } 445 getFps()446 private float getFps() { 447 return mTotalCallbacks / ((mEndTime - mStartTime) / 1e9f); 448 } 449 } 450 451 @Override observe(float initialRefreshRate, FrameRateRange expectedFrameRate, String condition)452 public void observe(float initialRefreshRate, FrameRateRange expectedFrameRate, 453 String condition) throws InterruptedException { 454 ChoreographerThread thread = new ChoreographerThread(expectedFrameRate, condition); 455 thread.start(); 456 thread.join(); 457 thread.verifyFrameRate(); 458 } 459 } 460 461 class DisplayGetRefreshRateFrameRateObserver implements FrameRateObserver { 462 @Override observe(float initialRefreshRate, FrameRateRange expectedFrameRate, String condition)463 public void observe(float initialRefreshRate, 464 FrameRateRange expectedFrameRate, String condition) { 465 Log.i(TAG, 466 String.format("%s: Display.getRefreshRate() returned refresh rate %.2f", 467 condition, mReportedDisplayRefreshRate)); 468 assertTrue(String.format("%s: Display.getRefreshRate() doesn't match the " 469 + "current refresh. expected: (%.2f, %.2f) observed: %.2f", 470 condition, expectedFrameRate.min, 471 expectedFrameRate.max, mReportedDisplayRefreshRate), 472 frameRatesMatchesOverride(mReportedDisplayRefreshRate, expectedFrameRate)); 473 } 474 } 475 class DisplayModeGetRefreshRateFrameRateObserver implements FrameRateObserver { 476 477 @Override observe(float initialRefreshRate, FrameRateRange expectedFrameRate, String condition)478 public void observe(float initialRefreshRate, 479 FrameRateRange expectedFrameRate, String condition) { 480 Log.i(TAG, 481 String.format( 482 "%s: Display.getMode().getRefreshRate() returned refresh rate %.2f", 483 condition, mReportedDisplayModeRefreshRate)); 484 assertTrue(String.format("%s: Display.getMode().getRefreshRate() doesn't match the " 485 + "current refresh. expected: %.2f observed: %.2f", condition, 486 initialRefreshRate, mReportedDisplayModeRefreshRate), 487 frameRatesMatchesOverride(mReportedDisplayModeRefreshRate, initialRefreshRate)); 488 } 489 } 490 491 interface TestScenario { test(FrameRateObserver frameRateObserver, float initialRefreshRate, int[] frameRateOverrides)492 void test(FrameRateObserver frameRateObserver, float initialRefreshRate, 493 int[] frameRateOverrides) throws InterruptedException, IOException; 494 } 495 496 class GameModeTest implements TestScenario { 497 private UiDevice mUiDevice; GameModeTest(UiDevice uiDevice)498 GameModeTest(UiDevice uiDevice) { 499 mUiDevice = uiDevice; 500 } 501 @Override test(FrameRateObserver frameRateObserver, float initialRefreshRate, int[] frameRateOverrides)502 public void test(FrameRateObserver frameRateObserver, float initialRefreshRate, 503 int[] frameRateOverrides) throws InterruptedException, IOException { 504 Log.i(TAG, "Starting testGameModeFrameRateOverride"); 505 506 for (int frameRateOverride : frameRateOverrides) { 507 508 Log.i(TAG, String.format("Setting Frame Rate to %d using Game Mode", 509 frameRateOverride)); 510 511 // Given that the frame rate we attempt to override is not always the divisor of 512 // the current refresh rate, get the expected frame rate, which is the closest 513 // divisor of the refresh rate here. 514 FrameRateRange expectedFrameRate = 515 getExpectedFrameRate(initialRefreshRate, frameRateOverride); 516 517 mUiDevice.executeShellCommand(String.format("cmd game set --mode 2 --fps %d %s", 518 frameRateOverride, getPackageName())); 519 520 waitForRefreshRateChange(expectedFrameRate); 521 frameRateObserver.observe(initialRefreshRate, expectedFrameRate, 522 String.format("Game Mode Override(%d), expectedFrameRate(%.2f %.2f)", 523 frameRateOverride, expectedFrameRate.min, expectedFrameRate.max)); 524 } 525 526 527 Log.i(TAG, String.format("Resetting game mode.")); 528 529 FrameRateRange expectedFrameRate = 530 getExpectedFrameRate(initialRefreshRate, GAME_DEFAULT_FRAMERATE_INT); 531 mUiDevice.executeShellCommand(String.format("cmd game reset %s", getPackageName())); 532 waitForRefreshRateChange(expectedFrameRate); 533 frameRateObserver.observe(initialRefreshRate, expectedFrameRate, 534 String.format("Game Default Frame Rate(%d), expectedFrameRate(%.2f %.2f)", 535 GAME_DEFAULT_FRAMERATE_INT, 536 expectedFrameRate.min, 537 expectedFrameRate.max)); 538 } 539 } 540 541 // The activity being intermittently paused/resumed has been observed to 542 // cause test failures in practice, so we run the test with retry logic. testFrameRateOverride(TestScenario frameRateOverrideBehavior, FrameRateObserver frameRateObserver, float initialRefreshRate, int[] frameRateOverrides)543 public void testFrameRateOverride(TestScenario frameRateOverrideBehavior, 544 FrameRateObserver frameRateObserver, 545 float initialRefreshRate, 546 int[] frameRateOverrides) 547 throws InterruptedException, IOException { 548 synchronized (mLock) { 549 Log.i(TAG, "testFrameRateOverride started with initial refresh rate " 550 + initialRefreshRate); 551 int attempts = 0; 552 boolean testPassed = false; 553 try { 554 while (!testPassed) { 555 waitForPreconditions(); 556 try { 557 frameRateOverrideBehavior.test(frameRateObserver, 558 initialRefreshRate, frameRateOverrides); 559 testPassed = true; 560 } catch (PreconditionViolatedException exc) { 561 // The logic below will retry if we're below max attempts. 562 } catch (FrameRateTimeoutException exc) { 563 // Sometimes we get a test timeout failure before we get the 564 // notification that the activity was paused, and it was the pause that 565 // caused the timeout failure. Wait for a bit to see if we get notified 566 // of a precondition violation, and if so, retry the test. Otherwise, 567 // fail. 568 assertTrue( 569 String.format( 570 "Timed out waiting for a stable and compatible frame" 571 + " rate. requested=(%.2f, %.2f) received=%.2f.", 572 exc.appRequestedFrameRate.min, 573 exc.appRequestedFrameRate.max, 574 exc.deviceRefreshRate), 575 waitForPreconditionViolation()); 576 } 577 578 if (!testPassed) { 579 Log.i(TAG, 580 String.format("Preconditions violated while running the test." 581 + " Have surface? %b. Activity resumed? %b.", 582 mSurface != null, 583 mActivityState == ActivityState.RUNNING)); 584 attempts++; 585 assertTrue(String.format( 586 "Exceeded %d precondition wait attempts. Giving up.", 587 PRECONDITION_WAIT_MAX_ATTEMPTS), 588 attempts < PRECONDITION_WAIT_MAX_ATTEMPTS); 589 } 590 } 591 } finally { 592 if (testPassed) { 593 Log.i(TAG, "**** PASS ****"); 594 } else { 595 Log.i(TAG, "**** FAIL ****"); 596 } 597 } 598 599 } 600 } 601 } 602