• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.jank;
18 
19 import static android.view.SurfaceControl.JankData.DISPLAY_HAL;
20 import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
21 import static android.view.SurfaceControl.JankData.JANK_NONE;
22 import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
23 import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED;
24 import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
25 import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
26 
27 import static com.android.internal.jank.InteractionJankMonitor.ACTION_METRICS_LOGGED;
28 import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_BEGIN;
29 import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CANCEL;
30 import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END;
31 
32 import android.annotation.IntDef;
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 import android.graphics.HardwareRendererObserver;
36 import android.os.Handler;
37 import android.os.Trace;
38 import android.util.Log;
39 import android.util.SparseArray;
40 import android.view.Choreographer;
41 import android.view.FrameMetrics;
42 import android.view.SurfaceControl;
43 import android.view.SurfaceControl.JankData.JankType;
44 import android.view.ThreadedRenderer;
45 import android.view.ViewRootImpl;
46 
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.jank.InteractionJankMonitor.Session;
49 import com.android.internal.util.FrameworkStatsLog;
50 
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 
54 /**
55  * A class that allows the app to get the frame metrics from HardwareRendererObserver.
56  * @hide
57  */
58 public class FrameTracker extends SurfaceControl.OnJankDataListener
59         implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
60     private static final String TAG = "FrameTracker";
61     private static final boolean DEBUG = false;
62 
63     private static final long INVALID_ID = -1;
64     public static final int NANOS_IN_MILLISECOND = 1_000_000;
65 
66     static final int REASON_END_UNKNOWN = -1;
67     static final int REASON_END_NORMAL = 0;
68     static final int REASON_END_SURFACE_DESTROYED = 1;
69     static final int REASON_CANCEL_NORMAL = 16;
70     static final int REASON_CANCEL_NOT_BEGUN = 17;
71     static final int REASON_CANCEL_SAME_VSYNC = 18;
72 
73     /** @hide */
74     @IntDef({
75             REASON_END_UNKNOWN,
76             REASON_END_NORMAL,
77             REASON_END_SURFACE_DESTROYED,
78             REASON_CANCEL_NORMAL,
79             REASON_CANCEL_NOT_BEGUN,
80             REASON_CANCEL_SAME_VSYNC,
81     })
82     @Retention(RetentionPolicy.SOURCE)
83     public @interface Reasons {
84     }
85 
86     private final HardwareRendererObserver mObserver;
87     private SurfaceControl mSurfaceControl;
88     private final int mTraceThresholdMissedFrames;
89     private final int mTraceThresholdFrameTimeMillis;
90     private final ThreadedRendererWrapper mRendererWrapper;
91     private final FrameMetricsWrapper mMetricsWrapper;
92     private final SparseArray<JankInfo> mJankInfos = new SparseArray<>();
93     private final Session mSession;
94     private final ViewRootWrapper mViewRoot;
95     private final SurfaceControlWrapper mSurfaceControlWrapper;
96     private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback;
97     private final Handler mHandler;
98     private final ChoreographerWrapper mChoreographer;
99 
100     private long mBeginVsyncId = INVALID_ID;
101     private long mEndVsyncId = INVALID_ID;
102     private boolean mMetricsFinalized;
103     private boolean mCancelled = false;
104     private FrameTrackerListener mListener;
105     private boolean mTracingStarted = false;
106 
107     private static class JankInfo {
108         long frameVsyncId;
109         long totalDurationNanos;
110         boolean isFirstFrame;
111         boolean hwuiCallbackFired;
112         boolean surfaceControlCallbackFired;
113         @JankType int jankType;
114 
createFromHwuiCallback(long frameVsyncId, long totalDurationNanos, boolean isFirstFrame)115         static JankInfo createFromHwuiCallback(long frameVsyncId, long totalDurationNanos,
116                 boolean isFirstFrame) {
117             return new JankInfo(frameVsyncId, true, false, JANK_NONE, totalDurationNanos,
118                     isFirstFrame);
119         }
120 
createFromSurfaceControlCallback(long frameVsyncId, @JankType int jankType)121         static JankInfo createFromSurfaceControlCallback(long frameVsyncId,
122                 @JankType int jankType) {
123             return new JankInfo(frameVsyncId, false, true, jankType, 0, false /* isFirstFrame */);
124         }
125 
JankInfo(long frameVsyncId, boolean hwuiCallbackFired, boolean surfaceControlCallbackFired, @JankType int jankType, long totalDurationNanos, boolean isFirstFrame)126         private JankInfo(long frameVsyncId, boolean hwuiCallbackFired,
127                 boolean surfaceControlCallbackFired, @JankType int jankType,
128                 long totalDurationNanos, boolean isFirstFrame) {
129             this.frameVsyncId = frameVsyncId;
130             this.hwuiCallbackFired = hwuiCallbackFired;
131             this.surfaceControlCallbackFired = surfaceControlCallbackFired;
132             this.totalDurationNanos = totalDurationNanos;
133             this.jankType = jankType;
134             this.isFirstFrame = isFirstFrame;
135         }
136     }
137 
FrameTracker(@onNull Session session, @NonNull Handler handler, @NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper, @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener)138     public FrameTracker(@NonNull Session session, @NonNull Handler handler,
139             @NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper,
140             @NonNull SurfaceControlWrapper surfaceControlWrapper,
141             @NonNull ChoreographerWrapper choreographer,
142             @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames,
143             int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) {
144         mSession = session;
145         mRendererWrapper = renderer;
146         mMetricsWrapper = metrics;
147         mViewRoot = viewRootWrapper;
148         mChoreographer = choreographer;
149         mSurfaceControlWrapper = surfaceControlWrapper;
150         mHandler = handler;
151         mObserver = new HardwareRendererObserver(
152                 this, mMetricsWrapper.getTiming(), handler, false /*waitForPresentTime*/);
153         mTraceThresholdMissedFrames = traceThresholdMissedFrames;
154         mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
155         mListener = listener;
156 
157         // If the surface isn't valid yet, wait until it's created.
158         if (viewRootWrapper.getSurfaceControl().isValid()) {
159             mSurfaceControl = viewRootWrapper.getSurfaceControl();
160         }
161         mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
162             @Override
163             public void surfaceCreated(SurfaceControl.Transaction t) {
164                 synchronized (FrameTracker.this) {
165                     if (mSurfaceControl == null) {
166                         mSurfaceControl = viewRootWrapper.getSurfaceControl();
167                         if (mBeginVsyncId != INVALID_ID) {
168                             mSurfaceControlWrapper.addJankStatsListener(
169                                     FrameTracker.this, mSurfaceControl);
170                             postTraceStartMarker();
171                         }
172                     }
173                 }
174             }
175 
176             @Override
177             public void surfaceReplaced(SurfaceControl.Transaction t) {
178             }
179 
180             @Override
181             public void surfaceDestroyed() {
182 
183                 // Wait a while to give the system a chance for the remaining frames to arrive, then
184                 // force finish the session.
185                 mHandler.postDelayed(() -> {
186                     synchronized (FrameTracker.this) {
187                         if (DEBUG) {
188                             Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
189                                     + ", finalized=" + mMetricsFinalized
190                                     + ", info=" + mJankInfos.size()
191                                     + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId);
192                         }
193                         if (!mMetricsFinalized) {
194                             end(REASON_END_SURFACE_DESTROYED);
195                             finish(mJankInfos.size() - 1);
196                         }
197                     }
198                 }, 50);
199             }
200         };
201 
202         // This callback has a reference to FrameTracker, remember to remove it to avoid leakage.
203         viewRootWrapper.addSurfaceChangedCallback(mSurfaceChangedCallback);
204     }
205 
206     /**
207      * Begin a trace session of the CUJ.
208      */
begin()209     public synchronized void begin() {
210         mBeginVsyncId = mChoreographer.getVsyncId() + 1;
211         if (mSurfaceControl != null) {
212             postTraceStartMarker();
213         }
214         mRendererWrapper.addObserver(mObserver);
215         if (DEBUG) {
216             Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId);
217         }
218         if (mSurfaceControl != null) {
219             mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
220         }
221         if (mListener != null) {
222             mListener.onCujEvents(mSession, ACTION_SESSION_BEGIN);
223         }
224     }
225 
226     /**
227      * Start trace section at appropriate time.
228      */
229     @VisibleForTesting
postTraceStartMarker()230     public void postTraceStartMarker() {
231         mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, () -> {
232             synchronized (FrameTracker.this) {
233                 if (mCancelled || mEndVsyncId != INVALID_ID) {
234                     return;
235                 }
236                 mTracingStarted = true;
237                 Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
238             }
239         }, null);
240     }
241 
242     /**
243      * End the trace session of the CUJ.
244      */
end(@easons int reason)245     public synchronized void end(@Reasons int reason) {
246         if (mEndVsyncId != INVALID_ID) return;
247         mEndVsyncId = mChoreographer.getVsyncId();
248 
249         // Cancel the session if:
250         // 1. The session begins and ends at the same vsync id.
251         // 2. The session never begun.
252         if (mBeginVsyncId == INVALID_ID) {
253             cancel(REASON_CANCEL_NOT_BEGUN);
254         } else if (mEndVsyncId <= mBeginVsyncId) {
255             cancel(REASON_CANCEL_SAME_VSYNC);
256         } else {
257             if (DEBUG) {
258                 Log.d(TAG, "end: " + mSession.getName()
259                         + ", end=" + mEndVsyncId + ", reason=" + reason);
260             }
261             Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
262             mSession.setReason(reason);
263             if (mListener != null) {
264                 mListener.onCujEvents(mSession, ACTION_SESSION_END);
265             }
266         }
267         // We don't remove observer here,
268         // will remove it when all the frame metrics in this duration are called back.
269         // See onFrameMetricsAvailable for the logic of removing the observer.
270     }
271 
272     /**
273      * Cancel the trace session of the CUJ.
274      */
cancel(@easons int reason)275     public synchronized void cancel(@Reasons int reason) {
276         // We don't need to end the trace section if it never begun.
277         if (mTracingStarted) {
278             Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
279         }
280         mCancelled = true;
281 
282         // Always remove the observers in cancel call to avoid leakage.
283         removeObservers();
284 
285         if (DEBUG) {
286             Log.d(TAG, "cancel: " + mSession.getName()
287                     + ", begin=" + mBeginVsyncId + ", end=" + mEndVsyncId + ", reason=" + reason);
288         }
289 
290         mSession.setReason(reason);
291         // Notify the listener the session has been cancelled.
292         // We don't notify the listeners if the session never begun.
293         if (mListener != null) {
294             mListener.onCujEvents(mSession, ACTION_SESSION_CANCEL);
295         }
296     }
297 
298     @Override
onJankDataAvailable(SurfaceControl.JankData[] jankData)299     public synchronized void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
300         if (mCancelled) {
301             return;
302         }
303 
304         for (SurfaceControl.JankData jankStat : jankData) {
305             if (!isInRange(jankStat.frameVsyncId)) {
306                 continue;
307             }
308             JankInfo info = findJankInfo(jankStat.frameVsyncId);
309             if (info != null) {
310                 info.surfaceControlCallbackFired = true;
311                 info.jankType = jankStat.jankType;
312             } else {
313                 mJankInfos.put((int) jankStat.frameVsyncId,
314                         JankInfo.createFromSurfaceControlCallback(
315                                 jankStat.frameVsyncId, jankStat.jankType));
316             }
317         }
318         processJankInfos();
319     }
320 
findJankInfo(long frameVsyncId)321     private @Nullable JankInfo findJankInfo(long frameVsyncId) {
322         return mJankInfos.get((int) frameVsyncId);
323     }
324 
isInRange(long vsyncId)325     private boolean isInRange(long vsyncId) {
326         // It's possible that we may miss a callback for the frame with vsyncId == mEndVsyncId.
327         // Because of that, we collect all frames even if they happen after the end so we eventually
328         // have a frame after the end with both callbacks present.
329         return vsyncId >= mBeginVsyncId;
330     }
331 
332     @Override
onFrameMetricsAvailable(int dropCountSinceLastInvocation)333     public synchronized void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
334         if (mCancelled) {
335             return;
336         }
337 
338         // Since this callback might come a little bit late after the end() call.
339         // We should keep tracking the begin / end timestamp.
340         // Then compare with vsync timestamp to check if the frame is in the duration of the CUJ.
341         long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
342         boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
343         long frameVsyncId = mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
344 
345         if (!isInRange(frameVsyncId)) {
346             return;
347         }
348         JankInfo info = findJankInfo(frameVsyncId);
349         if (info != null) {
350             info.hwuiCallbackFired = true;
351             info.totalDurationNanos = totalDurationNanos;
352             info.isFirstFrame = isFirstFrame;
353         } else {
354             mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
355                     frameVsyncId, totalDurationNanos, isFirstFrame));
356         }
357         processJankInfos();
358     }
359 
360     /**
361      * Finds the first index in {@link #mJankInfos} which happened on or after {@link #mEndVsyncId},
362      * or -1 if the session hasn't ended yet.
363      */
getIndexOnOrAfterEnd()364     private int getIndexOnOrAfterEnd() {
365         if (mEndVsyncId == INVALID_ID || mMetricsFinalized) {
366             return -1;
367         }
368         JankInfo last = mJankInfos.size() == 0 ? null : mJankInfos.valueAt(mJankInfos.size() - 1);
369         if (last == null) {
370             return -1;
371         }
372         if (last.frameVsyncId < mEndVsyncId) {
373             return -1;
374         }
375 
376         int lastIndex = -1;
377         for (int i = mJankInfos.size() - 1; i >= 0; i--) {
378             JankInfo info = mJankInfos.valueAt(i);
379             if (info.frameVsyncId >= mEndVsyncId) {
380                 if (info.hwuiCallbackFired && info.surfaceControlCallbackFired) {
381                     lastIndex = i;
382                 }
383             } else {
384                 break;
385             }
386         }
387         return lastIndex;
388     }
389 
processJankInfos()390     private void processJankInfos() {
391         int indexOnOrAfterEnd = getIndexOnOrAfterEnd();
392         if (indexOnOrAfterEnd == -1) {
393             return;
394         }
395         finish(indexOnOrAfterEnd);
396     }
397 
finish(int indexOnOrAfterEnd)398     private void finish(int indexOnOrAfterEnd) {
399 
400         mMetricsFinalized = true;
401 
402         // The tracing has been ended, remove the observer, see if need to trigger perfetto.
403         removeObservers();
404 
405         int totalFramesCount = 0;
406         long maxFrameTimeNanos = 0;
407         int missedFramesCount = 0;
408         int missedAppFramesCount = 0;
409         int missedSfFramesCount = 0;
410 
411         for (int i = 0; i <= indexOnOrAfterEnd; i++) {
412             JankInfo info = mJankInfos.valueAt(i);
413             if (info.isFirstFrame) {
414                 continue;
415             }
416             if (info.surfaceControlCallbackFired) {
417                 totalFramesCount++;
418                 boolean missedFrame = false;
419                 if ((info.jankType & PREDICTION_ERROR) != 0
420                         || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0)) {
421                     Log.w(TAG, "Missed App frame:" + info.jankType);
422                     missedAppFramesCount++;
423                     missedFrame = true;
424                 }
425                 if ((info.jankType & DISPLAY_HAL) != 0
426                         || (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
427                         || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
428                         || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0) {
429                     Log.w(TAG, "Missed SF frame:" + info.jankType);
430                     missedSfFramesCount++;
431                     missedFrame = true;
432                 }
433                 if (missedFrame) {
434                     missedFramesCount++;
435                 }
436                 // TODO (b/174755489): Early latch currently gets fired way too often, so we have
437                 // to ignore it for now.
438                 if (!info.hwuiCallbackFired) {
439                     Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId);
440                 }
441             }
442             if (info.hwuiCallbackFired) {
443                 maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos);
444                 if (!info.surfaceControlCallbackFired) {
445                     Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId);
446                 }
447             }
448         }
449 
450         // Log the frame stats as counters to make them easily accessible in traces.
451         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames",
452                 missedFramesCount);
453         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames",
454                 missedAppFramesCount);
455         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames",
456                 missedSfFramesCount);
457         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
458                 totalFramesCount);
459         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
460                 (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND));
461 
462         // Trigger perfetto if necessary.
463         boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
464                 && missedFramesCount >= mTraceThresholdMissedFrames;
465         boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1
466                 && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
467         if (overMissedFramesThreshold || overFrameTimeThreshold) {
468             triggerPerfetto();
469         }
470         if (mSession.logToStatsd()) {
471             FrameworkStatsLog.write(
472                     FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
473                     mSession.getStatsdInteractionType(),
474                     totalFramesCount,
475                     missedFramesCount,
476                     maxFrameTimeNanos,
477                     missedSfFramesCount,
478                     missedAppFramesCount);
479             if (mListener != null) {
480                 mListener.onCujEvents(mSession, ACTION_METRICS_LOGGED);
481             }
482         }
483         if (DEBUG) {
484             Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName()
485                     + " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
486                     + " totalFrames=" + totalFramesCount
487                     + " missedAppFrames=" + missedAppFramesCount
488                     + " missedSfFrames=" + missedSfFramesCount
489                     + " missedFrames=" + missedFramesCount
490                     + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND);
491         }
492     }
493 
494     /**
495      * Remove all the registered listeners, observers and callbacks.
496      */
497     @VisibleForTesting
removeObservers()498     public void removeObservers() {
499         mRendererWrapper.removeObserver(mObserver);
500         mSurfaceControlWrapper.removeJankStatsListener(this);
501         if (mSurfaceChangedCallback != null) {
502             mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
503         }
504     }
505 
506     /**
507      * Trigger the prefetto daemon.
508      */
triggerPerfetto()509     public void triggerPerfetto() {
510         InteractionJankMonitor.getInstance().trigger(mSession);
511     }
512 
513     /**
514      * A wrapper class that we can spy FrameMetrics (a final class) in unit tests.
515      */
516     public static class FrameMetricsWrapper {
517         private final FrameMetrics mFrameMetrics;
518 
FrameMetricsWrapper()519         public FrameMetricsWrapper() {
520             mFrameMetrics = new FrameMetrics();
521         }
522 
523         /**
524          * Wrapper method.
525          * @return timing data of the metrics
526          */
getTiming()527         public long[] getTiming() {
528             return mFrameMetrics.mTimingData;
529         }
530 
531         /**
532          * Wrapper method.
533          * @param index specific index of the timing data
534          * @return the timing data of the specified index
535          */
getMetric(int index)536         public long getMetric(int index) {
537             return mFrameMetrics.getMetric(index);
538         }
539     }
540 
541     /**
542      * A wrapper class that we can spy ThreadedRenderer (a final class) in unit tests.
543      */
544     public static class ThreadedRendererWrapper {
545         private final ThreadedRenderer mRenderer;
546 
ThreadedRendererWrapper(ThreadedRenderer renderer)547         public ThreadedRendererWrapper(ThreadedRenderer renderer) {
548             mRenderer = renderer;
549         }
550 
551         /**
552          * Wrapper method.
553          * @param observer observer
554          */
addObserver(HardwareRendererObserver observer)555         public void addObserver(HardwareRendererObserver observer) {
556             mRenderer.addObserver(observer);
557         }
558 
559         /**
560          * Wrapper method.
561          * @param observer observer
562          */
removeObserver(HardwareRendererObserver observer)563         public void removeObserver(HardwareRendererObserver observer) {
564             mRenderer.removeObserver(observer);
565         }
566     }
567 
568     public static class ViewRootWrapper {
569         private final ViewRootImpl mViewRoot;
570 
ViewRootWrapper(ViewRootImpl viewRoot)571         public ViewRootWrapper(ViewRootImpl viewRoot) {
572             mViewRoot = viewRoot;
573         }
574 
addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback)575         public void addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
576             mViewRoot.addSurfaceChangedCallback(callback);
577         }
578 
removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback)579         public void removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
580             mViewRoot.removeSurfaceChangedCallback(callback);
581         }
582 
getSurfaceControl()583         public SurfaceControl getSurfaceControl() {
584             return mViewRoot.getSurfaceControl();
585         }
586     }
587 
588     public static class SurfaceControlWrapper {
589 
addJankStatsListener(SurfaceControl.OnJankDataListener listener, SurfaceControl surfaceControl)590         public void addJankStatsListener(SurfaceControl.OnJankDataListener listener,
591                 SurfaceControl surfaceControl) {
592             SurfaceControl.addJankDataListener(listener, surfaceControl);
593         }
594 
removeJankStatsListener(SurfaceControl.OnJankDataListener listener)595         public void removeJankStatsListener(SurfaceControl.OnJankDataListener listener) {
596             SurfaceControl.removeJankDataListener(listener);
597         }
598     }
599 
600     public static class ChoreographerWrapper {
601 
602         private final Choreographer mChoreographer;
603 
ChoreographerWrapper(Choreographer choreographer)604         public ChoreographerWrapper(Choreographer choreographer) {
605             mChoreographer = choreographer;
606         }
607 
getVsyncId()608         public long getVsyncId() {
609             return mChoreographer.getVsyncId();
610         }
611     }
612 
613     /**
614      * A listener that notifies cuj events.
615      */
616     public interface FrameTrackerListener {
617         /**
618          * Notify that the CUJ session was created.
619          *
620          * @param session the CUJ session
621          * @param action the specific action
622          */
onCujEvents(Session session, String action)623         void onCujEvents(Session session, String action);
624     }
625 }
626