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