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

Commit 05264e77 authored by George Mount's avatar George Mount
Browse files

[VRR] Support setRequestedFrameRate() with AVDs

Fixes: 342666766

Adds support for Animated Vector Drawables that operate on the
Render Thread to VRR. The votes from their respective Views
are considered on each vote.

Test: new tests, manual testing
Change-Id: I1e761d62e5bc4a706cc7fb2904f26aaa68e87584
parent d5d53e0e
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);
        }
    }
}
+50 −3
Original line number Diff line number Diff line
@@ -667,6 +667,8 @@ public final class ViewRootImpl implements ViewParent,
    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
@@ -4277,6 +4279,7 @@ public final class ViewRootImpl implements ViewParent,
            }
            setCategoryFromCategoryCounts();
            updateInfrequentCount();
            updateFrameRateFromThreadedRendererViews();
            setPreferredFrameRate(mPreferredFrameRate);
            setPreferredFrameRateCategory(mPreferredFrameRateCategory);
            if (mPreferredFrameRate > 0
@@ -6789,8 +6792,9 @@ public final class ViewRootImpl implements ViewParent,
                        mFrameRateCategoryLowCount = 0;
                        mPreferredFrameRate = 0;
                        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
                        setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE);
                        setPreferredFrameRate(0f);
                        updateFrameRateFromThreadedRendererViews();
                        setPreferredFrameRate(mPreferredFrameRate);
                        setPreferredFrameRateCategory(mPreferredFrameRateCategory);
                        mInvalidationIdleMessagePosted = false;
                    } else {
                        mInvalidationIdleMessagePosted = true;
@@ -12616,6 +12620,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.
     */
@@ -12796,6 +12818,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
@@ -13041,7 +13088,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
+135 −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,7 @@ 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,
@@ -646,6 +649,138 @@ public class ViewFrameRateTest {
                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