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

Commit 3332d8ba authored by George Mount's avatar George Mount Committed by Android (Google) Code Review
Browse files

Merge changes from topic "vrr_idle" into main

* changes:
  [VRR] Support setRequestedFrameRate() with AVDs
  [VRR] vrr_bugfix_24q4 flag to gate toolkit VRR idle handling
  [VRR] Clear frame rate and category after 750ms idle
parents d4bea0f3 05264e77
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view;

import android.animation.Animator;

/**
 * Exists just to allow for android.graphics & android.view package separation
 *
@@ -26,4 +28,7 @@ package android.view;
public interface NativeVectorDrawableAnimator {
    /** @hide */
    long getAnimatorNativePtr();

    /** @hide */
    void setThreadedRendererAnimatorListener(Animator.AnimatorListener listener);
}
+26 −3
Original line number Diff line number Diff line
@@ -16,14 +16,19 @@

package android.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.graphics.RenderNode;

import androidx.annotation.NonNull;

/**
 * Maps a View to a RenderNode's AnimationHost
 *
 * @hide
 */
public class ViewAnimationHostBridge implements RenderNode.AnimationHost {
public class ViewAnimationHostBridge extends AnimatorListenerAdapter
        implements RenderNode.AnimationHost {
    private final View mView;

    /**
@@ -34,17 +39,35 @@ public class ViewAnimationHostBridge implements RenderNode.AnimationHost {
    }

    @Override
    public void registerAnimatingRenderNode(RenderNode animator) {
        mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(animator);
    public void registerAnimatingRenderNode(RenderNode renderNode, Animator animator) {
        mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(renderNode);
        animator.addListener(this);
    }

    @Override
    public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) {
        mView.mAttachInfo.mViewRootImpl.registerVectorDrawableAnimator(animator);
        animator.setThreadedRendererAnimatorListener(this);
    }

    @Override
    public boolean isAttached() {
        return mView.mAttachInfo != null;
    }

    @Override
    public void onAnimationStart(@NonNull Animator animation) {
        ViewRootImpl viewRoot = mView.getViewRootImpl();
        if (viewRoot != null) {
            viewRoot.addThreadedRendererView(mView);
        }
    }

    @Override
    public void onAnimationEnd(@NonNull Animator animation) {
        ViewRootImpl viewRoot = mView.getViewRootImpl();
        if (viewRoot != null) {
            viewRoot.removeThreadedRendererView(mView);
        }
    }
}
+92 −1
Original line number Diff line number Diff line
@@ -427,6 +427,12 @@ public final class ViewRootImpl implements ViewParent,
    private static final long NANOS_PER_SEC = 1000000000;
    // If the ViewRootImpl has been idle for more than 750ms, clear the preferred
    // frame rate category and frame rate.
    private static final int IDLE_TIME_MILLIS = 750;
    private static final long NANOS_PER_MILLI = 1_000_000;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
@@ -659,6 +665,10 @@ public final class ViewRootImpl implements ViewParent,
    private int mMinusOneFrameIntervalMillis = 0;
    // VRR interval between the previous and the frame before
    private int mMinusTwoFrameIntervalMillis = 0;
    // VRR has the invalidation idle message been posted?
    private boolean mInvalidationIdleMessagePosted = false;
    // VRR: List of all Views that are animating with the threaded render
    private ArrayList<View> mThreadedRendererViews = new ArrayList();
    /**
     * Update the Choreographer's FrameInfo object with the timing information for the current
@@ -1184,6 +1194,8 @@ public final class ViewRootImpl implements ViewParent,
            toolkitFrameRateVelocityMappingReadOnly();
    private static boolean sToolkitEnableInvalidateCheckThreadFlagValue =
            Flags.enableInvalidateCheckThread();
    private static boolean sSurfaceFlingerBugfixFlagValue =
            com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4();
    static {
        sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
@@ -4261,8 +4273,13 @@ public final class ViewRootImpl implements ViewParent,
        // when the values are applicable.
        if (mDrawnThisFrame) {
            mDrawnThisFrame = false;
            if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) {
                mInvalidationIdleMessagePosted = true;
                mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS);
            }
            setCategoryFromCategoryCounts();
            updateInfrequentCount();
            updateFrameRateFromThreadedRendererViews();
            setPreferredFrameRate(mPreferredFrameRate);
            setPreferredFrameRateCategory(mPreferredFrameRateCategory);
            if (mPreferredFrameRate > 0
@@ -6499,6 +6516,8 @@ public final class ViewRootImpl implements ViewParent,
                    return "MSG_WINDOW_TOUCH_MODE_CHANGED";
                case MSG_KEEP_CLEAR_RECTS_CHANGED:
                    return "MSG_KEEP_CLEAR_RECTS_CHANGED";
                case MSG_CHECK_INVALIDATION_IDLE:
                    return "MSG_CHECK_INVALIDATION_IDLE";
                case MSG_REFRESH_POINTER_ICON:
                    return "MSG_REFRESH_POINTER_ICON";
                case MSG_TOUCH_BOOST_TIMEOUT:
@@ -6759,6 +6778,31 @@ public final class ViewRootImpl implements ViewParent,
                    mNumPausedForSync = 0;
                    scheduleTraversals();
                    break;
                case MSG_CHECK_INVALIDATION_IDLE: {
                    long delta;
                    if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) {
                        delta = 0;
                    } else {
                        delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis;
                    }
                    if (delta >= IDLE_TIME_MILLIS) {
                        mFrameRateCategoryHighCount = 0;
                        mFrameRateCategoryHighHintCount = 0;
                        mFrameRateCategoryNormalCount = 0;
                        mFrameRateCategoryLowCount = 0;
                        mPreferredFrameRate = 0;
                        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
                        updateFrameRateFromThreadedRendererViews();
                        setPreferredFrameRate(mPreferredFrameRate);
                        setPreferredFrameRateCategory(mPreferredFrameRateCategory);
                        mInvalidationIdleMessagePosted = false;
                    } else {
                        mInvalidationIdleMessagePosted = true;
                        mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
                                IDLE_TIME_MILLIS - delta);
                    }
                    break;
                }
                case MSG_TOUCH_BOOST_TIMEOUT:
                    /**
                     * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
@@ -12580,6 +12624,24 @@ public final class ViewRootImpl implements ViewParent,
        EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
    }
    /**
     * Views that are animating with the ThreadedRenderer don't use the normal invalidation
     * path, so the value won't be updated through performTraversals. This reads the votes
     * from those views.
     */
    private void updateFrameRateFromThreadedRendererViews() {
        ArrayList<View> views = mThreadedRendererViews;
        for (int i = views.size() - 1; i >= 0; i--) {
            View view = views.get(i);
            View.AttachInfo attachInfo = view.mAttachInfo;
            if (attachInfo == null || attachInfo.mViewRootImpl != this) {
                views.remove(i);
            } else {
                view.votePreferredFrameRate();
            }
        }
    }
    /**
     * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts.
     */
@@ -12760,6 +12822,31 @@ public final class ViewRootImpl implements ViewParent,
        mDrawnThisFrame = true;
    }
    /**
     * Mark a View as having an active ThreadedRenderer animation. This is used for
     * RenderNodeAnimators and AnimatedVectorDrawables. When the animation stops,
     * {@link #removeThreadedRendererView(View)} must be called.
     * @param view The View with the ThreadedRenderer animation that started.
     */
    public void addThreadedRendererView(View view) {
        if (!mThreadedRendererViews.contains(view)) {
            mThreadedRendererViews.add(view);
        }
    }
    /**
     * When a ThreadedRenderer animation ends, the View that is associated with it using
     * {@link #addThreadedRendererView(View)} must be removed with a call to this method.
     * @param view The View whose ThreadedRender animation has stopped.
     */
    public void removeThreadedRendererView(View view) {
        mThreadedRendererViews.remove(view);
        if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) {
            mInvalidationIdleMessagePosted = true;
            mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS);
        }
    }
    /**
     * Returns {@link #INTERMITTENT_STATE_INTERMITTENT} when the ViewRootImpl has only been
     * updated intermittently, {@link #INTERMITTENT_STATE_NOT_INTERMITTENT} when it is
@@ -12983,6 +13070,10 @@ public final class ViewRootImpl implements ViewParent,
    private void removeVrrMessages() {
        mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
        mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
        if (mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) {
            mInvalidationIdleMessagePosted = false;
            mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
        }
    }
    /**
@@ -13001,7 +13092,7 @@ public final class ViewRootImpl implements ViewParent,
        mMinusOneFrameIntervalMillis = timeIntervalMillis;
        mLastUpdateTimeMillis = currentTimeMillis;
        if (timeIntervalMillis + mMinusTwoFrameIntervalMillis
        if (mThreadedRendererViews.isEmpty() && timeIntervalMillis + mMinusTwoFrameIntervalMillis
                >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
            int infrequentUpdateCount = mInfrequentUpdateCount;
            mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
+158 −0
Original line number Diff line number Diff line
@@ -41,11 +41,13 @@ import android.app.Instrumentation;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.DisplayMetrics;
import android.widget.FrameLayout;
import android.widget.ProgressBar;

import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -623,6 +625,162 @@ public class ViewFrameRateTest {
        assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory());
    }

    @LargeTest
    @Test
    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY,
            com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
    })
    public void idleDetected() throws Throwable {
        waitForFrameRateCategoryToSettle();
        mActivityRule.runOnUiThread(() -> {
            mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
            mMovingView.setFrameContentVelocity(Float.MAX_VALUE);
            mMovingView.invalidate();
            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH,
                    mViewRoot.getLastPreferredFrameRateCategory()));
        });
        waitForAfterDraw();

        // Wait for idle timeout
        Thread.sleep(1000);
        assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
        assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
                mViewRoot.getLastPreferredFrameRateCategory());
    }

    @LargeTest
    @Test
    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY,
            com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
    })
    public void vectorDrawableFrameRate() throws Throwable {
        final ProgressBar[] progressBars = new ProgressBar[3];
        final ViewGroup[] parents = new ViewGroup[1];
        mActivityRule.runOnUiThread(() -> {
            ViewGroup parent = (ViewGroup) mMovingView.getParent();
            parents[0] = parent;
            ProgressBar progressBar1 = new ProgressBar(mActivity);
            parent.addView(progressBar1);
            progressBar1.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
            progressBar1.setIndeterminate(true);
            progressBars[0] = progressBar1;

            ProgressBar progressBar2 = new ProgressBar(mActivity);
            parent.addView(progressBar2);
            progressBar2.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
            progressBar2.setIndeterminate(true);
            progressBars[1] = progressBar2;

            ProgressBar progressBar3 = new ProgressBar(mActivity);
            parent.addView(progressBar3);
            progressBar3.setRequestedFrameRate(45f);
            progressBar3.setIndeterminate(true);
            progressBars[2] = progressBar3;
        });
        waitForFrameRateCategoryToSettle();

        // Wait for idle timeout
        Thread.sleep(1000);
        assertEquals(45f, mViewRoot.getLastPreferredFrameRate());
        assertEquals(FRAME_RATE_CATEGORY_NORMAL, mViewRoot.getLastPreferredFrameRateCategory());

        // Removing the vector drawable with NORMAL should drop the category to LOW
        mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[1]));
        Thread.sleep(1000);
        assertEquals(45f, mViewRoot.getLastPreferredFrameRate());
        assertEquals(FRAME_RATE_CATEGORY_LOW,
                mViewRoot.getLastPreferredFrameRateCategory());
        // Removing the one voting for frame rate should leave only the category
        mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[2]));
        Thread.sleep(1000);
        assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
        assertEquals(FRAME_RATE_CATEGORY_LOW,
                mViewRoot.getLastPreferredFrameRateCategory());
        // Removing the last one should leave it with no preference
        mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[0]));
        Thread.sleep(1000);
        assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
        assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
                mViewRoot.getLastPreferredFrameRateCategory());
    }

    @LargeTest
    @Test
    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY,
            com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
    })
    public void renderNodeAnimatorFrameRateCanceled() throws Throwable {
        mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
        waitForFrameRateCategoryToSettle();

        RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1];
        renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f);
        renderNodeAnimator[0].setDuration(100000);

        mActivityRule.runOnUiThread(() -> {
            renderNodeAnimator[0].setTarget(mMovingView);
            renderNodeAnimator[0].start();
            mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
            runAfterDraw(() -> {
                assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
                assertEquals(FRAME_RATE_CATEGORY_LOW,
                        mViewRoot.getLastPreferredFrameRateCategory());
            });
        });
        waitForAfterDraw();

        mActivityRule.runOnUiThread(() -> {
            renderNodeAnimator[0].cancel();
        });

        // Wait for idle timeout
        Thread.sleep(1000);
        assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
        assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
                mViewRoot.getLastPreferredFrameRateCategory());
    }

    @LargeTest
    @Test
    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY,
            com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
    })
    public void renderNodeAnimatorFrameRateRemoved() throws Throwable {
        mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
        waitForFrameRateCategoryToSettle();

        RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1];
        renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f);
        renderNodeAnimator[0].setDuration(100000);

        mActivityRule.runOnUiThread(() -> {
            renderNodeAnimator[0].setTarget(mMovingView);
            renderNodeAnimator[0].start();
            mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
            runAfterDraw(() -> {
                assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
                assertEquals(FRAME_RATE_CATEGORY_LOW,
                        mViewRoot.getLastPreferredFrameRateCategory());
            });
        });
        waitForAfterDraw();

        mActivityRule.runOnUiThread(() -> {
            ViewGroup parent = (ViewGroup) mMovingView.getParent();
            assert parent != null;
            parent.removeView(mMovingView);
        });

        Thread.sleep(1000);
        assertEquals(0f, mViewRoot.getLastPreferredFrameRate());
        assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
                mViewRoot.getLastPreferredFrameRateCategory());
    }

    private void runAfterDraw(@NonNull Runnable runnable) {
        Handler handler = new Handler(Looper.getMainLooper());
        mAfterDrawLatch = new CountDownLatch(1);
+3 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.graphics;

import android.animation.Animator;
import android.annotation.BytesLong;
import android.annotation.ColorInt;
import android.annotation.FloatRange;
@@ -1639,7 +1640,7 @@ public final class RenderNode {
     */
    public interface AnimationHost {
        /** @hide */
        void registerAnimatingRenderNode(RenderNode animator);
        void registerAnimatingRenderNode(RenderNode renderNode, Animator animator);

        /** @hide */
        void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator);
@@ -1654,7 +1655,7 @@ public final class RenderNode {
            throw new IllegalStateException("Cannot start this animator on a detached view!");
        }
        nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
        mAnimationHost.registerAnimatingRenderNode(this);
        mAnimationHost.registerAnimatingRenderNode(this, animator);
    }

    /** @hide */
Loading