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