diff options
| -rw-r--r-- | tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java | 185 |
1 files changed, 157 insertions, 28 deletions
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java index 48d050ce4391..bb0d30af42ee 100644 --- a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java +++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java @@ -22,9 +22,9 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import android.Manifest; -import android.app.compat.CompatChanges; import android.hardware.display.DisplayManager; import android.os.Looper; import android.support.test.uiautomator.UiDevice; @@ -34,6 +34,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.WindowManager; import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ActivityScenario; @@ -52,7 +53,7 @@ import java.util.concurrent.TimeUnit; public class AttachedChoreographerTest { private static final String TAG = "AttachedChoreographerTest"; private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758; - private static final long THRESHOLD_MS = 10; + private static final long THRESHOLD_MS = 4; private static final int FRAME_ITERATIONS = 21; private static final int CALLBACK_MISSED_THRESHOLD = 2; @@ -66,7 +67,7 @@ public class AttachedChoreographerTest { private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private Choreographer mChoreographer; - private boolean mIsFirstCallback = true; + private long mExpectedPresentTime; private int mCallbackMissedCounter = 0; @Before @@ -74,6 +75,13 @@ public class AttachedChoreographerTest { mScenario = ActivityScenario.launch(GraphicsActivity.class); mScenario.moveToState(Lifecycle.State.CREATED); mScenario.onActivity(activity -> { + // Lock the display frame rate. This will not allow SurfaceFlinger to use the frame rate + // override feature that throttles down the global choreographer for this test. + float refreshRate = activity.getDisplay().getMode().getRefreshRate(); + WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); + attrs.preferredRefreshRate = refreshRate; + activity.getWindow().setAttributes(attrs); + mSurfaceView = activity.findViewById(R.id.surface); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { @@ -95,6 +103,12 @@ public class AttachedChoreographerTest { }); UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + + // TODO(b/290634611): clean this up once SF new front end is enabled by default + boolean sfNewFeEnabled = uiDevice.executeShellCommand("dumpsys SurfaceFlinger") + .indexOf("SurfaceFlinger New Frontend Enabled:true") != -1; + assumeTrue(sfNewFeEnabled); + uiDevice.wakeUp(); uiDevice.executeShellCommand("wm dismiss-keyguard"); mScenario.moveToState(Lifecycle.State.RESUMED); @@ -112,18 +126,16 @@ public class AttachedChoreographerTest { mDisplayManager.setRefreshRateSwitchingType( DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY); mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true); - boolean changeIsEnabled = - CompatChanges.isChangeEnabled( - DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID); - Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is " - + (changeIsEnabled ? "enabled" : "disabled")); }); } @After public void tearDown() { - mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate); - mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false); + if (mDisplayManager != null) { + mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate); + mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false); + } + InstrumentationRegistry.getInstrumentation().getUiAutomation() .dropShellPermissionIdentity(); } @@ -407,18 +419,141 @@ public class AttachedChoreographerTest { } } + @Test + public void testChoreographerAttachedAfterSetFrameRate() { + Log.i(TAG, "adyabr: starting testChoreographerAttachedAfterSetFrameRate"); + + class TransactionGenerator implements SurfaceControl.TransactionCommittedListener { + private SurfaceControl mSc; + private int mFrame; + private static final int NUM_FRAMES = 50; + private final CountDownLatch mCompleteLatch = new CountDownLatch(1); + + TransactionGenerator(SurfaceControl sc) { + mSc = sc; + + } + + @Override + public void onTransactionCommitted() { + mFrame++; + if (mFrame >= NUM_FRAMES) { + mCompleteLatch.countDown(); + return; + } + + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.setAlpha(mSc, 1.0f / mFrame) + .addTransactionCommittedListener(Runnable::run, this) + .apply(); + + } + + public void startAndWaitForCompletion() { + onTransactionCommitted(); + if (waitForCountDown(mCompleteLatch, /* timeoutInSeconds */ 10L)) { + fail("failed to send new transactions"); + } + } + } + + + for (int divisor : new int[]{2, 3}) { + CountDownLatch choreographerLatch = new CountDownLatch(1); + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate(); + float fps = displayRefreshRate / divisor; + long callbackDurationMs = Math.round(1000 / fps); + mCallbackMissedCounter = 0; + transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) + .apply(); + + + new TransactionGenerator(sc).startAndWaitForCompletion(); + + Choreographer choreographer = sc.getChoreographer(); + verifyVsyncCallbacks(choreographer, callbackDurationMs, + choreographerLatch, FRAME_ITERATIONS); + }); + // wait for the previous callbacks to finish before moving to the next divisor + if (waitForCountDown(choreographerLatch, /* timeoutInSeconds */ 5L)) { + fail("Test not finished in 5 Seconds"); + } + } + } + + @Test + public void testChoreographerNonDivisorFixedSourceRefreshRate() { + CountDownLatch continueLatch = new CountDownLatch(1); + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + Choreographer choreographer = sc.getChoreographer(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate(); + float fps = 61.7f; // hopefully not a divisor + long callbackDurationMs = Math.round(1000 / displayRefreshRate); + mCallbackMissedCounter = 0; + transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) + .addTransactionCommittedListener(Runnable::run, + () -> verifyVsyncCallbacks(choreographer, + callbackDurationMs, continueLatch, FRAME_ITERATIONS)) + .apply(); + }); + // wait for the previous callbacks to finish before moving to the next divisor + if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) { + fail("Test not finished in 5 Seconds"); + } + } + + @Test + public void testChoreographerNonDivisorRefreshRate() { + CountDownLatch continueLatch = new CountDownLatch(1); + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + Choreographer choreographer = sc.getChoreographer(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate(); + float fps = 61.7f; // hopefully not a divisor + float expectedFps = displayRefreshRate / Math.round(displayRefreshRate / fps); + long callbackDurationMs = Math.round(1000 / expectedFps); + mCallbackMissedCounter = 0; + transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) + .addTransactionCommittedListener(Runnable::run, + () -> verifyVsyncCallbacks(choreographer, + callbackDurationMs, continueLatch, FRAME_ITERATIONS)) + .apply(); + }); + // wait for the previous callbacks to finish before moving to the next divisor + if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) { + fail("Test not finished in 5 Seconds"); + } + } + private void verifyVsyncCallbacks(Choreographer choreographer, long callbackDurationMs, CountDownLatch continueLatch, int frameCount) { - long callbackRequestedTimeNs = System.nanoTime(); choreographer.postVsyncCallback(frameData -> { + long expectedPresentTime = + frameData.getPreferredFrameTimeline().getExpectedPresentationTimeNanos(); if (frameCount > 0) { - if (!mIsFirstCallback) { - // Skip the first callback as it takes 1 frame - // to reflect the new refresh rate - long callbackDurationDiffMs = getCallbackDurationDiffInMs( - frameData.getFrameTimeNanos(), - callbackRequestedTimeNs, callbackDurationMs); - if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) { + if (mExpectedPresentTime > 0) { + long callbackDurationDiffMs = + TimeUnit.NANOSECONDS.toMillis( + expectedPresentTime - mExpectedPresentTime); + if (callbackDurationDiffMs < 0 + || Math.abs(callbackDurationMs - callbackDurationDiffMs) + > THRESHOLD_MS) { mCallbackMissedCounter++; Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS) + " vsync callback failed, expected callback in " @@ -427,25 +562,19 @@ public class AttachedChoreographerTest { + " but actual duration difference is " + callbackDurationDiffMs); } } - mIsFirstCallback = false; + mExpectedPresentTime = expectedPresentTime; verifyVsyncCallbacks(choreographer, callbackDurationMs, continueLatch, frameCount - 1); } else { - assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while " - + CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed", + assertTrue("Missed timeline for " + mCallbackMissedCounter + + " callbacks, while " + CALLBACK_MISSED_THRESHOLD + + " missed callbacks are allowed", mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD); continueLatch.countDown(); } }); } - private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs, - long expectedCallbackMs) { - long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs) - - TimeUnit.NANOSECONDS.toMillis(requestedTimeNs); - return Math.abs(expectedCallbackMs - actualTimeMs); - } - private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) { try { return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS); |