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

Commit c31191be authored by George Mount's avatar George Mount
Browse files

Change how "small view updates" are calculated.

Fixes: 328508522

Changes small views to be anything smaller than 40dp x 40dp
or any one dimension smaller than 10dp.

Also adds reasoning to the trace to improve understanding
of why a frame rate is chosen.

Test: New tests
Change-Id: I227e221d34a8fb81a1791cbfc037758a9eccf1b2
parent 7d68d625
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -889,11 +889,11 @@ public class TextureView extends View {
     * @hide
     */
    @Override
    protected int calculateFrameRateCategory(float sizePercentage) {
    protected int calculateFrameRateCategory(int width, int height) {
        if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) {
            return FRAME_RATE_CATEGORY_NORMAL;
        }
        return super.calculateFrameRateCategory(sizePercentage);
        return super.calculateFrameRateCategory(width, height);
    }

    @UnsupportedAppUsage
+91 −29
Original line number Diff line number Diff line
@@ -2371,6 +2371,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
    /**
     * This indicates that the frame rate category was chosen because it was a small area update.
     * @hide
     */
    public static final int FRAME_RATE_CATEGORY_REASON_SMALL = 0x0100_0000;
    /**
     * This indicates that the frame rate category was chosen because it was an intermittent update.
     * @hide
     */
    public static final int FRAME_RATE_CATEGORY_REASON_INTERMITTENT = 0x0200_0000;
    /**
     * This indicates that the frame rate category was chosen because it was a large View.
     * @hide
     */
    public static final int FRAME_RATE_CATEGORY_REASON_LARGE = 0x03000000;
    /**
     * This indicates that the frame rate category was chosen because it was requested.
     * @hide
     */
    public static final int FRAME_RATE_CATEGORY_REASON_REQUESTED = 0x0400_0000;
    /**
     * This indicates that the frame rate category was chosen because an invalid frame rate was
     * requested.
     * @hide
     */
    public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
    private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
    private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
    private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
    // Used to set frame rate compatibility.
@@ -5637,10 +5670,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    private ViewTranslationResponse mViewTranslationResponse;
    /**
     * A threshold value to determine the frame rate category of the View based on the size.
     * Threshold size for something to be considered a small area update (in DP).
     * This is the dimension for both width and height.
     */
    private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
    private static final float FRAME_RATE_SMALL_SIZE_THRESHOLD = 40f;
    /**
     * Threshold size for something to be considered a small area update (in DP) if
     * it is narrow. This is for either width OR height. For example, a narrow progress
     * bar could be considered a small area.
     */
    private static final float FRAME_RATE_NARROW_THRESHOLD = 10f;
    private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
    private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -33655,18 +33695,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     *
     * @hide
     */
    protected int calculateFrameRateCategory(float sizePercentage) {
    protected int calculateFrameRateCategory(int width, int height) {
        if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
                < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
            if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
                return FRAME_RATE_CATEGORY_NORMAL;
            DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
            float density = displayMetrics.density;
            if (density == 0f) {
                density = 1f;
            }
            float widthDp = width / density;
            float heightDp = height / density;
            if (widthDp <= FRAME_RATE_NARROW_THRESHOLD
                    || heightDp <= FRAME_RATE_NARROW_THRESHOLD
                    || (widthDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD
                    && heightDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD)) {
                return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_SMALL;
            } else {
                return FRAME_RATE_CATEGORY_HIGH;
                return FRAME_RATE_CATEGORY_HIGH | FRAME_RATE_CATEGORY_REASON_LARGE;
            }
        }
        if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
            return FRAME_RATE_CATEGORY_NORMAL;
            return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
        }
        return mLastFrameRateCategory;
    }
@@ -33674,12 +33724,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    private void votePreferredFrameRate() {
        // use toolkitSetFrameRate flag to gate the change
        ViewRootImpl viewRootImpl = getViewRootImpl();
        float sizePercentage = getSizePercentage();
        int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
        if (viewRootImpl != null && sizePercentage > 0) {
            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
                viewRootImpl.recordViewPercentage(sizePercentage);
            }
        int width = mRight - mLeft;
        int height = mBottom - mTop;
        if (viewRootImpl != null && (width != 0 && height != 0)) {
            if (viewVelocityApi()) {
                float velocity = mFrameContentVelocity;
                if (velocity < 0f) {
@@ -33691,25 +33738,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                    return;
                }
            }
            if (!Float.isNaN(mPreferredFrameRate)) {
                if (mPreferredFrameRate < 0) {
            int frameRateCategory;
            if (Float.isNaN(mPreferredFrameRate)) {
                frameRateCategory = calculateFrameRateCategory(width, height);
            } else if (mPreferredFrameRate < 0) {
                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
                        frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
                    frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
                        frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
                    frameRateCategory = FRAME_RATE_CATEGORY_LOW
                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
                        frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
                    frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
                        frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
                    frameRateCategory = FRAME_RATE_CATEGORY_HIGH
                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
                } else {
                    // invalid frame rate, default to HIGH
                    frameRateCategory = FRAME_RATE_CATEGORY_HIGH
                            | FRAME_RATE_CATEGORY_REASON_INVALID;
                }
            } else {
                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
                        mFrameRateCompatibility);
                return;
            }
            int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
                int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
                viewRootImpl.recordCategory(category, reason, this);
            }
            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
            mLastFrameRateCategory = frameRateCateogry;
            viewRootImpl.votePreferredFrameRateCategory(category);
            mLastFrameRateCategory = frameRateCategory;
        }
    }
+25 −10
Original line number Diff line number Diff line
@@ -32,6 +32,11 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -841,8 +846,9 @@ public final class ViewRootImpl implements ViewParent,
    private boolean mInsetsAnimationRunning;
    private long mPreviousFrameDrawnTime = -1;
    // The largest view size percentage to the display size. Used on trace to collect metric.
    private float mLargestChildPercentage = 0.0f;
    // The reason the category was changed.
    private int mFrameRateCategoryChangeReason = 0;
    private String mFrameRateCategoryView;
    /**
     * The resolved pointer icon type requested by this window.
@@ -4847,10 +4853,6 @@ public final class ViewRootImpl implements ViewParent,
        long fps = NANOS_PER_SEC / timeDiff;
        Trace.setCounter(mFpsTraceName, fps);
        mPreviousFrameDrawnTime = expectedDrawnTime;
        long percentage = (long) (mLargestChildPercentage * 100);
        Trace.setCounter(mLargestViewTraceName, percentage);
        mLargestChildPercentage = 0.0f;
    }
    private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -12367,9 +12369,20 @@ public final class ViewRootImpl implements ViewParent,
        try {
            if (mLastPreferredFrameRateCategory != frameRateCategory) {
                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                    String reason = "none";
                    switch (mFrameRateCategoryChangeReason) {
                        case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
                        case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
                        case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
                        case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
                        case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
                    }
                    String sourceView = mFrameRateCategoryView == null ? "No View Given"
                            : mFrameRateCategoryView;
                    Trace.traceBegin(
                            Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
                                + frameRateCategory);
                                    + frameRateCategory + ", reason " + reason + ", "
                                    + sourceView);
                }
                mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
                        frameRateCategory, false).applyAsyncUnsafe();
@@ -12592,10 +12605,12 @@ public final class ViewRootImpl implements ViewParent,
        mWindowlessBackKeyCallback = callback;
    }
    void recordViewPercentage(float percentage) {
    void recordCategory(int category, int reason, View view) {
        if (!Trace.isEnabled()) return;
        // Record the largest view of percentage to the display size.
        mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
        if (category > mPreferredFrameRateCategory) {
            mFrameRateCategoryChangeReason = reason;
            mFrameRateCategoryView = view.getClass().getSimpleName();
        }
    }
    /**
+240 −0
Original line number Diff line number Diff line
@@ -39,9 +39,12 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class ViewVelocityTest {
public class ViewFrameRateTest {

    @Rule
    public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
@@ -105,4 +108,133 @@ public class ViewVelocityTest {
            assertFalse(mViewRoot.getIsTouchBoosting());
        });
    }

    private void waitForFrameRateCategoryToSettle() throws Throwable {
        for (int i = 0; i < 5; i++) {
            final CountDownLatch drawLatch = new CountDownLatch(1);

            // Now that it is small, any invalidation should have a normal category
            mActivityRule.runOnUiThread(() -> {
                mMovingView.invalidate();
                mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch::countDown);
            });

            assertTrue(drawLatch.await(1, TimeUnit.SECONDS));
        }
    }

    @Test
    public void noVelocityUsesCategorySmall() throws Throwable {
        final CountDownLatch drawLatch1 = new CountDownLatch(1);
        mActivityRule.runOnUiThread(() -> {
            float density = mActivity.getResources().getDisplayMetrics().density;
            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
            layoutParams.height = (int) (40 * density);
            layoutParams.width = (int) (40 * density);
            mMovingView.setLayoutParams(layoutParams);
            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
        });

        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
        waitForFrameRateCategoryToSettle();

        // Now that it is small, any invalidation should have a normal category
        mActivityRule.runOnUiThread(() -> {
            mMovingView.invalidate();
            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
                    mViewRoot.getPreferredFrameRateCategory());
        });
    }

    @Test
    public void noVelocityUsesCategoryNarrowWidth() throws Throwable {
        final CountDownLatch drawLatch1 = new CountDownLatch(1);
        mActivityRule.runOnUiThread(() -> {
            float density = mActivity.getResources().getDisplayMetrics().density;
            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.width = (int) (10 * density);
            mMovingView.setLayoutParams(layoutParams);
            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
        });

        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
        waitForFrameRateCategoryToSettle();

        // Now that it is small, any invalidation should have a normal category
        mActivityRule.runOnUiThread(() -> {
            mMovingView.invalidate();
            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
                    mViewRoot.getPreferredFrameRateCategory());
        });
    }

    @Test
    public void noVelocityUsesCategoryNarrowHeight() throws Throwable {
        final CountDownLatch drawLatch1 = new CountDownLatch(1);
        mActivityRule.runOnUiThread(() -> {
            float density = mActivity.getResources().getDisplayMetrics().density;
            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
            layoutParams.height = (int) (10 * density);
            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
            mMovingView.setLayoutParams(layoutParams);
            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
        });

        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
        waitForFrameRateCategoryToSettle();

        // Now that it is small, any invalidation should have a normal category
        mActivityRule.runOnUiThread(() -> {
            mMovingView.invalidate();
            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
                    mViewRoot.getPreferredFrameRateCategory());
        });
    }

    @Test
    public void noVelocityUsesCategoryLargeWidth() throws Throwable {
        final CountDownLatch drawLatch1 = new CountDownLatch(1);
        mActivityRule.runOnUiThread(() -> {
            float density = mActivity.getResources().getDisplayMetrics().density;
            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
            layoutParams.height = (int) (40 * density);
            layoutParams.width = (int) Math.ceil(41 * density);
            mMovingView.setLayoutParams(layoutParams);
            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
        });

        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
        waitForFrameRateCategoryToSettle();

        // Now that it is small, any invalidation should have a high category
        mActivityRule.runOnUiThread(() -> {
            mMovingView.invalidate();
            assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
                    mViewRoot.getPreferredFrameRateCategory());
        });
    }

    @Test
    public void noVelocityUsesCategoryLargeHeight() throws Throwable {
        final CountDownLatch drawLatch1 = new CountDownLatch(1);
        mActivityRule.runOnUiThread(() -> {
            float density = mActivity.getResources().getDisplayMetrics().density;
            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
            layoutParams.height = (int) Math.ceil(41 * density);
            layoutParams.width = (int) (40 * density);
            mMovingView.setLayoutParams(layoutParams);
            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
        });

        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
        waitForFrameRateCategoryToSettle();

        // Now that it is small, any invalidation should have a high category
        mActivityRule.runOnUiThread(() -> {
            mMovingView.invalidate();
            assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
                    mViewRoot.getPreferredFrameRateCategory());
        });
    }
}