Merge "Improve performance of VRR" into main
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index c66abe8..5466bf5 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -889,11 +889,11 @@
      * @hide
      */
     @Override
-    protected int calculateFrameRateCategory(int width, int height) {
+    protected int calculateFrameRateCategory() {
         if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) {
             return FRAME_RATE_CATEGORY_NORMAL;
         }
-        return super.calculateFrameRateCategory(width, height);
+        return super.calculateFrameRateCategory();
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 736e815..b6df1bb 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5695,17 +5695,10 @@
     private ViewTranslationResponse mViewTranslationResponse;
 
     /**
-     * Threshold size for something to be considered a small area update (in DP).
-     * This is the dimension for both width and height.
+     * The multiplier for mAttachInfo.mSmallSizePixels to consider a View to be small
+     * if both dimensions are smaller than this.
      */
-    private static final float FRAME_RATE_SMALL_SIZE_THRESHOLD = 40f;
-
-    /**
-     * Threshold size for something to be considered a small area update (in DP) if
-     * it is narrow. This is for either width OR height. For example, a narrow progress
-     * bar could be considered a small area.
-     */
-    private static final float FRAME_RATE_NARROW_THRESHOLD = 10f;
+    private static final int FRAME_RATE_SQUARE_SMALL_SIZE_MULTIPLIER = 4;
 
     private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
     private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -5740,6 +5733,8 @@
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4;
 
+    private int mSizeBasedFrameRateCategoryAndReason;
+
     /**
      * Simple constructor to use when creating a view from code.
      *
@@ -23561,6 +23556,9 @@
             return renderNode;
         }
 
+        mLastFrameX = mLeft + mRenderNode.getTranslationX();
+        mLastFrameY = mTop + mRenderNode.getTranslationY();
+
         if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                 || !renderNode.hasDisplayList()
                 || (mRecreateDisplayList)) {
@@ -24724,8 +24722,6 @@
         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 
         mFrameContentVelocity = -1;
-        mLastFrameX = mLeft + mRenderNode.getTranslationX();
-        mLastFrameY = mTop + mRenderNode.getTranslationY();
 
         /*
          * Draw traversal performs several drawing steps which must be executed
@@ -25474,6 +25470,21 @@
     }
 
     private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+        if (mAttachInfo != null) {
+            int narrowSize = mAttachInfo.mSmallSizePixels;
+            int smallSize = narrowSize * FRAME_RATE_SQUARE_SMALL_SIZE_MULTIPLIER;
+            if (newWidth <= narrowSize || newHeight <= narrowSize
+                    || (newWidth <= smallSize && newHeight <= smallSize)) {
+                int category = toolkitFrameRateBySizeReadOnly()
+                        ? FRAME_RATE_CATEGORY_LOW : FRAME_RATE_CATEGORY_NORMAL;
+                mSizeBasedFrameRateCategoryAndReason = category | FRAME_RATE_CATEGORY_REASON_SMALL;
+            } else {
+                int category = toolkitFrameRateDefaultNormalReadOnly()
+                        ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+                mSizeBasedFrameRateCategoryAndReason = category | FRAME_RATE_CATEGORY_REASON_LARGE;
+            }
+        }
+
         onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
         if (mOverlay != null) {
             mOverlay.getOverlayView().setRight(newWidth);
@@ -32040,6 +32051,13 @@
         int mSensitiveViewsCount;
 
         /**
+         * The size used for a View to be considered small for the purposes of using
+         * low refresh rate by default. This is the size in one direction, so a long, thin
+         * item like a progress bar can be compared to this.
+         */
+        final int mSmallSizePixels;
+
+        /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
          *
@@ -32056,6 +32074,7 @@
             mHandler = handler;
             mRootCallbacks = effectPlayer;
             mTreeObserver = new ViewTreeObserver(context);
+            mSmallSizePixels = (int) (context.getResources().getDisplayMetrics().density * 10);
         }
 
         void increaseSensitiveViewsCount() {
@@ -33784,28 +33803,10 @@
      *
      * @hide
      */
-    protected int calculateFrameRateCategory(int width, int height) {
+    protected int calculateFrameRateCategory() {
         if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
                 < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
-            DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
-            float density = displayMetrics.density;
-            if (density == 0f) {
-                density = 1f;
-            }
-            float widthDp = width / density;
-            float heightDp = height / density;
-            if (widthDp <= FRAME_RATE_NARROW_THRESHOLD
-                    || heightDp <= FRAME_RATE_NARROW_THRESHOLD
-                    || (widthDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD
-                    && heightDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD)) {
-                int category = toolkitFrameRateBySizeReadOnly()
-                        ? FRAME_RATE_CATEGORY_LOW : FRAME_RATE_CATEGORY_NORMAL;
-                return category | FRAME_RATE_CATEGORY_REASON_SMALL;
-            } else {
-                int category = toolkitFrameRateDefaultNormalReadOnly()
-                        ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
-                return category | FRAME_RATE_CATEGORY_REASON_LARGE;
-            }
+            return mSizeBasedFrameRateCategoryAndReason;
         }
 
         if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
@@ -33823,7 +33824,14 @@
             if (viewVelocityApi()) {
                 float velocity = mFrameContentVelocity;
                 if (velocity < 0f) {
-                    velocity = calculateVelocity();
+                    // This current calculation is very simple. If something on the screen moved,
+                    // then it votes for the highest velocity. If it doesn't move, then return 0.
+                    RenderNode renderNode = mRenderNode;
+                    float x = mLeft + renderNode.getTranslationX();
+                    float y = mTop + renderNode.getTranslationY();
+
+                    velocity = (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
+                            ? 100_000f : 0f;
                 }
                 if (velocity > 0f) {
                     float frameRate = convertVelocityToFrameRate(velocity);
@@ -33831,43 +33839,55 @@
                     return;
                 }
             }
-            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
-                float sizePercentage = getSizePercentage();
-                viewRootImpl.recordViewPercentage(sizePercentage);
-            }
-            int frameRateCategory;
-            if (Float.isNaN(mPreferredFrameRate)) {
-                frameRateCategory = calculateFrameRateCategory(width, height);
-            } else if (mPreferredFrameRate < 0) {
-                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-                    frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
-                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
-                    frameRateCategory = FRAME_RATE_CATEGORY_LOW
-                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
-                    frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
-                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
-                    frameRateCategory = FRAME_RATE_CATEGORY_HIGH
-                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
-                } else {
-                    // invalid frame rate, use default
-                    int category = toolkitFrameRateDefaultNormalReadOnly()
-                            ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
-                    frameRateCategory = category
-                            | FRAME_RATE_CATEGORY_REASON_INVALID;
+            if (!willNotDraw()) {
+                if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                    float sizePercentage = getSizePercentage();
+                    viewRootImpl.recordViewPercentage(sizePercentage);
                 }
-            } else {
-                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
-                        mFrameRateCompatibility);
-                return;
-            }
 
-            int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
-            int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
-            viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
-            mLastFrameRateCategory = frameRateCategory;
+                int frameRateCategory;
+                if (Float.isNaN(mPreferredFrameRate)) {
+                    if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+                            < INFREQUENT_UPDATE_INTERVAL_MILLIS && mAttachInfo != null) {
+                        frameRateCategory = mSizeBasedFrameRateCategoryAndReason;
+                    } else if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
+                        frameRateCategory =
+                                FRAME_RATE_CATEGORY_NORMAL
+                                        | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+                    } else {
+                        frameRateCategory = mLastFrameRateCategory;
+                    }
+                } else if (mPreferredFrameRate < 0) {
+                    if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+                        frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+                                | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+                        frameRateCategory = FRAME_RATE_CATEGORY_LOW
+                                | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+                        frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
+                                | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+                        frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+                                | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                    } else {
+                        // invalid frame rate, use default
+                        int category = toolkitFrameRateDefaultNormalReadOnly()
+                                ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+                        frameRateCategory = category
+                                | FRAME_RATE_CATEGORY_REASON_INVALID;
+                    }
+                } else {
+                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+                            mFrameRateCompatibility);
+                    return;
+                }
+
+                int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
+                int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+                viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
+                mLastFrameRateCategory = frameRateCategory;
+            }
         }
     }
 
@@ -33878,16 +33898,6 @@
         return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
     }
 
-    private float calculateVelocity() {
-        // This current calculation is very simple. If something on the screen moved, then
-        // it votes for the highest velocity. If it doesn't move, then return 0.
-        float x = mLeft + mRenderNode.getTranslationX();
-        float y = mTop + mRenderNode.getTranslationY();
-
-        return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
-                ? 100_000f : 0f;
-    }
-
     /**
      * Set the current velocity of the View, we only track positive value.
      * We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eb9be18..dd548c6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -12626,12 +12626,11 @@
         }
         mHasInvalidation = true;
         checkIdleness();
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)
-                && mPreferredFrameRateCategory != oldCategory
+        if (mPreferredFrameRateCategory != oldCategory
                 && mPreferredFrameRateCategory == frameRateCategory
         ) {
             mFrameRateCategoryChangeReason = reason;
-            mFrameRateCategoryView = view.getClass().getSimpleName();
+            mFrameRateCategoryView = view == null ? "null" : view.getClass().getSimpleName();
         }
     }
 
@@ -12791,7 +12790,6 @@
         // uncomment this when we are ready for enabling dVRR
         // return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
         return false;
-
     }
 
     private void checkIdleness() {
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 226629e..dcdb8b0 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -19,7 +19,6 @@
 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.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_DEFAULT_NORMAL_READ_ONLY;
 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;
@@ -137,8 +136,8 @@
         mActivityRule.runOnUiThread(() -> {
             float density = mActivity.getResources().getDisplayMetrics().density;
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
-            layoutParams.height = (int) (40 * density);
-            layoutParams.width = (int) (40 * density);
+            layoutParams.height = 4 * ((int) (10 * density));
+            layoutParams.width = 4 * ((int) (10 * density));
             mMovingView.setLayoutParams(layoutParams);
             mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
         });
@@ -212,8 +211,8 @@
         mActivityRule.runOnUiThread(() -> {
             float density = mActivity.getResources().getDisplayMetrics().density;
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
-            layoutParams.height = (int) (40 * density);
-            layoutParams.width = (int) Math.ceil(41 * density);
+            layoutParams.height = 4 * ((int) (10 * density));
+            layoutParams.width = 4 * ((int) Math.ceil(10 * density)) + 1;
             mMovingView.setLayoutParams(layoutParams);
             mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
         });
@@ -237,8 +236,8 @@
         mActivityRule.runOnUiThread(() -> {
             float density = mActivity.getResources().getDisplayMetrics().density;
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
-            layoutParams.height = (int) Math.ceil(41 * density);
-            layoutParams.width = (int) (40 * density);
+            layoutParams.height = 4 * ((int) Math.ceil(10 * density)) + 1;
+            layoutParams.width = 4 * ((int) (10 * density));
             mMovingView.setLayoutParams(layoutParams);
             mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
         });
@@ -256,13 +255,14 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
-            FLAG_TOOLKIT_FRAME_RATE_DEFAULT_NORMAL_READ_ONLY})
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public void defaultNormal() throws Throwable {
         waitForFrameRateCategoryToSettle();
         mActivityRule.runOnUiThread(() -> {
             mMovingView.invalidate();
-            assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+            int expected = toolkitFrameRateDefaultNormalReadOnly()
+                    ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+            assertEquals(expected,
                     mViewRoot.getPreferredFrameRateCategory());
         });
     }
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index d95a039..a034f3b 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -667,7 +667,7 @@
     }
 
     /**
-     * Test how values of the frame rate cateogry are aggregated.
+     * Test how values of the frame rate category are aggregated.
      * It should take the max value among all of the voted categories per frame.
      */
     @Test