Loading core/java/android/view/TextureView.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading core/java/android/view/View.java +91 −29 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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) { Loading @@ -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; } } core/java/android/view/ViewRootImpl.java +25 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -12369,9 +12371,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(); Loading Loading @@ -12603,10 +12616,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(); } } /** core/tests/coretests/src/android/view/ViewVelocityTest.java→core/tests/coretests/src/android/view/ViewFrameRateTest.java +240 −0 Original line number Diff line number Diff line Loading @@ -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<>( Loading Loading @@ -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()); }); } } Loading
core/java/android/view/TextureView.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
core/java/android/view/View.java +91 −29 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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) { Loading @@ -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; } }
core/java/android/view/ViewRootImpl.java +25 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -12369,9 +12371,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(); Loading Loading @@ -12603,10 +12616,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(); } } /**
core/tests/coretests/src/android/view/ViewVelocityTest.java→core/tests/coretests/src/android/view/ViewFrameRateTest.java +240 −0 Original line number Diff line number Diff line Loading @@ -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<>( Loading Loading @@ -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()); }); } }