Loading core/java/android/view/ViewRootImpl.java +34 −3 Original line number Diff line number Diff line Loading @@ -123,6 +123,7 @@ import static android.view.flags.Flags.toolkitFrameRateTouchBoost25q1; import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly; import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly; import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly; import static android.view.flags.Flags.toolkitInitialTouchBoost; import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; Loading Loading @@ -1113,9 +1114,13 @@ public final class ViewRootImpl implements ViewParent, private boolean mIsFrameRateConflicted = false; // Used to check whether SurfaceControl has been replaced. private boolean mSurfaceReplaced = false; // Indicates whether a draw operation occurred during this frame while a touch event was active. private boolean mTouchAndDrawn = false; // Used to set frame rate compatibility. @Surface.FrameRateCompatibility int mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; // time for initial touch boost period. private static final int FRAME_RATE_INITIAL_TOUCH_BOOST_TIME = 30; // time for touch boost period. private static final int FRAME_RATE_TOUCH_BOOST_TIME = 3000; // Timeout for the other frame rate boosts other than touch boost. Loading Loading @@ -1213,6 +1218,7 @@ public final class ViewRootImpl implements ViewParent, private static boolean sSurfaceFlingerBugfixFlagValue = com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4(); private static final boolean sEnableVrr = ViewProperties.vrr_enabled().orElse(true); private static final boolean sToolkitInitialTouchBoostFlagValue = toolkitInitialTouchBoost(); static { sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); Loading Loading @@ -4435,6 +4441,10 @@ public final class ViewRootImpl implements ViewParent, // We set the preferred frame rate and frame rate category at the end of performTraversals // when the values are applicable. if (mDrawnThisFrame) { if (sToolkitInitialTouchBoostFlagValue && mIsTouchBoosting) { mTouchAndDrawn = true; } mDrawnThisFrame = false; if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { mInvalidationIdleMessagePosted = true; Loading Loading @@ -6718,6 +6728,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_REFRESH_POINTER_ICON = 41; private static final int MSG_FRAME_RATE_SETTING = 42; private static final int MSG_SURFACE_REPLACED_TIMEOUT = 43; private static final int MSG_INITIAL_TOUCH_BOOST_TIMEOUT = 44; final class ViewRootHandler extends Handler { @Override Loading Loading @@ -6791,6 +6802,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_FRAME_RATE_SETTING"; case MSG_SURFACE_REPLACED_TIMEOUT: return "MSG_SURFACE_REPLACED_TIMEOUT"; case MSG_INITIAL_TOUCH_BOOST_TIMEOUT: return "MSG_INITIAL_TOUCH_BOOST_TIMEOUT"; } return super.getMessageName(message); } Loading Loading @@ -7066,6 +7079,17 @@ public final class ViewRootImpl implements ViewParent, setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE); } break; case MSG_INITIAL_TOUCH_BOOST_TIMEOUT: if (mTouchAndDrawn) { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); } else { mIsTouchBoosting = false; setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE); } mTouchAndDrawn = false; break; case MSG_REFRESH_POINTER_ICON: if (mPointerIconEvent == null) { break; Loading Loading @@ -8130,10 +8154,17 @@ public final class ViewRootImpl implements ViewParent, */ if (mIsTouchBoosting && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { if (sToolkitInitialTouchBoostFlagValue) { mHandler.removeMessages(MSG_INITIAL_TOUCH_BOOST_TIMEOUT); mHandler.sendEmptyMessageDelayed(MSG_INITIAL_TOUCH_BOOST_TIMEOUT, FRAME_RATE_INITIAL_TOUCH_BOOST_TIME); } else { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); } } return handled ? FINISH_HANDLED : FORWARD; } Loading core/java/android/view/flags/refresh_rate_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -137,3 +137,10 @@ flag { bug: "335874198" is_exported: true } flag { name: "toolkit_initial_touch_boost" namespace: "toolkit" description: "Feature flag to update initial touch boost logic" bug: "393004744" } No newline at end of file core/tests/coretests/src/android/view/ViewFrameRateTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_ANIMATION_BUGFIX_ import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_TOUCH_BOOST_25Q1; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY; import static android.view.flags.Flags.FLAG_TOOLKIT_INITIAL_TOUCH_BOOST; 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.toolkitFrameRateBySizeReadOnly; Loading @@ -34,6 +35,7 @@ import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.annotation.NonNull; Loading Loading @@ -179,6 +181,75 @@ public class ViewFrameRateTest { assertTrue(mViewRoot.mWindowAttributes.type == windowType); } @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_INITIAL_TOUCH_BOOST) public void initialTouchBoost() throws Throwable { if (!ViewProperties.vrr_enabled().orElse(true)) { return; } mActivityRule.runOnUiThread(() -> { ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams(); layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; mMovingView.setLayoutParams(layoutParams); mMovingView.setOnClickListener((v) -> {}); }); waitForFrameRateCategoryToSettle(); int[] position = new int[2]; mActivityRule.runOnUiThread(() -> { mMovingView.getLocationOnScreen(position); position[0] += mMovingView.getWidth() / 2; position[1] += mMovingView.getHeight() / 2; }); final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); assertFalse(mViewRoot.getIsTouchBoosting()); long now = SystemClock.uptimeMillis(); MotionEvent down = MotionEvent.obtain( now, // downTime now, // eventTime MotionEvent.ACTION_DOWN, // action position[0], // x position[1], // y 0 // metaState ); mActivityRule.runOnUiThread(() -> { mMovingView.getViewRootImpl().dispatchAppVisibility(false); }); down.setSource(InputDevice.SOURCE_TOUCHSCREEN); instrumentation.sendPointerSync(down); down.recycle(); Thread.sleep(100); // should have touch boost assertTrue(mViewRoot.getIsTouchBoosting()); MotionEvent up = MotionEvent.obtain( now, // downTime now, // eventTime MotionEvent.ACTION_UP, // action position[0], // x position[1], // y 0 // metaState ); up.setSource(InputDevice.SOURCE_TOUCHSCREEN); instrumentation.sendPointerSync(up); up.recycle(); // Should not be boosted if nothing is drawn Thread.sleep(30); assertFalse(mViewRoot.getIsTouchBoosting()); mActivityRule.runOnUiThread(() -> { mMovingView.getViewRootImpl().dispatchAppVisibility(true); }); } @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY) public void inputMethodWithContentMoves() throws Throwable { Loading Loading
core/java/android/view/ViewRootImpl.java +34 −3 Original line number Diff line number Diff line Loading @@ -123,6 +123,7 @@ import static android.view.flags.Flags.toolkitFrameRateTouchBoost25q1; import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly; import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly; import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly; import static android.view.flags.Flags.toolkitInitialTouchBoost; import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; Loading Loading @@ -1113,9 +1114,13 @@ public final class ViewRootImpl implements ViewParent, private boolean mIsFrameRateConflicted = false; // Used to check whether SurfaceControl has been replaced. private boolean mSurfaceReplaced = false; // Indicates whether a draw operation occurred during this frame while a touch event was active. private boolean mTouchAndDrawn = false; // Used to set frame rate compatibility. @Surface.FrameRateCompatibility int mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; // time for initial touch boost period. private static final int FRAME_RATE_INITIAL_TOUCH_BOOST_TIME = 30; // time for touch boost period. private static final int FRAME_RATE_TOUCH_BOOST_TIME = 3000; // Timeout for the other frame rate boosts other than touch boost. Loading Loading @@ -1213,6 +1218,7 @@ public final class ViewRootImpl implements ViewParent, private static boolean sSurfaceFlingerBugfixFlagValue = com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4(); private static final boolean sEnableVrr = ViewProperties.vrr_enabled().orElse(true); private static final boolean sToolkitInitialTouchBoostFlagValue = toolkitInitialTouchBoost(); static { sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); Loading Loading @@ -4435,6 +4441,10 @@ public final class ViewRootImpl implements ViewParent, // We set the preferred frame rate and frame rate category at the end of performTraversals // when the values are applicable. if (mDrawnThisFrame) { if (sToolkitInitialTouchBoostFlagValue && mIsTouchBoosting) { mTouchAndDrawn = true; } mDrawnThisFrame = false; if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { mInvalidationIdleMessagePosted = true; Loading Loading @@ -6718,6 +6728,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_REFRESH_POINTER_ICON = 41; private static final int MSG_FRAME_RATE_SETTING = 42; private static final int MSG_SURFACE_REPLACED_TIMEOUT = 43; private static final int MSG_INITIAL_TOUCH_BOOST_TIMEOUT = 44; final class ViewRootHandler extends Handler { @Override Loading Loading @@ -6791,6 +6802,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_FRAME_RATE_SETTING"; case MSG_SURFACE_REPLACED_TIMEOUT: return "MSG_SURFACE_REPLACED_TIMEOUT"; case MSG_INITIAL_TOUCH_BOOST_TIMEOUT: return "MSG_INITIAL_TOUCH_BOOST_TIMEOUT"; } return super.getMessageName(message); } Loading Loading @@ -7066,6 +7079,17 @@ public final class ViewRootImpl implements ViewParent, setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE); } break; case MSG_INITIAL_TOUCH_BOOST_TIMEOUT: if (mTouchAndDrawn) { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); } else { mIsTouchBoosting = false; setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE); } mTouchAndDrawn = false; break; case MSG_REFRESH_POINTER_ICON: if (mPointerIconEvent == null) { break; Loading Loading @@ -8130,10 +8154,17 @@ public final class ViewRootImpl implements ViewParent, */ if (mIsTouchBoosting && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { if (sToolkitInitialTouchBoostFlagValue) { mHandler.removeMessages(MSG_INITIAL_TOUCH_BOOST_TIMEOUT); mHandler.sendEmptyMessageDelayed(MSG_INITIAL_TOUCH_BOOST_TIMEOUT, FRAME_RATE_INITIAL_TOUCH_BOOST_TIME); } else { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); } } return handled ? FINISH_HANDLED : FORWARD; } Loading
core/java/android/view/flags/refresh_rate_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -137,3 +137,10 @@ flag { bug: "335874198" is_exported: true } flag { name: "toolkit_initial_touch_boost" namespace: "toolkit" description: "Feature flag to update initial touch boost logic" bug: "393004744" } No newline at end of file
core/tests/coretests/src/android/view/ViewFrameRateTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_ANIMATION_BUGFIX_ import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_TOUCH_BOOST_25Q1; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY; import static android.view.flags.Flags.FLAG_TOOLKIT_INITIAL_TOUCH_BOOST; 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.toolkitFrameRateBySizeReadOnly; Loading @@ -34,6 +35,7 @@ import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.annotation.NonNull; Loading Loading @@ -179,6 +181,75 @@ public class ViewFrameRateTest { assertTrue(mViewRoot.mWindowAttributes.type == windowType); } @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_INITIAL_TOUCH_BOOST) public void initialTouchBoost() throws Throwable { if (!ViewProperties.vrr_enabled().orElse(true)) { return; } mActivityRule.runOnUiThread(() -> { ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams(); layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; mMovingView.setLayoutParams(layoutParams); mMovingView.setOnClickListener((v) -> {}); }); waitForFrameRateCategoryToSettle(); int[] position = new int[2]; mActivityRule.runOnUiThread(() -> { mMovingView.getLocationOnScreen(position); position[0] += mMovingView.getWidth() / 2; position[1] += mMovingView.getHeight() / 2; }); final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); assertFalse(mViewRoot.getIsTouchBoosting()); long now = SystemClock.uptimeMillis(); MotionEvent down = MotionEvent.obtain( now, // downTime now, // eventTime MotionEvent.ACTION_DOWN, // action position[0], // x position[1], // y 0 // metaState ); mActivityRule.runOnUiThread(() -> { mMovingView.getViewRootImpl().dispatchAppVisibility(false); }); down.setSource(InputDevice.SOURCE_TOUCHSCREEN); instrumentation.sendPointerSync(down); down.recycle(); Thread.sleep(100); // should have touch boost assertTrue(mViewRoot.getIsTouchBoosting()); MotionEvent up = MotionEvent.obtain( now, // downTime now, // eventTime MotionEvent.ACTION_UP, // action position[0], // x position[1], // y 0 // metaState ); up.setSource(InputDevice.SOURCE_TOUCHSCREEN); instrumentation.sendPointerSync(up); up.recycle(); // Should not be boosted if nothing is drawn Thread.sleep(30); assertFalse(mViewRoot.getIsTouchBoosting()); mActivityRule.runOnUiThread(() -> { mMovingView.getViewRootImpl().dispatchAppVisibility(true); }); } @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY) public void inputMethodWithContentMoves() throws Throwable { Loading