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

Commit 9be4653c authored by Jian-Syuan (Shane) Wong's avatar Jian-Syuan (Shane) Wong Committed by Android (Google) Code Review
Browse files

Merge "Basic framework of the UI toolkit variable refresh rate project" into main

parents ad48a441 22dc3e0a
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -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;
@@ -5508,6 +5512,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.
     *
@@ -20183,6 +20192,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;
@@ -20285,6 +20297,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);
        }
    }
@@ -32981,6 +32995,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.
+176 −0
Original line number Diff line number Diff line
@@ -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
@@ -3918,6 +3953,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() {
@@ -5970,6 +6011,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
@@ -6265,6 +6308,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;
            }
        }
    }
@@ -7207,6 +7276,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.
@@ -7227,6 +7297,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;
        }

@@ -11810,4 +11896,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;
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -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",
+136 −0
Original line number Diff line number Diff line
@@ -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);