diff options
| -rw-r--r-- | core/java/android/view/View.java | 48 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 176 | ||||
| -rw-r--r-- | core/tests/coretests/Android.bp | 2 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/view/ViewRootImplTest.java | 136 |
4 files changed, 362 insertions, 0 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 16318e0a3f49..79d68deaf980 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -19,6 +19,8 @@ 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_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; 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; @@ -27,6 +29,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER 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_VIEW_VELOCITY_API; +import static android.view.flags.Flags.toolkitSetFrameRate; import static android.view.flags.Flags.viewVelocityApi; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; @@ -114,6 +117,7 @@ import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.FloatProperty; import android.util.LayoutDirection; import android.util.Log; @@ -5509,6 +5513,11 @@ 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. + */ + private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; + + /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -20182,6 +20191,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } + // For VRR to vote the preferred frame rate + votePreferredFrameRate(); + // Reset content capture caches mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK; mContentCaptureSessionCached = false; @@ -20284,6 +20296,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void damageInParent() { if (mParent != null && mAttachInfo != null) { + // For VRR to vote the preferred frame rate + votePreferredFrameRate(); mParent.onDescendantInvalidated(this, this); } } @@ -32980,6 +32994,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } + private float getSizePercentage() { + if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) { + return 0; + } + + DisplayMetrics displayMetrics = mResources.getDisplayMetrics(); + int screenSize = displayMetrics.widthPixels + * displayMetrics.heightPixels; + int viewSize = getWidth() * getHeight(); + + if (screenSize == 0 || viewSize == 0) { + return 0f; + } + return (float) viewSize / screenSize; + } + + private int calculateFrameRateCategory() { + float sizePercentage = getSizePercentage(); + + if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { + return FRAME_RATE_CATEGORY_LOW; + } else { + return FRAME_RATE_CATEGORY_NORMAL; + } + } + + private void votePreferredFrameRate() { + // use toolkitSetFrameRate flag to gate the change + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) { + viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory()); + } + } + /** * Set the current velocity of the View, we only track positive value. * We will use the velocity information to adjust the frame rate when applicable. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b5648cc90dcd..8e19ac2becbe 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -24,6 +24,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; 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; @@ -74,7 +76,10 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; @@ -87,6 +92,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; +import static android.view.flags.Flags.toolkitSetFrameRate; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -732,6 +738,7 @@ public final class ViewRootImpl implements ViewParent, private SurfaceControl mBoundsLayer; private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final Transaction mTransaction = new Transaction(); + private final Transaction mFrameRateTransaction = new Transaction(); @UnsupportedAppUsage boolean mAdded; @@ -955,6 +962,34 @@ public final class ViewRootImpl implements ViewParent, private AccessibilityWindowAttributes mAccessibilityWindowAttributes; + /* + * for Variable Refresh Rate project + */ + + // The preferred frame rate category of the view that + // could be updated on a frame-by-frame basis. + private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + // The preferred frame rate category of the last frame that + // could be used to lower frame rate after touch boost + private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + // The preferred frame rate of the view that is mainly used for + // touch boosting, view velocity handling, and TextureView. + private float mPreferredFrameRate = 0; + // Used to check if there were any view invalidations in + // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). + private boolean mHasInvalidation = false; + // Used to check if it is in the touch boosting period. + private boolean mIsFrameRateBoosting = false; + // Used to check if there is a message in the message queue + // for idleness handling. + private boolean mHasIdledMessage = false; + // time for touch boost period. + private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500; + // time for checking idle status periodically. + private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500; + // time for revaluating the idle status before lowering the frame rate. + private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500; + /** * A temporary object used so relayoutWindow can return the latest SyncSeqId * system. The SyncSeqId system was designed to work without synchronous relayout @@ -3917,6 +3952,12 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_NONE; } } + + // For the variable refresh rate project. + setPreferredFrameRate(mPreferredFrameRate); + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mLastPreferredFrameRateCategory = mPreferredFrameRateCategory; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; } private void createSyncIfNeeded() { @@ -5969,6 +6010,8 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36; private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37; private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38; + private static final int MSG_TOUCH_BOOST_TIMEOUT = 39; + private static final int MSG_CHECK_INVALIDATION_IDLE = 40; final class ViewRootHandler extends Handler { @Override @@ -6264,6 +6307,32 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; + case MSG_TOUCH_BOOST_TIMEOUT: + /** + * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). + */ + mIsFrameRateBoosting = false; + setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, + mLastPreferredFrameRateCategory)); + break; + case MSG_CHECK_INVALIDATION_IDLE: + if (!mHasInvalidation && !mIsFrameRateBoosting) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mHasIdledMessage = false; + } else { + /** + * If there is no invalidation within a certain period, + * we consider the display is idled. + * We then set the frame rate catetogry to NO_PREFERENCE. + * Note that SurfaceFlinger also has a mechanism to lower the refresh rate + * if there is no updates of the buffer. + */ + mHasInvalidation = false; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + FRAME_RATE_IDLENESS_REEVALUATE_TIME); + } + break; } } } @@ -7206,6 +7275,7 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; + final int action = event.getAction(); boolean handled = mHandwritingInitiator.onTouchEvent(event); if (handled) { // If handwriting is started, toolkit doesn't receive ACTION_UP. @@ -7226,6 +7296,22 @@ public final class ViewRootImpl implements ViewParent, scheduleConsumeBatchedInputImmediately(); } } + + // For the variable refresh rate project + if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { + // set the frame rate to the maximum value. + mIsFrameRateBoosting = true; + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + } + /** + * We want to lower the refresh rate when MotionEvent.ACTION_UP, + * MotionEvent.ACTION_CANCEL is detected. + * Not using ACTION_MOVE to avoid checking and sending messages too frequently. + */ + if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL)) { + sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); + } return handled ? FINISH_HANDLED : FORWARD; } @@ -11809,4 +11895,94 @@ public final class ViewRootImpl implements ViewParent, @NonNull Consumer<Boolean> listener) { t.clearTrustedPresentationCallback(getSurfaceControl()); } + + private void setPreferredFrameRateCategory(int preferredFrameRateCategory) { + if (!shouldSetFrameRateCategory()) { + return; + } + + int frameRateCategory = mIsFrameRateBoosting + ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; + + try { + mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, + frameRateCategory, false).apply(); + } catch (Exception e) { + Log.e(mTag, "Unable to set frame rate category", e); + } + + if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) { + // Check where the display is idled periodically. + // If so, set the frame rate category to NO_PREFERENCE + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS); + mHasIdledMessage = true; + } + } + + private void setPreferredFrameRate(float preferredFrameRate) { + if (!shouldSetFrameRate()) { + return; + } + + try { + mFrameRateTransaction.setFrameRate(mSurfaceControl, + preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply(); + } catch (Exception e) { + Log.e(mTag, "Unable to set frame rate", e); + } + } + + private void sendDelayedEmptyMessage(int message, int delayedTime) { + mHandler.removeMessages(message); + + mHandler.sendEmptyMessageDelayed(message, delayedTime); + } + + private boolean shouldSetFrameRateCategory() { + // use toolkitSetFrameRate flag to gate the change + return mSurface.isValid() && toolkitSetFrameRate(); + } + + private boolean shouldSetFrameRate() { + // use toolkitSetFrameRate flag to gate the change + return mPreferredFrameRate > 0 && toolkitSetFrameRate(); + } + + private boolean shouldTouchBoost(int motionEventAction, int windowType) { + boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN + || motionEventAction == MotionEvent.ACTION_MOVE + || motionEventAction == MotionEvent.ACTION_UP; + boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION + || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION; + // use toolkitSetFrameRate flag to gate the change + return desiredAction && desiredType && toolkitSetFrameRate(); + } + + /** + * Allow Views to vote for the preferred frame rate category + * + * @param frameRateCategory the preferred frame rate category of a View + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public void votePreferredFrameRateCategory(int frameRateCategory) { + mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory); + mHasInvalidation = true; + } + + /** + * Get the value of mPreferredFrameRateCategory + */ + @VisibleForTesting + public int getPreferredFrameRateCategory() { + return mPreferredFrameRateCategory; + } + + /** + * Get the value of mPreferredFrameRate + */ + @VisibleForTesting + public float getPreferredFrameRate() { + return mPreferredFrameRate; + } } diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 2993a0e63228..445ddf52bf1c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -65,6 +65,7 @@ android_test { "device-time-shell-utils", "testables", "com.android.text.flags-aconfig-java", + "flag-junit", ], libs: [ @@ -75,6 +76,7 @@ android_test { "framework", "ext", "framework-res", + "android.view.flags-aconfig-java", ], jni_libs: [ "libpowermanagertest_jni", diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 6a9fc04230f8..1a38decae604 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -17,6 +17,11 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; +import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE; +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.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; @@ -48,8 +53,12 @@ import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; @@ -97,6 +106,10 @@ public class ViewRootImplTest { // state after the test completes. private static boolean sOriginalTouchMode; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @BeforeClass public static void setUpClass() { sContext = sInstrumentation.getTargetContext(); @@ -427,6 +440,129 @@ public class ViewRootImplTest { assertThat(result).isFalse(); } + /** + * Test the default values are properly set + */ + @UiThreadTest + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_getDefaultValues() { + ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, + sContext.getDisplayNoVerify()); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1); + } + + /** + * Test the value of the frame rate cateogry based on the visibility of a view + * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE + * Visible: FRAME_RATE_CATEGORY_NORMAL + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_visibility() { + View view = new View(sContext); + attachViewToWindow(view); + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.setVisibility(View.INVISIBLE); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + }); + + sInstrumentation.runOnMainSync(() -> { + view.setVisibility(View.VISIBLE); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NORMAL); + }); + } + + /** + * Test the value of the frame rate cateogry based on the size of a view. + * The current threshold value is 7% of the screen size + * <7%: FRAME_RATE_CATEGORY_LOW + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_smallSize() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + wmlp.width = 1; + wmlp.height = 1; + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); + }); + } + + /** + * Test the value of the frame rate cateogry based on the size of a view. + * The current threshold value is 7% of the screen size + * >=7% : FRAME_RATE_CATEGORY_NORMAL + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_normalSize() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + Display display = wm.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + wmlp.width = (int) (metrics.widthPixels * 0.9); + wmlp.height = (int) (metrics.heightPixels * 0.9); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + }); + } + + /** + * Test how values of the frame rate cateogry are aggregated. + * It should take the max value among all of the voted categories per frame. + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_aggregate() { + View view = new View(sContext); + attachViewToWindow(view); + sInstrumentation.runOnMainSync(() -> { + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + }); + } + @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); |