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

Commit 2ee0ab95 authored by Jorim Jaggi's avatar Jorim Jaggi
Browse files

Migrate FrameTracker to use ST Jank information

Test: FrameTrackerTest
Test: Systrace, perform CUJ
Bug: 174755489
Change-Id: I906abce66d456add7c562b1d9038352eb589f6f0
parent 677c97dd
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -207,7 +207,7 @@ public final class FrameMetrics {
            Index.FRAME_COMPLETED,
    })
    @Retention(RetentionPolicy.SOURCE)
    private @interface Index {
    public @interface Index {
        int FLAGS = 0;
        int FRAME_TIMELINE_VSYNC_ID = 1;
        int INTENDED_VSYNC = 2;
+13 −6
Original line number Diff line number Diff line
@@ -266,12 +266,12 @@ public final class SurfaceControl implements Parcelable {

        /** @hide */
        @IntDef(flag = true, value = {JANK_NONE,
                JANK_DISPLAY,
                DISPLAY_HAL,
                JANK_SURFACEFLINGER_DEADLINE_MISSED,
                JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED,
                JANK_APP_DEADLINE_MISSED,
                JANK_PREDICTION_EXPIRED,
                JANK_SURFACEFLINGER_EARLY_LATCH})
                PREDICTION_ERROR,
                SURFACE_FLINGER_SCHEDULING})
        @Retention(RetentionPolicy.SOURCE)
        public @interface JankType {}

@@ -281,7 +281,7 @@ public final class SurfaceControl implements Parcelable {
        public static final int JANK_NONE = 0x0;

        // Jank not related to SurfaceFlinger or the App
        public static final int JANK_DISPLAY = 0x1;
        public static final int DISPLAY_HAL = 0x1;
        // SF took too long on the CPU
        public static final int JANK_SURFACEFLINGER_DEADLINE_MISSED = 0x2;
        // SF took too long on the GPU
@@ -291,9 +291,16 @@ public final class SurfaceControl implements Parcelable {
        // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a
        // jank
        // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame.
        public static final int JANK_PREDICTION_EXPIRED = 0x10;
        public static final int PREDICTION_ERROR = 0x10;
        // Latching a buffer early might cause an early present of the frame
        public static final int JANK_SURFACEFLINGER_EARLY_LATCH = 0x20;
        public static final int SURFACE_FLINGER_SCHEDULING = 0x20;
        // A buffer is said to be stuffed if it was expected to be presented on a vsync but was
        // presented later because the previous buffer was presented in its expected vsync. This
        // usually happens if there is an unexpectedly long frame causing the rest of the buffers
        // to enter a stuffed state.
        public static final int BUFFER_STUFFING = 0x40;
        // Jank due to unknown reasons.
        public static final int UNKNOWN = 0x80;

        public JankData(long frameVsyncId, @JankType int jankType) {
            this.frameVsyncId = frameVsyncId;
+3 −3
Original line number Diff line number Diff line
@@ -1787,18 +1787,18 @@ public final class ViewRootImpl implements ViewParent,


    /** Register callbacks to be notified when the ViewRootImpl surface changes. */
    interface SurfaceChangedCallback {
    public interface SurfaceChangedCallback {
        void surfaceCreated(Transaction t);
        void surfaceReplaced(Transaction t);
        void surfaceDestroyed();
    }

    private final ArrayList<SurfaceChangedCallback> mSurfaceChangedCallbacks = new ArrayList<>();
    void addSurfaceChangedCallback(SurfaceChangedCallback c) {
    public void addSurfaceChangedCallback(SurfaceChangedCallback c) {
        mSurfaceChangedCallbacks.add(c);
    }

    void removeSurfaceChangedCallback(SurfaceChangedCallback c) {
    public void removeSurfaceChangedCallback(SurfaceChangedCallback c) {
        mSurfaceChangedCallbacks.remove(c);
    }

+339 −83
Original line number Diff line number Diff line
@@ -16,13 +16,28 @@

package com.android.internal.jank;

import static android.view.SurfaceControl.JankData.BUFFER_STUFFING;
import static android.view.SurfaceControl.JankData.DISPLAY_HAL;
import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
import static android.view.SurfaceControl.JankData.JANK_NONE;
import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED;
import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.HardwareRendererObserver;
import android.os.Handler;
import android.os.Trace;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.FrameMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControl.JankData.JankType;
import android.view.ThreadedRenderer;
import android.view.ViewRootImpl;

import com.android.internal.jank.InteractionJankMonitor.Session;
import com.android.internal.util.FrameworkStatsLog;
@@ -31,68 +46,141 @@ import com.android.internal.util.FrameworkStatsLog;
 * A class that allows the app to get the frame metrics from HardwareRendererObserver.
 * @hide
 */
public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
    private static final String TAG = FrameTracker.class.getSimpleName();
public class FrameTracker extends SurfaceControl.OnJankDataListener
        implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
    private static final String TAG = "FrameTracker";
    private static final boolean DEBUG = false;
    //TODO (163431584): need also consider other refresh rates.
    private static final long JANK_THRESHOLD_NANOS = 1000000000 / 60;
    private static final long UNKNOWN_TIMESTAMP = -1;

    private static final long INVALID_ID = -1;
    public static final int NANOS_IN_MILLISECOND = 1_000_000;

    private final HardwareRendererObserver mObserver;
    private SurfaceControl mSurfaceControl;
    private final int mTraceThresholdMissedFrames;
    private final int mTraceThresholdFrameTimeMillis;
    private final ThreadedRendererWrapper mRendererWrapper;
    private final FrameMetricsWrapper mMetricsWrapper;
    private final SparseArray<JankInfo> mJankInfos = new SparseArray<>();
    private final Session mSession;
    private final ViewRootWrapper mViewRoot;
    private final SurfaceControlWrapper mSurfaceControlWrapper;
    private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback;
    private final Handler mHandler;
    private final ChoreographerWrapper mChoreographer;

    private long mBeginTime = UNKNOWN_TIMESTAMP;
    private long mEndTime = UNKNOWN_TIMESTAMP;
    private boolean mSessionEnd;
    private long mBeginVsyncId = INVALID_ID;
    private long mEndVsyncId = INVALID_ID;
    private boolean mMetricsFinalized;
    private boolean mCancelled = false;
    private int mTotalFramesCount = 0;
    private int mMissedFramesCount = 0;
    private int mSfMissedFramesCount = 0;
    private long mMaxFrameTimeNanos = 0;

    private Session mSession;
    private static class JankInfo {
        long frameVsyncId;
        long totalDurationNanos;
        boolean isFirstFrame;
        boolean hwuiCallbackFired;
        boolean surfaceControlCallbackFired;
        @JankType int jankType;

        static JankInfo createFromHwuiCallback(long frameVsyncId, long totalDurationNanos,
                boolean isFirstFrame) {
            return new JankInfo(frameVsyncId, true, false, JANK_NONE, totalDurationNanos,
                    isFirstFrame);
        }

        static JankInfo createFromSurfaceControlCallback(long frameVsyncId,
                @JankType int jankType) {
            return new JankInfo(frameVsyncId, false, true, jankType, 0, false /* isFirstFrame */);
        }

        private JankInfo(long frameVsyncId, boolean hwuiCallbackFired,
                boolean surfaceControlCallbackFired, @JankType int jankType,
                long totalDurationNanos, boolean isFirstFrame) {
            this.frameVsyncId = frameVsyncId;
            this.hwuiCallbackFired = hwuiCallbackFired;
            this.surfaceControlCallbackFired = surfaceControlCallbackFired;
            this.totalDurationNanos = totalDurationNanos;
            this.jankType = jankType;
            this.isFirstFrame = isFirstFrame;
        }
    }

    public FrameTracker(@NonNull Session session, @NonNull Handler handler,
            @NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics,
            int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis) {
            @NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper,
            @NonNull SurfaceControlWrapper surfaceControlWrapper,
            @NonNull ChoreographerWrapper choreographer,
            @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames,
            int traceThresholdFrameTimeMillis) {
        mSession = session;
        mRendererWrapper = renderer;
        mMetricsWrapper = metrics;
        mViewRoot = viewRootWrapper;
        mChoreographer = choreographer;
        mSurfaceControlWrapper = surfaceControlWrapper;
        mHandler = handler;
        mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler);
        mTraceThresholdMissedFrames = traceThresholdMissedFrames;
        mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;

        // If the surface isn't valid yet, wait until it's created.
        if (viewRootWrapper.getSurfaceControl().isValid()) {
            mSurfaceControl = viewRootWrapper.getSurfaceControl();
        }
        mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
            @Override
            public void surfaceCreated(SurfaceControl.Transaction t) {
                synchronized (FrameTracker.this) {
                    if (mSurfaceControl == null) {
                        mSurfaceControl = viewRootWrapper.getSurfaceControl();
                        if (mBeginVsyncId != INVALID_ID) {
                            mSurfaceControlWrapper.addJankStatsListener(
                                    FrameTracker.this, mSurfaceControl);
                        }
                    }
                }
            }

            @Override
            public void surfaceReplaced(SurfaceControl.Transaction t) {
            }

            @Override
            public void surfaceDestroyed() {

                // Wait a while to give the system a chance for the remaining frames to arrive, then
                // force finish the session.
                mHandler.postDelayed(() -> {
                    synchronized (FrameTracker.this) {
                        if (!mMetricsFinalized) {
                            finish(mJankInfos.size() - 1);
                        }
                    }
                }, 50);
            }
        };
        viewRootWrapper.addSurfaceChangedCallback(mSurfaceChangedCallback);
    }

    /**
     * Begin a trace session of the CUJ.
     */
    public synchronized void begin() {
        long timestamp = System.nanoTime();
        if (DEBUG) {
            Log.d(TAG, "begin: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime
                    + ", end(ns)=" + mEndTime + ", session=" + mSession.getName());
        }
        mBeginTime = timestamp;
        mEndTime = UNKNOWN_TIMESTAMP;
        Trace.beginAsyncSection(mSession.getName(), (int) mBeginTime);
        mBeginVsyncId = mChoreographer.getVsyncId() + 1;
        Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
        mRendererWrapper.addObserver(mObserver);
        if (mSurfaceControl != null) {
            mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
        }
    }

    /**
     * End the trace session of the CUJ.
     */
    public synchronized void end() {
        long timestamp = System.nanoTime();
        if (DEBUG) {
            Log.d(TAG, "end: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime
                    + ", end(ns)=" + mEndTime + ", session=" + mSession.getName());
        mEndVsyncId = mChoreographer.getVsyncId();
        Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
        if (mEndVsyncId == mBeginVsyncId) {
            cancel();
        }
        mEndTime = timestamp;
        Trace.endAsyncSection(mSession.getName(), (int) mBeginTime);
        // We don't remove observer here,
        // will remove it when all the frame metrics in this duration are called back.
        // See onFrameMetricsAvailable for the logic of removing the observer.
@@ -102,14 +190,45 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
     * Cancel the trace session of the CUJ.
     */
    public synchronized void cancel() {
        if (mBeginTime == UNKNOWN_TIMESTAMP || mEndTime != UNKNOWN_TIMESTAMP) return;
        if (DEBUG) {
            Log.d(TAG, "cancel: time(ns)=" + System.nanoTime() + ", begin(ns)=" + mBeginTime
                    + ", end(ns)=" + mEndTime + ", session=" + mSession.getName());
        }
        Trace.endAsyncSection(mSession.getName(), (int) mBeginTime);
        mRendererWrapper.removeObserver(mObserver);
        if (mBeginVsyncId == INVALID_ID || mEndVsyncId != INVALID_ID) return;
        Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
        mCancelled = true;
        removeObservers();
    }

    @Override
    public synchronized void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
        if (mCancelled) {
            return;
        }

        for (SurfaceControl.JankData jankStat : jankData) {
            if (!isInRange(jankStat.frameVsyncId)) {
                continue;
            }
            JankInfo info = findJankInfo(jankStat.frameVsyncId);
            if (info != null) {
                info.surfaceControlCallbackFired = true;
                info.jankType = jankStat.jankType;
            } else {
                mJankInfos.put((int) jankStat.frameVsyncId,
                        JankInfo.createFromSurfaceControlCallback(
                                jankStat.frameVsyncId, jankStat.jankType));
            }
        }
        processJankInfos();
    }

    private @Nullable JankInfo findJankInfo(long frameVsyncId) {
        return mJankInfos.get((int) frameVsyncId);
    }

    private boolean isInRange(long vsyncId) {

        // It's possible that we may miss a callback for the frame with vsyncId == mEndVsyncId.
        // Because of that, we collect all frames even if they happen after the end so we eventually
        // have a frame after the end with both callbacks present.
        return vsyncId >= mBeginVsyncId;
    }

    @Override
@@ -121,32 +240,126 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
        // Since this callback might come a little bit late after the end() call.
        // We should keep tracking the begin / end timestamp.
        // Then compare with vsync timestamp to check if the frame is in the duration of the CUJ.
        long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
        boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
        long frameVsyncId = mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];

        long vsyncTimestamp = mMetricsWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
        // Discard the frame metrics which is not in the trace session.
        if (vsyncTimestamp < mBeginTime) return;
        if (!isInRange(frameVsyncId)) {
            return;
        }
        JankInfo info = findJankInfo(frameVsyncId);
        if (info != null) {
            info.hwuiCallbackFired = true;
            info.totalDurationNanos = totalDurationNanos;
            info.isFirstFrame = isFirstFrame;
        } else {
            mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
                    frameVsyncId, totalDurationNanos, isFirstFrame));
        }
        processJankInfos();
    }

    /**
     * Finds the first index in {@link #mJankInfos} which happened on or after {@link #mEndVsyncId},
     * or -1 if the session hasn't ended yet.
     */
    private int getIndexOnOrAfterEnd() {
        if (mEndVsyncId == INVALID_ID || mMetricsFinalized) {
            return -1;
        }
        JankInfo last = mJankInfos.size() == 0 ? null : mJankInfos.valueAt(mJankInfos.size() - 1);
        if (last == null) {
            return -1;
        }
        if (last.frameVsyncId < mEndVsyncId) {
            return -1;
        }

        int lastIndex = -1;
        for (int i = mJankInfos.size() - 1; i >= 0; i--) {
            JankInfo info = mJankInfos.valueAt(i);
            if (info.frameVsyncId >= mEndVsyncId) {
                if (info.hwuiCallbackFired && info.surfaceControlCallbackFired) {
                    lastIndex = i;
                }
            } else {
                break;
            }
        }
        return lastIndex;
    }

    private void processJankInfos() {
        int indexOnOrAfterEnd = getIndexOnOrAfterEnd();
        if (indexOnOrAfterEnd == -1) {
            return;
        }
        finish(indexOnOrAfterEnd);
    }

    private void finish(int indexOnOrAfterEnd) {

        mMetricsFinalized = true;

        // We stop getting callback when the vsync is later than the end calls.
        if (mEndTime != UNKNOWN_TIMESTAMP && vsyncTimestamp > mEndTime && !mSessionEnd) {
            mSessionEnd = true;
        // The tracing has been ended, remove the observer, see if need to trigger perfetto.
            mRendererWrapper.removeObserver(mObserver);
        removeObservers();

        int totalFramesCount = 0;
        long maxFrameTimeNanos = 0;
        int missedAppFramesCount = 0;
        int missedSfFramesCounts = 0;

        for (int i = 0; i <= indexOnOrAfterEnd; i++) {
            JankInfo info = mJankInfos.valueAt(i);
            if (info.isFirstFrame) {
                continue;
            }
            if (info.surfaceControlCallbackFired) {
                totalFramesCount++;

                // Only count missed frames if it's not stuffed.
                if ((info.jankType & PREDICTION_ERROR) != 0
                        || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0
                                && (info.jankType & BUFFER_STUFFING) == 0)) {
                    Log.w(TAG, "Missed App frame:" + info.jankType);
                    missedAppFramesCount++;
                }
                if ((info.jankType & DISPLAY_HAL) != 0
                        || (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
                        || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
                        || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0) {
                    Log.w(TAG, "Missed SF frame:" + info.jankType);
                    missedSfFramesCounts++;
                }
                // TODO (b/174755489): Early latch currently gets fired way too often, so we have
                // to ignore it for now.
                if (!info.hwuiCallbackFired) {
                    Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId);
                }
            }
            if (info.hwuiCallbackFired) {
                maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos);
                if (!info.surfaceControlCallbackFired) {
                    Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId);
                }
            }
        }

        // Log the frame stats as counters to make them easily accessible in traces.
            Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#sfMissedFrames",
                    mSfMissedFramesCount);
            Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames",
                    mMissedFramesCount);
        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames",
                missedAppFramesCount);
        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames",
                missedSfFramesCounts);
        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
                    mTotalFramesCount);
                totalFramesCount);
        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
                    (int) (mMaxFrameTimeNanos / NANOS_IN_MILLISECOND));
                (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND));

        // Trigger perfetto if necessary.
        boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
                    && (mMissedFramesCount + mSfMissedFramesCount) >= mTraceThresholdMissedFrames;
                && missedAppFramesCount + missedSfFramesCounts >= mTraceThresholdMissedFrames;
        boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1
                    && mMaxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
                && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
        if (overMissedFramesThreshold || overFrameTimeThreshold) {
            triggerPerfetto();
        }
@@ -154,27 +367,25 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
            FrameworkStatsLog.write(
                    FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
                    mSession.getStatsdInteractionType(),
                        mTotalFramesCount,
                        mMissedFramesCount,
                        mMaxFrameTimeNanos,
                        mSfMissedFramesCount);
                    totalFramesCount,
                    missedAppFramesCount + missedSfFramesCounts,
                    maxFrameTimeNanos,
                    missedSfFramesCounts);
        }
            return;
        if (DEBUG) {
            Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName()
                    + " totalFrames=" + totalFramesCount
                    + " missedAppFrames=" + missedAppFramesCount
                    + " missedSfFrames=" + missedSfFramesCounts
                    + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND);
        }

        long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
        boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
        boolean isJankyFrame = !isFirstFrame && totalDurationNanos > JANK_THRESHOLD_NANOS;

        mTotalFramesCount += 1;

        if (!isFirstFrame) {
            mMaxFrameTimeNanos = Math.max(totalDurationNanos, mMaxFrameTimeNanos);
    }

        // TODO(b/171049584): Also update mSfMissedFramesCount once the data is available.
        if (isJankyFrame) {
            mMissedFramesCount += 1;
    private void removeObservers() {
        mRendererWrapper.removeObserver(mObserver);
        mSurfaceControlWrapper.removeJankStatsListener(this);
        if (mSurfaceChangedCallback != null) {
            mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
        }
    }

@@ -189,7 +400,7 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
     * A wrapper class that we can spy FrameMetrics (a final class) in unit tests.
     */
    public static class FrameMetricsWrapper {
        private FrameMetrics mFrameMetrics;
        private final FrameMetrics mFrameMetrics;

        public FrameMetricsWrapper() {
            mFrameMetrics = new FrameMetrics();
@@ -217,7 +428,7 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
     * A wrapper class that we can spy ThreadedRenderer (a final class) in unit tests.
     */
    public static class ThreadedRendererWrapper {
        private ThreadedRenderer mRenderer;
        private final ThreadedRenderer mRenderer;

        public ThreadedRendererWrapper(ThreadedRenderer renderer) {
            mRenderer = renderer;
@@ -239,4 +450,49 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
            mRenderer.removeObserver(observer);
        }
    }

    public static class ViewRootWrapper {
        private final ViewRootImpl mViewRoot;

        public ViewRootWrapper(ViewRootImpl viewRoot) {
            mViewRoot = viewRoot;
        }

        public void addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
            mViewRoot.addSurfaceChangedCallback(callback);
        }

        public void removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
            mViewRoot.removeSurfaceChangedCallback(callback);
        }

        public SurfaceControl getSurfaceControl() {
            return mViewRoot.getSurfaceControl();
        }
    }

    public static class SurfaceControlWrapper {

        public void addJankStatsListener(SurfaceControl.OnJankDataListener listener,
                SurfaceControl surfaceControl) {
            SurfaceControl.addJankDataListener(listener, surfaceControl);
        }

        public void removeJankStatsListener(SurfaceControl.OnJankDataListener listener) {
            SurfaceControl.removeJankDataListener(listener);
        }
    }

    public static class ChoreographerWrapper {

        private final Choreographer mChoreographer;

        public ChoreographerWrapper(Choreographer choreographer) {
            mChoreographer = choreographer;
        }

        public long getVsyncId() {
            return mChoreographer.getVsyncId();
        }
    }
}
Loading