Loading core/java/android/view/View.java +48 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -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. core/java/android/view/ViewRootImpl.java +176 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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 Loading Loading @@ -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; } } } Loading Loading @@ -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. Loading @@ -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; } Loading Loading @@ -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; } } core/tests/coretests/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ android_test { "device-time-shell-utils", "testables", "com.android.text.flags-aconfig-java", "flag-junit", ], libs: [ Loading @@ -75,6 +76,7 @@ android_test { "framework", "ext", "framework-res", "android.view.flags-aconfig-java", ], jni_libs: [ "libpowermanagertest_jni", Loading core/tests/coretests/src/android/view/ViewRootImplTest.java +136 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); Loading Loading
core/java/android/view/View.java +48 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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; Loading Loading @@ -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); } } Loading Loading @@ -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.
core/java/android/view/ViewRootImpl.java +176 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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 Loading Loading @@ -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; } } } Loading Loading @@ -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. Loading @@ -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; } Loading Loading @@ -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; } }
core/tests/coretests/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ android_test { "device-time-shell-utils", "testables", "com.android.text.flags-aconfig-java", "flag-junit", ], libs: [ Loading @@ -75,6 +76,7 @@ android_test { "framework", "ext", "framework-res", "android.view.flags-aconfig-java", ], jni_libs: [ "libpowermanagertest_jni", Loading
core/tests/coretests/src/android/view/ViewRootImplTest.java +136 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); Loading