Loading core/java/android/view/TextureView.java +13 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.view; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; Loading Loading @@ -883,6 +885,17 @@ public class TextureView extends View { mListener = listener; } /** * @hide */ @Override protected int calculateFrameRateCategory(float sizePercentage) { if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) { return FRAME_RATE_CATEGORY_NORMAL; } return super.calculateFrameRateCategory(sizePercentage); } @UnsupportedAppUsage private final SurfaceTexture.OnFrameAvailableListener mUpdateListener = surfaceTexture -> { Loading core/java/android/view/View.java +19 −5 Original line number Diff line number Diff line Loading @@ -5649,9 +5649,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mInfrequentUpdateCount = 0; private long mLastUpdateTimeMillis = 0; private long mMinusOneFrameIntervalMillis = 0; private long mMinusTwoFrameIntervalMillis = 0; private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; /** * @hide */ protected long mMinusOneFrameIntervalMillis = 0; /** * @hide */ protected long mMinusTwoFrameIntervalMillis = 0; private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN; Loading Loading @@ -33612,7 +33618,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return (float) viewSize / screenSize; } private int calculateFrameRateCategory(float sizePercentage) { /** * Used to calculate the frame rate category of a View. * * @hide */ protected int calculateFrameRateCategory(float sizePercentage) { if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis < INFREQUENT_UPDATE_INTERVAL_MILLIS) { if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { Loading Loading @@ -33746,7 +33757,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis; mMinusOneFrameIntervalMillis = timeIntervalMillis; mLastUpdateTimeMillis = currentTimeMillis; if (mMinusOneFrameIntervalMillis - mMinusTwoFrameIntervalMillis >= 30 && timeIntervalMillis < 2) { return; } if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1; core/java/android/view/ViewRootImpl.java +17 −11 Original line number Diff line number Diff line Loading @@ -1043,7 +1043,7 @@ public final class ViewRootImpl implements ViewParent, // 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; private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 1000; // time for evaluating the interval between current time and // the time when frame rate was set previously. private static final int FRAME_RATE_SETTING_REEVALUATE_TIME = 100; Loading Loading @@ -6540,6 +6540,7 @@ public final class ViewRootImpl implements ViewParent, mHasInvalidation = false; mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, FRAME_RATE_IDLENESS_REEVALUATE_TIME); mHasIdledMessage = true; } break; case MSG_REFRESH_POINTER_ICON: Loading Loading @@ -12368,14 +12369,6 @@ public final class ViewRootImpl implements ViewParent, } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } 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) { Loading @@ -12389,7 +12382,8 @@ public final class ViewRootImpl implements ViewParent, if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin( Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate " + preferredFrameRate); + preferredFrameRate + " compatibility " + mFrameRateCompatibility); } mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate, mFrameRateCompatibility).applyAsyncUnsafe(); Loading @@ -12415,7 +12409,7 @@ public final class ViewRootImpl implements ViewParent, private boolean shouldSetFrameRate() { // use toolkitSetFrameRate flag to gate the change return mSurface.isValid() && mPreferredFrameRate > 0 return mSurface.isValid() && mPreferredFrameRate >= 0 && shouldEnableDvrr() && !mIsFrameRateConflicted; } Loading Loading @@ -12456,6 +12450,7 @@ public final class ViewRootImpl implements ViewParent, mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW; } mHasInvalidation = true; checkIdleness(); } /** Loading Loading @@ -12498,6 +12493,7 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING, FRAME_RATE_SETTING_REEVALUATE_TIME); } checkIdleness(); } /** Loading Loading @@ -12603,4 +12599,14 @@ public final class ViewRootImpl implements ViewParent, private boolean shouldEnableDvrr() { return sToolkitSetFrameRateReadOnlyFlagValue && mIsFrameRatePowerSavingsBalanced; } private void checkIdleness() { if (!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; } } } core/tests/coretests/src/android/view/ViewRootImplTest.java +54 −0 Original line number Diff line number Diff line Loading @@ -1017,6 +1017,60 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), true); } /** * Test the TextureView heuristic: * 1. Store the last 3 invalidates time - FT1, FT2, FT3. * 2. If FT2-FT1 > 15ms && FT3-FT2 > 15ms -> vote for NORMAL category */ @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public void votePreferredFrameRate_applyTextureViewHeuristic() throws InterruptedException { final long delay = 30L; TextureView view = new TextureView(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(() -> { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NO_PREFERENCE); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); }); // reset the frame rate category counts for (int i = 0; i < 5; i++) { Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); view.invalidate(); }); sInstrumentation.waitForIdleSync(); } Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); }); } @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); Loading Loading
core/java/android/view/TextureView.java +13 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.view; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; Loading Loading @@ -883,6 +885,17 @@ public class TextureView extends View { mListener = listener; } /** * @hide */ @Override protected int calculateFrameRateCategory(float sizePercentage) { if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) { return FRAME_RATE_CATEGORY_NORMAL; } return super.calculateFrameRateCategory(sizePercentage); } @UnsupportedAppUsage private final SurfaceTexture.OnFrameAvailableListener mUpdateListener = surfaceTexture -> { Loading
core/java/android/view/View.java +19 −5 Original line number Diff line number Diff line Loading @@ -5649,9 +5649,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mInfrequentUpdateCount = 0; private long mLastUpdateTimeMillis = 0; private long mMinusOneFrameIntervalMillis = 0; private long mMinusTwoFrameIntervalMillis = 0; private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; /** * @hide */ protected long mMinusOneFrameIntervalMillis = 0; /** * @hide */ protected long mMinusTwoFrameIntervalMillis = 0; private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN; Loading Loading @@ -33612,7 +33618,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return (float) viewSize / screenSize; } private int calculateFrameRateCategory(float sizePercentage) { /** * Used to calculate the frame rate category of a View. * * @hide */ protected int calculateFrameRateCategory(float sizePercentage) { if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis < INFREQUENT_UPDATE_INTERVAL_MILLIS) { if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { Loading Loading @@ -33746,7 +33757,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis; mMinusOneFrameIntervalMillis = timeIntervalMillis; mLastUpdateTimeMillis = currentTimeMillis; if (mMinusOneFrameIntervalMillis - mMinusTwoFrameIntervalMillis >= 30 && timeIntervalMillis < 2) { return; } if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1;
core/java/android/view/ViewRootImpl.java +17 −11 Original line number Diff line number Diff line Loading @@ -1043,7 +1043,7 @@ public final class ViewRootImpl implements ViewParent, // 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; private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 1000; // time for evaluating the interval between current time and // the time when frame rate was set previously. private static final int FRAME_RATE_SETTING_REEVALUATE_TIME = 100; Loading Loading @@ -6540,6 +6540,7 @@ public final class ViewRootImpl implements ViewParent, mHasInvalidation = false; mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, FRAME_RATE_IDLENESS_REEVALUATE_TIME); mHasIdledMessage = true; } break; case MSG_REFRESH_POINTER_ICON: Loading Loading @@ -12368,14 +12369,6 @@ public final class ViewRootImpl implements ViewParent, } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } 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) { Loading @@ -12389,7 +12382,8 @@ public final class ViewRootImpl implements ViewParent, if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin( Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate " + preferredFrameRate); + preferredFrameRate + " compatibility " + mFrameRateCompatibility); } mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate, mFrameRateCompatibility).applyAsyncUnsafe(); Loading @@ -12415,7 +12409,7 @@ public final class ViewRootImpl implements ViewParent, private boolean shouldSetFrameRate() { // use toolkitSetFrameRate flag to gate the change return mSurface.isValid() && mPreferredFrameRate > 0 return mSurface.isValid() && mPreferredFrameRate >= 0 && shouldEnableDvrr() && !mIsFrameRateConflicted; } Loading Loading @@ -12456,6 +12450,7 @@ public final class ViewRootImpl implements ViewParent, mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW; } mHasInvalidation = true; checkIdleness(); } /** Loading Loading @@ -12498,6 +12493,7 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING, FRAME_RATE_SETTING_REEVALUATE_TIME); } checkIdleness(); } /** Loading Loading @@ -12603,4 +12599,14 @@ public final class ViewRootImpl implements ViewParent, private boolean shouldEnableDvrr() { return sToolkitSetFrameRateReadOnlyFlagValue && mIsFrameRatePowerSavingsBalanced; } private void checkIdleness() { if (!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; } } }
core/tests/coretests/src/android/view/ViewRootImplTest.java +54 −0 Original line number Diff line number Diff line Loading @@ -1017,6 +1017,60 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), true); } /** * Test the TextureView heuristic: * 1. Store the last 3 invalidates time - FT1, FT2, FT3. * 2. If FT2-FT1 > 15ms && FT3-FT2 > 15ms -> vote for NORMAL category */ @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public void votePreferredFrameRate_applyTextureViewHeuristic() throws InterruptedException { final long delay = 30L; TextureView view = new TextureView(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(() -> { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NO_PREFERENCE); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); }); // reset the frame rate category counts for (int i = 0; i < 5; i++) { Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); view.invalidate(); }); sInstrumentation.waitForIdleSync(); } Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); }); } @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); Loading