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

Commit 079fc836 authored by George Mount's avatar George Mount
Browse files

Don't let two close frames to interrupt intermittency

Fixes: 336840867

There is a common case where two frames follow quickly during
intermittent updates. This CL requires that at least three
frames take less than 100ms before intermittency is not considered.

Also, some same-frame movement results in 0 overall motion.
For example, a setFrame() call that moves content will follow-on
with an offsetTopAndBottom() that moves it back. Now, the
previous frame's position is tracked and compared instead
of only flagging the motion.

Test: new test, existing tests
Change-Id: Ia2c9dbc5485b0af34b4f4aa5f3a1dda4cb8552f7
parent dbf2dd62
Loading
Loading
Loading
Loading
+20 −8
Original line number Diff line number Diff line
@@ -4743,6 +4743,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    @ViewDebug.ExportedProperty(category = "layout")
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    protected int mLeft;
    /**
     * The mLeft from the previous frame. Used for detecting movement for purposes of variable
     * refresh rate.
     */
    private int mLastFrameLeft;
    /**
     * The distance in pixels from the left edge of this view's parent
     * to the right edge of this view.
@@ -4759,6 +4764,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    @ViewDebug.ExportedProperty(category = "layout")
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    protected int mTop;
    /**
     * The mTop from the previous frame. Used for detecting movement for purposes of variable
     * refresh rate.
     */
    private int mLastFrameTop;
    /**
     * The distance in pixels from the top edge of this view's parent
     * to the bottom edge of this view.
@@ -19516,7 +19526,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    public final void setTop(int top) {
        if (top != mTop) {
            mPrivateFlags4 |= PFLAG4_HAS_MOVED;
            final boolean matrixIsIdentity = hasIdentityMatrix();
            if (matrixIsIdentity) {
                if (mAttachInfo != null) {
@@ -20408,7 +20417,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    public void offsetTopAndBottom(int offset) {
        if (offset != 0) {
            mPrivateFlags4 |= PFLAG4_HAS_MOVED;
            final boolean matrixIsIdentity = hasIdentityMatrix();
            if (matrixIsIdentity) {
                if (isHardwareAccelerated()) {
@@ -20460,7 +20468,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    public void offsetLeftAndRight(int offset) {
        if (offset != 0) {
            mPrivateFlags4 |= PFLAG4_HAS_MOVED;
            final boolean matrixIsIdentity = hasIdentityMatrix();
            if (matrixIsIdentity) {
                if (isHardwareAccelerated()) {
@@ -25504,7 +25511,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            mPrivateFlags4 |= PFLAG4_HAS_MOVED;
            changed = true;
            // Remember our drawn bit
@@ -33948,8 +33954,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            // The most common case is when nothing is set, so this special case is called
            // often.
            if (mAttachInfo.mViewVelocityApi
                    && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
                    PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)
                    && ((mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
                    PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) || mLastFrameLeft != mLeft
                    || mLastFrameTop != mTop)
                    && viewRootImpl.shouldCheckFrameRate(false)
                    && parent instanceof View
                    && ((View) parent).mFrameContentVelocity <= 0) {
@@ -33962,14 +33969,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
                mLastFrameRateCategory = frameRateCategory;
            }
            mLastFrameLeft = mLeft;
            mLastFrameTop = mTop;
            return;
        }
        if (viewRootImpl.shouldCheckFrameRate(frameRate > 0f)) {
            float velocityFrameRate = 0f;
            if (mAttachInfo.mViewVelocityApi) {
                if (velocity < 0f
                        && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
                        PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)
                        && ((mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
                        PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) || mLastFrameLeft != mLeft
                        || mLastFrameTop != mTop)
                        && mParent instanceof View
                        && ((View) mParent).mFrameContentVelocity <= 0
                ) {
@@ -34034,6 +34044,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
            mLastFrameRateCategory = frameRateCategory;
        }
        mLastFrameLeft = mLeft;
        mLastFrameTop = mTop;
    }
    private float convertVelocityToFrameRate(float velocityPps) {
+2 −1
Original line number Diff line number Diff line
@@ -12974,7 +12974,8 @@ public final class ViewRootImpl implements ViewParent,
        mMinusOneFrameIntervalMillis = timeIntervalMillis;
        mLastUpdateTimeMillis = currentTimeMillis;
        if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
        if (timeIntervalMillis + mMinusTwoFrameIntervalMillis
                >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
            int infrequentUpdateCount = mInfrequentUpdateCount;
            mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
                    ? infrequentUpdateCount : infrequentUpdateCount + 1;
+75 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.view;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
@@ -435,6 +436,80 @@ public class ViewFrameRateTest {
        waitForAfterDraw();
    }

    /**
     * A common behavior is for two different views to be invalidated in succession, but
     * intermittently. We want to treat this as an intermittent invalidation.
     *
     * This test will only succeed on non-cuttlefish devices, so it is commented out
     * for potential manual testing.
     */
//    @Test
    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
    public void intermittentDoubleInvalidate() throws Throwable {
        View parent = (View) mMovingView.getParent();
        mActivityRule.runOnUiThread(() -> {
            parent.setWillNotDraw(false);
            // Make sure the View is large
            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
            layoutParams.width = parent.getWidth();
            layoutParams.height = parent.getHeight();
            mMovingView.setLayoutParams(layoutParams);
        });
        waitForFrameRateCategoryToSettle();
        for (int i = 0; i < 5; i++) {
            int expectedCategory;
            if (i < 4) {
                // not intermittent yet.
                // It takes 2 frames of intermittency before Views vote as intermittent.
                // It takes 4 more frames for the category to drop to the next category.
                expectedCategory =
                        toolkitFrameRateDefaultNormalReadOnly() ? FRAME_RATE_CATEGORY_NORMAL
                                : FRAME_RATE_CATEGORY_HIGH;
            } else {
                // intermittent
                expectedCategory = FRAME_RATE_CATEGORY_NORMAL;
            }
            mActivityRule.runOnUiThread(() -> {
                mMovingView.invalidate();
                runAfterDraw(() -> assertEquals(expectedCategory,
                        mViewRoot.getLastPreferredFrameRateCategory()));
            });
            waitForAfterDraw();
            mActivityRule.runOnUiThread(() -> {
                parent.invalidate();
                runAfterDraw(() -> assertEquals(expectedCategory,
                        mViewRoot.getLastPreferredFrameRateCategory()));
            });
            waitForAfterDraw();
            Thread.sleep(90);
        }
    }

    // When a view has two motions that offset each other, the overall motion
    // should be canceled and be considered unmoved.
    @Test
    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
    })
    public void sameFrameMotion() throws Throwable {
        mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
        waitForFrameRateCategoryToSettle();

        mActivityRule.runOnUiThread(() -> {
            mMovingView.offsetLeftAndRight(10);
            mMovingView.offsetLeftAndRight(-10);
            mMovingView.offsetTopAndBottom(100);
            mMovingView.offsetTopAndBottom(-100);
            mMovingView.invalidate();
            runAfterDraw(() -> {
                assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
                assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
                        mViewRoot.getLastPreferredFrameRateCategory());
            });
        });
        waitForAfterDraw();
    }
    private void runAfterDraw(@NonNull Runnable runnable) {
        Handler handler = new Handler(Looper.getMainLooper());
        mAfterDrawLatch = new CountDownLatch(1);