diff options
| -rw-r--r-- | core/api/current.txt | 7 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 79 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 40 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/view/ViewRootImplTest.java | 52 |
4 files changed, 172 insertions, 6 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 7e14ae1612fa..1c9adcb595d1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -51977,6 +51977,7 @@ package android.view { method public android.view.PointerIcon getPointerIcon(); method @NonNull public final java.util.List<android.graphics.Rect> getPreferKeepClearRects(); method @Nullable public String[] getReceiveContentMimeTypes(); + method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public float getRequestedFrameRate(); method public android.content.res.Resources getResources(); method public final boolean getRevealOnFocusHint(); method public final int getRight(); @@ -52353,6 +52354,7 @@ package android.view { method public final void setPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>); method public void setPressed(boolean); method public void setRenderEffect(@Nullable android.graphics.RenderEffect); + method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setRequestedFrameRate(float); method public final void setRevealOnFocusHint(boolean); method public final void setRight(int); method public void setRotation(float); @@ -52535,6 +52537,11 @@ package android.view { field protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET; field protected static final int[] PRESSED_STATE_SET; field protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1.0f; field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION; field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION_X; field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION_Y; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a478b3c40df5..d591f896d99a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -19,8 +19,10 @@ package android.view; import static android.content.res.Resources.ID_NULL; import static android.os.Trace.TRACE_TAG_APP; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; +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.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; @@ -28,6 +30,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE; +import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.flags.Flags.viewVelocityApi; @@ -5521,6 +5524,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; + // The preferred frame rate of the view that is mainly used for + // touch boosting, view velocity handling, and TextureView. + private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT; + + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120; + /** * Simple constructor to use when creating a view from code. * @@ -33015,9 +33033,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return (float) viewSize / screenSize; } - private int calculateFrameRateCategory() { - float sizePercentage = getSizePercentage(); - + private int calculateFrameRateCategory(float sizePercentage) { if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { return FRAME_RATE_CATEGORY_LOW; } else { @@ -33028,9 +33044,24 @@ 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 (sToolkitSetFrameRateReadOnlyFlagValue && viewRootImpl != null - && getSizePercentage() > 0) { - viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory()); + && sizePercentage > 0) { + if (mPreferredFrameRate < 0) { + if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) { + frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE; + } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) { + frameRateCateogry = FRAME_RATE_CATEGORY_LOW; + } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) { + frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL; + } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) { + frameRateCateogry = FRAME_RATE_CATEGORY_HIGH; + } + } else { + viewRootImpl.votePreferredFrameRate(mPreferredFrameRate); + } + viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry); } } @@ -33064,4 +33095,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return 0; } + + /** + * You can set the preferred frame rate for a View using a positive number + * or by specifying the preferred frame rate category using constants, including + * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW, + * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH. + * Keep in mind that the preferred frame rate affects the frame rate for the next frame, + * so use this method carefully. It's important to note that the preference is valid as + * long as the View is invalidated. + * + * @param frameRate the preferred frame rate of the view. + */ + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public void setRequestedFrameRate(float frameRate) { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + mPreferredFrameRate = frameRate; + } + } + + /** + * Get the current preferred frame rate of the View. + * The value could be negative when preferred frame rate category is set + * instead of perferred frame rate. + * The frame rate category includes + * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW, + * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, and REQUESTED_FRAME_RATE_CATEGORY_HIGH. + * Note that the frame rate value is valid as long as the View is invalidated. + * + * @return REQUESTED_FRAME_RATE_CATEGORY_DEFAULT by default, + * or value passed to {@link #setRequestedFrameRate(float)}. + */ + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public float getRequestedFrameRate() { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + return mPreferredFrameRate; + } + return 0; + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d97dfb0f9c94..ac2c5509cac4 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -982,6 +982,9 @@ public final class ViewRootImpl implements ViewParent, // The preferred frame rate of the view that is mainly used for // touch boosting, view velocity handling, and TextureView. private float mPreferredFrameRate = 0; + // The last preferred frame rate of the view that is mainly used to + // track the difference between the current preferred frame rate and the previous value. + private float mLastPreferredFrameRate = 0; // Used to check if there were any view invalidations in // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). private boolean mHasInvalidation = false; @@ -3991,10 +3994,14 @@ public final class ViewRootImpl implements ViewParent, } // For the variable refresh rate project. + // We set the preferred frame rate and frame rate category at the end of performTraversals + // when the values are applicable. setPreferredFrameRate(mPreferredFrameRate); setPreferredFrameRateCategory(mPreferredFrameRateCategory); mLastPreferredFrameRateCategory = mPreferredFrameRateCategory; mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + mLastPreferredFrameRate = mPreferredFrameRate; + mPreferredFrameRate = 0; } private void createSyncIfNeeded() { @@ -7370,7 +7377,9 @@ public final class ViewRootImpl implements ViewParent, */ if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { - sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); + mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT, + FRAME_RATE_TOUCH_BOOST_TIME); } return handled ? FINISH_HANDLED : FORWARD; } @@ -12062,6 +12071,35 @@ public final class ViewRootImpl implements ViewParent, } /** + * Allow Views to vote for the preferred frame rate + * When determining the preferred frame rate value, + * we follow this logic: If no preferred frame rate has been set yet, + * we assign the value of frameRate as the preferred frame rate. + * If either the current or the new preferred frame rate exceeds 60 Hz, + * we select the higher value between them. + * However, if both values are 60 Hz or lower, we set the preferred frame rate + * to 60 Hz to maintain optimal performance. + * + * @param frameRate the preferred frame rate of a View + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public void votePreferredFrameRate(float frameRate) { + if (frameRate <= 0) { + return; + } + + if (mPreferredFrameRate == 0) { + mPreferredFrameRate = frameRate; + } else if (frameRate > 60 || mPreferredFrameRate > 60) { + mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate); + } else if (mPreferredFrameRate != frameRate) { + mPreferredFrameRate = 60; + } + + mHasInvalidation = true; + } + + /** * Get the value of mPreferredFrameRateCategory */ @VisibleForTesting diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index dfe6cf81813d..e0e3a3542cb0 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -577,6 +577,58 @@ public class ViewRootImplTest { }); } + /** + * Test the accurate aggregation of frame rate values as follows: + * 1. When values exceed 60Hz, select the maximum value. + * 2. If frame rates are less than 60Hz and multiple frame rates are voted, + * prioritize 60Hz.. + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public void votePreferredFrameRate_voteFrameRate_aggregate() { + View view = new View(sContext); + attachViewToWindow(view); + sInstrumentation.runOnMainSync(() -> { + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1); + viewRootImpl.votePreferredFrameRate(24); + assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1); + viewRootImpl.votePreferredFrameRate(30); + assertEquals(viewRootImpl.getPreferredFrameRate(), 60, 0.1); + viewRootImpl.votePreferredFrameRate(120); + assertEquals(viewRootImpl.getPreferredFrameRate(), 120, 0.1); + }); + } + + /** + * Override the frame rate category value with setRequestedFrameRate method. + * This function can replace the existing frameRateCategory value and + * submit your preferred choice to the ViewRootImpl. + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public void votePreferredFrameRate_voteFrameRate_category() { + View view = new View(sContext); + attachViewToWindow(view); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_LOW); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NORMAL); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_HIGH); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + }); + } + + @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); |