Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c3a410d7 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "BouncyBall: Test at default frame rate" into main

parents 29f023c2 27f68f45
Loading
Loading
Loading
Loading
+41 −30
Original line number Diff line number Diff line
@@ -46,17 +46,26 @@ public class BouncyBallActivity extends AppCompatActivity {

    private static final String LOG_TAG = "BouncyBall";

    private static final float DESIRED_FRAME_RATE = 60.0f;

    // Our focus isn't smoothness on startup; it's smoothness once we're
    // running.  So we ignore frame drops in the first 0.1 seconds.
    private static final int INITIAL_FRAMES_TO_IGNORE = 6;
    // The app needs to run at at least this frame rate to be a valid test.
    // If the system defaults us to a higher frame rate, we'll test with that,
    // but we need to at least meet this rate.
    private static final float MINIMUM_FRAME_RATE = 60.0f;

    // This test measures sustained frame rate, so it's safe to ignore
    // frame drops around the start time.
    // This value must be high enough to skip jank due to clocks not having
    // ramped up yet.
    // This value must not be too high as to miss jank due to clocks ramping
    // down.
    private static final float INITIAL_TIME_TO_IGNORE_IN_SECONDS = 0.1f;

    private int mDisplayId = -1;
    private boolean mHasFocus = false;
    private boolean mWarmedUp = false;
    private float mFrameRate;
    private long mFrameMaxDurationNanos;
    private int mFrameCount = 0;
    private int mFirstAutomatedTestFrame = -1;
    private int mNumFramesDropped = 0;
    private Choreographer mChoreographer;

@@ -83,11 +92,10 @@ public class BouncyBallActivity extends AppCompatActivity {
            new Choreographer.FrameCallback() {

                private long mLastFrameTimeNanos = -1;
                private int mFrameCount = 0;

                @Override
                public void doFrame(long frameTimeNanos) {
                    if (mFrameCount == INITIAL_FRAMES_TO_IGNORE) {
                    if (mFrameCount == mFirstAutomatedTestFrame) {
                        mWarmedUp = true;
                        if (!mHasFocus) {
                            String msg = "App does not have focus after "
@@ -99,7 +107,8 @@ public class BouncyBallActivity extends AppCompatActivity {
                        long elapsedNanos = frameTimeNanos - mLastFrameTimeNanos;
                        if (elapsedNanos > mFrameMaxDurationNanos) {
                            mNumFramesDropped++;
                            Log.e(LOG_TAG, "FRAME DROPPED (total " + mNumFramesDropped
                            Log.e(LOG_TAG, "DROPPED FRAME #" + mFrameCount
                                    + " (total " + mNumFramesDropped
                                    + "): Took " + nanosToMillis(elapsedNanos) + "ms");
                        } else if (LOG_EVERY_FRAME) {
                            Log.d(LOG_TAG, "Frame " + mFrameCount + " took "
@@ -138,7 +147,7 @@ public class BouncyBallActivity extends AppCompatActivity {
                                        DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE,
                                        mDisplayListener);

        setFrameRatePreference();
        initFrameRate();
        mChoreographer = Choreographer.getInstance();
        mChoreographer.postFrameCallback(mFrameCallback);
        Trace.endSection();
@@ -156,29 +165,27 @@ public class BouncyBallActivity extends AppCompatActivity {
        mHasFocus = hasFocus;
    }

    // If available at our current resolution, use 60Hz.  If not, use the
    // lowest refresh rate above 60Hz which is available.  Otherwise, throw
    // an exception which kills the app.
    //
    // The philosophy is that, for now, we only require this test to run
    // solidly at 60Hz.  If a device has a higher refresh rate than that,
    // we slow it down to 60Hz to make this easier to pass.  If a device
    // isn't able to go all the way down to 60Hz, we use the lowest refresh
    // rate above 60Hz.  If the device only supports below 30Hz, that's below
    // our standards so we abort.
    private void setFrameRatePreference() {
        float preferredRate = Float.POSITIVE_INFINITY;

    private void initFrameRate() {
        Display display = getDisplay();
        Display.Mode currentMode = display.getMode();
        mDisplayId = display.getDisplayId();
        setFrameRate(currentMode.getRefreshRate());
        if (mFrameRate == DESIRED_FRAME_RATE) {
            Log.i(LOG_TAG, "Already running at " + mFrameRate + "Hz");
            // We're already using what we want.  Nothing to do here.
        if (mFrameRate >= MINIMUM_FRAME_RATE) {
            // The default frame rate is sufficient for our testing.
            return;
        }

        String minRateStr = MINIMUM_FRAME_RATE + "Hz";
        // Using a Warning here, because this seems unexpected that a device
        // defaults to running at below 60Hz.
        Log.w(LOG_TAG, "Default frame rate (" + mFrameRate
                  + "Hz) is below the acceptable minimum (" + minRateStr + ")");

        // If available at our current resolution, use 60Hz.  If not, use the
        // lowest refresh rate above 60Hz which is available.  Otherwise, throw
        // an exception which kills the app.
        float preferredRate = Float.POSITIVE_INFINITY;

        for (Display.Mode mode : display.getSupportedModes()) {
            if ((currentMode.getPhysicalHeight() != mode.getPhysicalHeight())
                    || (currentMode.getPhysicalWidth() != mode.getPhysicalWidth())) {
@@ -186,24 +193,23 @@ public class BouncyBallActivity extends AppCompatActivity {
                continue;
            }
            float rate = mode.getRefreshRate();
            if (rate == DESIRED_FRAME_RATE) {
            if (rate == MINIMUM_FRAME_RATE) {
                // This is exactly what we were hoping for, so we can stop
                // looking.
                preferredRate = rate;
                break;
            }
            if ((rate > DESIRED_FRAME_RATE) && (rate < preferredRate)) {
            if ((rate > MINIMUM_FRAME_RATE) && (rate < preferredRate)) {
                // This is the best rate we've seen so far in terms of being
                // closest to our desired rate without being under it.
                preferredRate = rate;
            }
        }
        if (preferredRate == Float.POSITIVE_INFINITY) {
            String msg = "No display mode with at least " + DESIRED_FRAME_RATE + "Hz";
            String msg = "No display mode with at least " + minRateStr;
            throw new RuntimeException(msg);
        }
        Log.i(LOG_TAG, "Changing preferred rate from " + mFrameRate + "Hz to "
                + preferredRate + "Hz");
        Log.i(LOG_TAG, "Requesting to run at " + preferredRate + "Hz");
        Window window = getWindow();
        WindowManager.LayoutParams params = window.getAttributes();
        params.preferredRefreshRate = preferredRate;
@@ -221,6 +227,11 @@ public class BouncyBallActivity extends AppCompatActivity {
        // We store as nanoseconds, to avoid per-frame floating point math in
        // the common case.
        mFrameMaxDurationNanos = ((long) frameMaxDurationMillis) * 1_000_000;

        Log.i(LOG_TAG, "Running at frame rate " + mFrameRate + "Hz");

        mFirstAutomatedTestFrame =
            Math.round(INITIAL_TIME_TO_IGNORE_IN_SECONDS * mFrameRate);
    }

    private float nanosToMillis(long nanos) {