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

Commit 5af0462a authored by Rachel Lee's avatar Rachel Lee
Browse files

Implement Java Choreographer multi frame timeline.

Bug: 198191703
Test: atest ChoreographerTest
Change-Id: I822782874bc7167be9ad016681ce31b91a38b7ed
parent ffe4331a
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -47595,15 +47595,33 @@ package android.view {
  public final class Choreographer {
    method public static android.view.Choreographer getInstance();
    method public void postExtendedFrameCallback(@NonNull android.view.Choreographer.ExtendedFrameCallback);
    method public void postFrameCallback(android.view.Choreographer.FrameCallback);
    method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long);
    method public void removeExtendedFrameCallback(@Nullable android.view.Choreographer.ExtendedFrameCallback);
    method public void removeFrameCallback(android.view.Choreographer.FrameCallback);
  }
  public static interface Choreographer.ExtendedFrameCallback {
    method public void onVsync(@NonNull android.view.Choreographer.FrameData);
  }
  public static interface Choreographer.FrameCallback {
    method public void doFrame(long);
  }
  public static class Choreographer.FrameData {
    method public long getFrameTimeNanos();
    method @NonNull public android.view.Choreographer.FrameTimeline[] getFrameTimelines();
    method @NonNull public android.view.Choreographer.FrameTimeline getPreferredFrameTimeline();
  }
  public static class Choreographer.FrameTimeline {
    method public long getDeadlineNanos();
    method public long getExpectedPresentTimeNanos();
    method public long getVsyncId();
  }
  public interface CollapsibleActionView {
    method public void onActionViewCollapsed();
    method public void onActionViewExpanded();
+197 −15
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ package android.view;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.FrameInfo;
@@ -151,10 +154,15 @@ public final class Choreographer {
    private static final int MSG_DO_SCHEDULE_VSYNC = 1;
    private static final int MSG_DO_SCHEDULE_CALLBACK = 2;

    // All frame callbacks posted by applications have this token.
    // All frame callbacks posted by applications have this token or EXTENDED_FRAME_CALLBACK_TOKEN.
    private static final Object FRAME_CALLBACK_TOKEN = new Object() {
        public String toString() { return "FRAME_CALLBACK_TOKEN"; }
    };
    private static final Object EXTENDED_FRAME_CALLBACK_TOKEN = new Object() {
        public String toString() {
            return "EXTENDED_FRAME_CALLBACK_TOKEN";
        }
    };

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private final Object mLock = new Object();
@@ -483,6 +491,24 @@ public final class Choreographer {
        }
    }

    /**
     * Posts an extended frame callback to run on the next frame.
     * <p>
     * The callback runs once then is automatically removed.
     * </p>
     *
     * @param callback The extended frame callback to run during the next frame.
     *
     * @see #removeExtendedFrameCallback
     */
    public void postExtendedFrameCallback(@NonNull ExtendedFrameCallback callback) {
        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN, 0);
    }

    /**
     * Removes callbacks that have the specified action and token.
     *
@@ -572,6 +598,21 @@ public final class Choreographer {
        removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
    }

    /**
     * Removes a previously posted extended frame callback.
     *
     * @param callback The extended frame callback to remove.
     *
     * @see #postExtendedFrameCallback
     */
    public void removeExtendedFrameCallback(@Nullable ExtendedFrameCallback callback) {
        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        removeCallbacksInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN);
    }

    /**
     * Gets the time when the current frame started.
     * <p>
@@ -673,7 +714,7 @@ public final class Choreographer {
     * @hide
     */
    public long getVsyncId() {
        return mLastVsyncEventData.id;
        return mLastVsyncEventData.preferredFrameTimeline().vsyncId;
    }

    /**
@@ -684,7 +725,7 @@ public final class Choreographer {
     * @hide
     */
    public long getFrameDeadline() {
        return mLastVsyncEventData.frameDeadline;
        return mLastVsyncEventData.preferredFrameTimeline().deadline;
    }

    void setFPSDivisor(int divisor) {
@@ -705,8 +746,9 @@ public final class Choreographer {
        try {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                        "Choreographer#doFrame " + vsyncEventData.id);
                        "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId);
            }
            FrameData frameData = new FrameData(frameTimeNanos, vsyncEventData);
            synchronized (mLock) {
                if (!mFrameScheduled) {
                    traceMessage("Frame not scheduled");
@@ -737,6 +779,7 @@ public final class Choreographer {
                                + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                    }
                    frameTimeNanos = startNanos - lastFrameOffset;
                    frameData.setFrameTimeNanos(-lastFrameOffset);
                }

                if (frameTimeNanos < mLastFrameTimeNanos) {
@@ -758,8 +801,10 @@ public final class Choreographer {
                    }
                }

                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                        vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos,
                        vsyncEventData.preferredFrameTimeline().vsyncId,
                        vsyncEventData.preferredFrameTimeline().deadline, startNanos,
                        vsyncEventData.frameInterval);
                mFrameScheduled = false;
                mLastFrameTimeNanos = frameTimeNanos;
                mLastFrameIntervalNanos = frameIntervalNanos;
@@ -769,17 +814,17 @@ public final class Choreographer {
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData,
                    frameIntervalNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
@@ -793,8 +838,9 @@ public final class Choreographer {
        }
    }

    void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
    void doCallbacks(int callbackType, FrameData frameData, long frameIntervalNanos) {
        CallbackRecord callbacks;
        long frameTimeNanos = frameData.mFrameTimeNanos;
        synchronized (mLock) {
            // We use "now" to determine when callbacks become due because it's possible
            // for earlier processing phases in a frame to post callbacks that should run
@@ -831,6 +877,7 @@ public final class Choreographer {
                    }
                    frameTimeNanos = now - lastFrameOffset;
                    mLastFrameTimeNanos = frameTimeNanos;
                    frameData.setFrameTimeNanos(frameTimeNanos);
                }
            }
        }
@@ -842,7 +889,7 @@ public final class Choreographer {
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                c.run(frameTimeNanos);
                c.run(frameData);
            }
        } finally {
            synchronized (mLock) {
@@ -942,6 +989,130 @@ public final class Choreographer {
        public void doFrame(long frameTimeNanos);
    }

    /** Holds data that describes one possible VSync frame event to render at. */
    public static class FrameTimeline {
        static final FrameTimeline INVALID_FRAME_TIMELINE = new FrameTimeline(
                FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE);

        FrameTimeline(long vsyncId, long expectedPresentTimeNanos, long deadlineNanos) {
            this.mVsyncId = vsyncId;
            this.mExpectedPresentTimeNanos = expectedPresentTimeNanos;
            this.mDeadlineNanos = deadlineNanos;
        }

        private long mVsyncId;
        private long mExpectedPresentTimeNanos;
        private long mDeadlineNanos;

        /**
         * The id that corresponds to this frame timeline, used to correlate a frame
         * produced by HWUI with the timeline data stored in Surface Flinger.
         */
        public long getVsyncId() {
            return mVsyncId;
        }

        /** Sets the vsync ID. */
        void resetVsyncId() {
            mVsyncId = FrameInfo.INVALID_VSYNC_ID;
        }

        /**
         * The time in {@link System#nanoTime()} timebase which this frame is expected to be
         * presented.
         */
        public long getExpectedPresentTimeNanos() {
            return mExpectedPresentTimeNanos;
        }

        /**
         * The time in  {@link System#nanoTime()} timebase which this frame needs to be ready by.
         */
        public long getDeadlineNanos() {
            return mDeadlineNanos;
        }
    }

    /**
     * The payload for {@link ExtendedFrameCallback} which includes frame information such as when
     * the frame started being rendered, and multiple possible frame timelines and their
     * information including deadline and expected present time.
     */
    public static class FrameData {
        static final FrameTimeline[] INVALID_FRAME_TIMELINES = new FrameTimeline[0];
        FrameData() {
            this.mFrameTimelines = INVALID_FRAME_TIMELINES;
            this.mPreferredFrameTimeline = FrameTimeline.INVALID_FRAME_TIMELINE;
        }

        FrameData(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
            FrameTimeline[] frameTimelines =
                    new FrameTimeline[vsyncEventData.frameTimelines.length];
            for (int i = 0; i <  vsyncEventData.frameTimelines.length; i++) {
                DisplayEventReceiver.VsyncEventData.FrameTimeline frameTimeline =
                        vsyncEventData.frameTimelines[i];
                frameTimelines[i] = new FrameTimeline(frameTimeline.vsyncId,
                        frameTimeline.expectedPresentTime, frameTimeline.deadline);
            }
            this.mFrameTimeNanos = frameTimeNanos;
            this.mFrameTimelines = frameTimelines;
            this.mPreferredFrameTimeline =
                    frameTimelines[vsyncEventData.preferredFrameTimelineIndex];
        }

        private long mFrameTimeNanos;
        private final FrameTimeline[] mFrameTimelines;
        private final FrameTimeline mPreferredFrameTimeline;

        void setFrameTimeNanos(long frameTimeNanos) {
            mFrameTimeNanos = frameTimeNanos;
            for (FrameTimeline ft : mFrameTimelines) {
                // The ID is no longer valid because the frame time that was registered with the ID
                // no longer matches.
                // TODO(b/205721584): Ask SF for valid vsync information.
                ft.resetVsyncId();
            }
        }

        /** The time in nanoseconds when the frame started being rendered. */
        public long getFrameTimeNanos() {
            return mFrameTimeNanos;
        }

        /** The possible frame timelines, sorted chronologically. */
        @NonNull
        @SuppressLint("ArrayReturn") // For API consistency and speed.
        public FrameTimeline[] getFrameTimelines() {
            return mFrameTimelines;
        }

        /** The platform-preferred frame timeline. */
        @NonNull
        public FrameTimeline getPreferredFrameTimeline() {
            return mPreferredFrameTimeline;
        }
    }

    /**
     * Implement this interface to receive a callback to start the next frame. The callback is
     * invoked on the {@link Looper} thread to which the {@link Choreographer} is attached. The
     * callback payload contains information about multiple possible frames, allowing choice of
     * the appropriate frame based on latency requirements.
     *
     * @see FrameCallback
     */
    public interface ExtendedFrameCallback {
        /**
         * Called when a new display frame is being rendered.
         *
         * @param data The payload which includes frame information. Divide nanosecond values by
         *             {@code 1000000} to convert it to the {@link SystemClock#uptimeMillis()}
         *             time base.
         * @see FrameCallback#doFrame
         **/
        void onVsync(@NonNull FrameData data);
    }

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
@@ -983,7 +1154,8 @@ public final class Choreographer {
            try {
                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                            "Choreographer#onVsync " + vsyncEventData.id);
                            "Choreographer#onVsync "
                                    + vsyncEventData.preferredFrameTimeline().vsyncId);
                }
                // Post the vsync event to the Handler.
                // The idea is to prevent incoming vsync events from completely starving
@@ -1026,7 +1198,9 @@ public final class Choreographer {
    private static final class CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        /** Runnable or FrameCallback or ExtendedFrameCallback object. */
        public Object action;
        /** Denotes the action type. */
        public Object token;

        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1037,6 +1211,14 @@ public final class Choreographer {
                ((Runnable)action).run();
            }
        }

        void run(FrameData frameData) {
            if (token == EXTENDED_FRAME_CALLBACK_TOKEN) {
                ((ExtendedFrameCallback) action).onVsync(frameData);
            } else {
                run(frameData.getFrameTimeNanos());
            }
        }
    }

    private final class CallbackQueue {
+39 −14
Original line number Diff line number Diff line
@@ -138,13 +138,28 @@ public abstract class DisplayEventReceiver {
    }

    static final class VsyncEventData {

        static final FrameTimeline[] INVALID_FRAME_TIMELINES =
                {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)};

        public static class FrameTimeline {
            FrameTimeline(long vsyncId, long expectedPresentTime, long deadline) {
                this.vsyncId = vsyncId;
                this.expectedPresentTime = expectedPresentTime;
                this.deadline = deadline;
            }

            // The frame timeline vsync id, used to correlate a frame
            // produced by HWUI with the timeline data stored in Surface Flinger.
        public final long id;
            public final long vsyncId;

            // The frame timestamp for when the frame is expected to be presented.
            public final long expectedPresentTime;

            // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
            // allotted for the frame to be completed.
        public final long frameDeadline;
            public final long deadline;
        }

        /**
         * The current interval between frames in ns. This will be used to align
@@ -153,16 +168,27 @@ public abstract class DisplayEventReceiver {
         */
        public final long frameInterval;

        VsyncEventData(long id, long frameDeadline, long frameInterval) {
            this.id = id;
            this.frameDeadline = frameDeadline;
        public final FrameTimeline[] frameTimelines;

        public final int preferredFrameTimelineIndex;

        // Called from native code.
        @SuppressWarnings("unused")
        VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex,
                long frameInterval) {
            this.frameTimelines = frameTimelines;
            this.preferredFrameTimelineIndex = preferredFrameTimelineIndex;
            this.frameInterval = frameInterval;
        }

        VsyncEventData() {
            this.id = FrameInfo.INVALID_VSYNC_ID;
            this.frameDeadline = Long.MAX_VALUE;
            this.frameInterval = -1;
            this.frameTimelines = INVALID_FRAME_TIMELINES;
            this.preferredFrameTimelineIndex = 0;
        }

        public FrameTimeline preferredFrameTimeline() {
            return frameTimelines[preferredFrameTimelineIndex];
        }
    }

@@ -256,9 +282,8 @@ public abstract class DisplayEventReceiver {
    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
            long frameTimelineVsyncId, long frameDeadline, long frameInterval) {
        onVsync(timestampNanos, physicalDisplayId, frame,
                new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval));
            VsyncEventData vsyncEventData) {
        onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);
    }

    // Called from native code.
+60 −3
Original line number Diff line number Diff line
@@ -48,6 +48,16 @@ static struct {
        jmethodID init;
    } frameRateOverrideClassInfo;

    struct {
        jclass clazz;
        jmethodID init;
    } frameTimelineClassInfo;

    struct {
        jclass clazz;
        jmethodID init;
    } vsyncEventDataClassInfo;

} gDisplayEventReceiverClassInfo;


@@ -105,9 +115,38 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla
    ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
    if (receiverObj.get()) {
        ALOGV("receiver %p ~ Invoking vsync handler.", this);

        ScopedLocalRef<jobjectArray>
                frameTimelineObjs(env,
                                  env->NewObjectArray(vsyncEventData.frameTimelines.size(),
                                                      gDisplayEventReceiverClassInfo
                                                              .frameTimelineClassInfo.clazz,
                                                      /*initial element*/ NULL));
        for (int i = 0; i < vsyncEventData.frameTimelines.size(); i++) {
            VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i];
            ScopedLocalRef<jobject>
                    frameTimelineObj(env,
                                     env->NewObject(gDisplayEventReceiverClassInfo
                                                            .frameTimelineClassInfo.clazz,
                                                    gDisplayEventReceiverClassInfo
                                                            .frameTimelineClassInfo.init,
                                                    frameTimeline.id,
                                                    frameTimeline.expectedPresentTime,
                                                    frameTimeline.deadlineTimestamp));
            env->SetObjectArrayElement(frameTimelineObjs.get(), i, frameTimelineObj.get());
        }
        ScopedLocalRef<jobject>
                vsyncEventDataJava(env,
                                   env->NewObject(gDisplayEventReceiverClassInfo
                                                          .vsyncEventDataClassInfo.clazz,
                                                  gDisplayEventReceiverClassInfo
                                                          .vsyncEventDataClassInfo.init,
                                                  frameTimelineObjs.get(),
                                                  vsyncEventData.preferredFrameTimelineIndex,
                                                  vsyncEventData.frameInterval));

        env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
                            timestamp, displayId.value, count, vsyncEventData.id,
                            vsyncEventData.deadlineTimestamp, vsyncEventData.frameInterval);
                            timestamp, displayId.value, count, vsyncEventDataJava.get());
        ALOGV("receiver %p ~ Returned from vsync handler.", this);
    }

@@ -239,7 +278,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {

    gDisplayEventReceiverClassInfo.dispatchVsync =
            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync",
                             "(JJIJJJ)V");
                             "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V");
    gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
            gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
    gDisplayEventReceiverClassInfo.dispatchModeChanged =
@@ -258,6 +297,24 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.clazz,
                             "<init>", "(IF)V");

    jclass frameTimelineClazz =
            FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData$FrameTimeline");
    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz =
            MakeGlobalRefOrDie(env, frameTimelineClazz);
    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init =
            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
                             "<init>", "(JJJ)V");

    jclass vsyncEventDataClazz =
            FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData");
    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz =
            MakeGlobalRefOrDie(env, vsyncEventDataClazz);
    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init =
            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
                             "<init>",
                             "([Landroid/view/"
                             "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V");

    return res;
}