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.JANK_APP_DEADLINE_MISSED;
20 import static android.view.SurfaceControl.JankData.JANK_NONE;
21 import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
22 
23 import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
24 import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
25 import static com.android.internal.jank.Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
26 import static com.android.internal.jank.Cuj.CUJ_WALLPAPER_TRANSITION;
27 import static com.android.internal.util.FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED;
28 
29 import static com.google.common.truth.Truth.assertThat;
30 
31 import static org.mockito.ArgumentMatchers.any;
32 import static org.mockito.ArgumentMatchers.eq;
33 import static org.mockito.Mockito.doNothing;
34 import static org.mockito.Mockito.doReturn;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.never;
37 import static org.mockito.Mockito.only;
38 import static org.mockito.Mockito.spy;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41 
42 import android.os.Handler;
43 import android.view.FrameMetrics;
44 import android.view.SurfaceControl;
45 import android.view.SurfaceControl.JankData;
46 import android.view.SurfaceControl.JankData.JankType;
47 import android.view.SurfaceControl.OnJankDataListener;
48 import android.view.View;
49 import android.view.ViewAttachTestActivity;
50 
51 import androidx.test.ext.junit.rules.ActivityScenarioRule;
52 import androidx.test.filters.SmallTest;
53 
54 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
55 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
56 import com.android.internal.jank.FrameTracker.StatsLogWrapper;
57 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
58 import com.android.internal.jank.InteractionJankMonitor.Configuration;
59 
60 import org.junit.Before;
61 import org.junit.Rule;
62 import org.junit.Test;
63 import org.mockito.ArgumentCaptor;
64 import org.mockito.Mockito;
65 
66 import java.util.concurrent.TimeUnit;
67 
68 @SmallTest
69 public class FrameTrackerTest {
70     private static final String SESSION_NAME = "SessionName";
71     private static final long FRAME_TIME_60Hz = (long) 1e9 / 60;
72 
73     private ViewAttachTestActivity mActivity;
74 
75     @Rule
76     public ActivityScenarioRule<ViewAttachTestActivity> mRule =
77             new ActivityScenarioRule<>(ViewAttachTestActivity.class);
78 
79     private ThreadedRendererWrapper mRenderer;
80     private FrameMetricsWrapper mWrapper;
81     private SurfaceControlWrapper mSurfaceControlWrapper;
82     private ViewRootWrapper mViewRootWrapper;
83     private ChoreographerWrapper mChoreographer;
84     private StatsLogWrapper mStatsLog;
85     private ArgumentCaptor<OnJankDataListener> mListenerCapture;
86     private SurfaceControl mSurfaceControl;
87     private FrameTracker.FrameTrackerListener mTrackerListener;
88     private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
89 
90     @Before
setup()91     public void setup() {
92         // Prepare an activity for getting ThreadedRenderer later.
93         mRule.getScenario().onActivity(activity -> mActivity = activity);
94         View view = mActivity.getWindow().getDecorView();
95         assertThat(view.isAttachedToWindow()).isTrue();
96 
97         mWrapper = Mockito.spy(new FrameMetricsWrapper());
98         mRenderer = Mockito.spy(new ThreadedRendererWrapper(view.getThreadedRenderer()));
99         doNothing().when(mRenderer).addObserver(any());
100         doNothing().when(mRenderer).removeObserver(any());
101 
102         mSurfaceControl = new SurfaceControl.Builder().setName("Surface").build();
103         mViewRootWrapper = mock(ViewRootWrapper.class);
104         when(mViewRootWrapper.getSurfaceControl()).thenReturn(mSurfaceControl);
105         doNothing().when(mViewRootWrapper).addSurfaceChangedCallback(any());
106         doNothing().when(mViewRootWrapper).removeSurfaceChangedCallback(any());
107         mSurfaceControlWrapper = mock(SurfaceControlWrapper.class);
108 
109         mListenerCapture = ArgumentCaptor.forClass(OnJankDataListener.class);
110         doNothing().when(mSurfaceControlWrapper).addJankStatsListener(
111                 mListenerCapture.capture(), any());
112         doNothing().when(mSurfaceControlWrapper).removeJankStatsListener(
113                 mListenerCapture.capture());
114 
115         mChoreographer = mock(ChoreographerWrapper.class);
116         mStatsLog = mock(StatsLogWrapper.class);
117         mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
118         mTrackerListener = mock(FrameTracker.FrameTrackerListener.class);
119     }
120 
spyFrameTracker(boolean surfaceOnly)121     private FrameTracker spyFrameTracker(boolean surfaceOnly) {
122         Handler handler = mActivity.getMainThreadHandler();
123         Configuration config = mock(Configuration.class);
124         when(config.getSessionName()).thenReturn(SESSION_NAME);
125         when(config.isSurfaceOnly()).thenReturn(surfaceOnly);
126         when(config.getSurfaceControl()).thenReturn(mSurfaceControl);
127         when(config.shouldDeferMonitor()).thenReturn(true);
128         when(config.getDisplayId()).thenReturn(42);
129         View view = mActivity.getWindow().getDecorView();
130         Handler spyHandler = spy(new Handler(handler.getLooper()));
131         when(config.getView()).thenReturn(surfaceOnly ? null : view);
132         when(config.getHandler()).thenReturn(spyHandler);
133         when(config.logToStatsd()).thenReturn(true);
134         when(config.getStatsdInteractionType()).thenReturn(surfaceOnly
135                 ? Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)
136                 : Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE));
137         FrameTracker frameTracker = Mockito.spy(
138                 new FrameTracker(config, mRenderer, mViewRootWrapper,
139                         mSurfaceControlWrapper, mChoreographer, mWrapper, mStatsLog,
140                         /* traceThresholdMissedFrames= */ 1,
141                         /* traceThresholdFrameTimeMillis= */ -1,
142                         mTrackerListener));
143         doNothing().when(frameTracker).postTraceStartMarker(mRunnableArgumentCaptor.capture());
144         return frameTracker;
145     }
146 
147     @Test
testOnlyFirstWindowFrameOverThreshold()148     public void testOnlyFirstWindowFrameOverThreshold() {
149         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
150 
151         // Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP
152         when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP))
153                 .then(unusedInvocation -> System.nanoTime());
154 
155         when(mChoreographer.getVsyncId()).thenReturn(100L);
156         tracker.begin();
157         mRunnableArgumentCaptor.getValue().run();
158         verify(mRenderer, only()).addObserver(any());
159 
160         // send first frame with a long duration - should not be taken into account
161         sendFirstWindowFrame(tracker, 100, JANK_APP_DEADLINE_MISSED, 100L);
162 
163         // send another frame with a short duration - should not be considered janky
164         sendFrame(tracker, 5, JANK_NONE, 101L);
165 
166         // end the trace session, the last janky frame is after the end() so is discarded.
167         when(mChoreographer.getVsyncId()).thenReturn(102L);
168         tracker.end(FrameTracker.REASON_END_NORMAL);
169         sendFrame(tracker, 5, JANK_NONE, 102L);
170         sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
171 
172         verify(tracker).removeObservers();
173         verify(mTrackerListener, never()).triggerPerfetto(any());
174         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
175                 eq(42), /* displayId */
176                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
177                 eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
178                 eq(2L) /* totalFrames */,
179                 eq(0L) /* missedFrames */,
180                 eq(5000000L) /* maxFrameTimeNanos */,
181                 eq(0L) /* missedSfFramesCount */,
182                 eq(0L) /* missedAppFramesCount */,
183                 eq(0L) /* maxSuccessiveMissedFramesCount */);
184     }
185 
186     @Test
testSfJank()187     public void testSfJank() {
188         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
189 
190         when(mChoreographer.getVsyncId()).thenReturn(100L);
191         tracker.begin();
192         mRunnableArgumentCaptor.getValue().run();
193         verify(mRenderer, only()).addObserver(any());
194 
195         // send first frame - not janky
196         sendFrame(tracker, 4, JANK_NONE, 100L);
197 
198         // send another frame - should be considered janky
199         sendFrame(tracker, 40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
200 
201         // end the trace session
202         when(mChoreographer.getVsyncId()).thenReturn(102L);
203         tracker.end(FrameTracker.REASON_END_NORMAL);
204         sendFrame(tracker, 4, JANK_NONE, 102L);
205 
206         verify(tracker).removeObservers();
207 
208         // We detected a janky frame - trigger Perfetto
209         verify(mTrackerListener).triggerPerfetto(any());
210 
211         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
212                 eq(42), /* displayId */
213                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
214                 eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
215                 eq(2L) /* totalFrames */,
216                 eq(1L) /* missedFrames */,
217                 eq(40000000L) /* maxFrameTimeNanos */,
218                 eq(1L) /* missedSfFramesCount */,
219                 eq(0L) /* missedAppFramesCount */,
220                 eq(1L) /* maxSuccessiveMissedFramesCount */);
221     }
222 
223     @Test
testFirstFrameJankyNoTrigger()224     public void testFirstFrameJankyNoTrigger() {
225         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
226 
227         when(mChoreographer.getVsyncId()).thenReturn(100L);
228         tracker.begin();
229         mRunnableArgumentCaptor.getValue().run();
230         verify(mRenderer, only()).addObserver(any());
231 
232         // send first frame - janky
233         sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 100L);
234 
235         // send another frame - not jank
236         sendFrame(tracker, 4, JANK_NONE, 101L);
237 
238         // end the trace session
239         when(mChoreographer.getVsyncId()).thenReturn(102L);
240         tracker.end(FrameTracker.REASON_END_NORMAL);
241         sendFrame(tracker, 4, JANK_NONE, 102L);
242 
243         verify(tracker).removeObservers();
244 
245         verify(mTrackerListener, never()).triggerPerfetto(any());
246 
247         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
248                 eq(42), /* displayId */
249                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
250                 eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
251                 eq(2L) /* totalFrames */,
252                 eq(0L) /* missedFrames */,
253                 eq(4000000L) /* maxFrameTimeNanos */,
254                 eq(0L) /* missedSfFramesCount */,
255                 eq(0L) /* missedAppFramesCount */,
256                 eq(0L) /* maxSuccessiveMissedFramesCount */);
257     }
258 
259     @Test
testOtherFrameOverThreshold()260     public void testOtherFrameOverThreshold() {
261         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
262 
263         when(mChoreographer.getVsyncId()).thenReturn(100L);
264         tracker.begin();
265         mRunnableArgumentCaptor.getValue().run();
266         verify(mRenderer, only()).addObserver(any());
267 
268         // send first frame - not janky
269         sendFrame(tracker, 4, JANK_NONE, 100L);
270 
271         // send another frame - should be considered janky
272         sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 101L);
273 
274         // end the trace session
275         when(mChoreographer.getVsyncId()).thenReturn(102L);
276         tracker.end(FrameTracker.REASON_END_NORMAL);
277         sendFrame(tracker, 4, JANK_NONE, 102L);
278 
279         verify(tracker).removeObservers();
280 
281         // We detected a janky frame - trigger Perfetto
282         verify(mTrackerListener).triggerPerfetto(any());
283 
284         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
285                 eq(42), /* displayId */
286                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
287                 eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
288                 eq(2L) /* totalFrames */,
289                 eq(1L) /* missedFrames */,
290                 eq(40000000L) /* maxFrameTimeNanos */,
291                 eq(0L) /* missedSfFramesCount */,
292                 eq(1L) /* missedAppFramesCount */,
293                 eq(1L) /* maxSuccessiveMissedFramesCount */);
294     }
295 
296     @Test
testLastFrameOverThresholdBeforeEnd()297     public void testLastFrameOverThresholdBeforeEnd() {
298         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
299 
300         when(mChoreographer.getVsyncId()).thenReturn(100L);
301         tracker.begin();
302         mRunnableArgumentCaptor.getValue().run();
303         verify(mRenderer, only()).addObserver(any());
304 
305         // send first frame - not janky
306         sendFrame(tracker, 4, JANK_NONE, 100L);
307 
308         // send another frame - not janky
309         sendFrame(tracker, 4, JANK_NONE, 101L);
310 
311         // end the trace session, simulate one more valid callback came after the end call.
312         when(mChoreographer.getVsyncId()).thenReturn(102L);
313         tracker.end(FrameTracker.REASON_END_NORMAL);
314         sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
315 
316         // One more callback with VSYNC after the end() vsync id.
317         sendFrame(tracker, 4, JANK_NONE, 103L);
318 
319         verify(tracker).removeObservers();
320 
321         // We detected a janky frame - trigger Perfetto
322         verify(mTrackerListener).triggerPerfetto(any());
323 
324         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
325                 eq(42), /* displayId */
326                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
327                 eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
328                 eq(2L) /* totalFrames */,
329                 eq(1L) /* missedFrames */,
330                 eq(50000000L) /* maxFrameTimeNanos */,
331                 eq(0L) /* missedSfFramesCount */,
332                 eq(1L) /* missedAppFramesCount */,
333                 eq(1L) /* maxSuccessiveMissedFramesCount */);
334     }
335 
336     /**
337      * b/223787365
338      */
339     @Test
testNoOvercountingAfterEnd()340     public void testNoOvercountingAfterEnd() {
341         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
342 
343         when(mChoreographer.getVsyncId()).thenReturn(100L);
344         tracker.begin();
345         mRunnableArgumentCaptor.getValue().run();
346         verify(mRenderer, only()).addObserver(any());
347 
348         // send first frame - not janky
349         sendFrame(tracker, 4, JANK_NONE, 100L);
350 
351         // send another frame - not janky
352         sendFrame(tracker, 4, JANK_NONE, 101L);
353 
354         // end the trace session, simulate one more valid callback came after the end call.
355         when(mChoreographer.getVsyncId()).thenReturn(102L);
356         tracker.end(FrameTracker.REASON_END_NORMAL);
357 
358         // Send incomplete callback for 102L
359         sendSfFrame(tracker, 102L, JANK_NONE);
360 
361         // Send janky but complete callbck fo 103L
362         sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
363 
364         verify(tracker).removeObservers();
365         verify(mTrackerListener, never()).triggerPerfetto(any());
366         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
367                 eq(42), /* displayId */
368                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
369                 eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
370                 eq(2L) /* totalFrames */,
371                 eq(0L) /* missedFrames */,
372                 eq(4000000L) /* maxFrameTimeNanos */,
373                 eq(0L) /* missedSfFramesCount */,
374                 eq(0L) /* missedAppFramesCount */,
375                 eq(0L) /* maxSuccessiveMissedFramesCount */);
376     }
377 
378     @Test
testBeginCancel()379     public void testBeginCancel() {
380         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
381 
382         when(mChoreographer.getVsyncId()).thenReturn(100L);
383         tracker.begin();
384         mRunnableArgumentCaptor.getValue().run();
385         verify(mRenderer).addObserver(any());
386 
387         // First frame - not janky
388         sendFrame(tracker, 4, JANK_NONE, 100L);
389 
390         // normal frame - not janky
391         sendFrame(tracker, 4, JANK_NONE, 101L);
392 
393         // a janky frame
394         sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
395 
396         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
397         verify(tracker).removeObservers();
398         // Since the tracker has been cancelled, shouldn't trigger perfetto.
399         verify(mTrackerListener, never()).triggerPerfetto(any());
400     }
401 
402     @Test
testCancelIfEndVsyncIdEqualsToBeginVsyncId()403     public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() {
404         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
405 
406         when(mChoreographer.getVsyncId()).thenReturn(100L);
407         tracker.begin();
408         mRunnableArgumentCaptor.getValue().run();
409         verify(mRenderer, only()).addObserver(any());
410 
411         // end the trace session
412         when(mChoreographer.getVsyncId()).thenReturn(101L);
413         tracker.end(FrameTracker.REASON_END_NORMAL);
414 
415         // Since the begin vsync id (101) equals to the end vsync id (101), will be treat as cancel.
416         verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
417 
418         // Observers should be removed in this case, or FrameTracker object will be leaked.
419         verify(tracker).removeObservers();
420 
421         // Should never trigger Perfetto since it is a cancel.
422         verify(mTrackerListener, never()).triggerPerfetto(any());
423     }
424 
425     @Test
testCancelIfEndVsyncIdLessThanBeginVsyncId()426     public void testCancelIfEndVsyncIdLessThanBeginVsyncId() {
427         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
428 
429         when(mChoreographer.getVsyncId()).thenReturn(100L);
430         tracker.begin();
431         mRunnableArgumentCaptor.getValue().run();
432         verify(mRenderer, only()).addObserver(any());
433 
434         // end the trace session at the same vsync id, end vsync id will less than the begin one.
435         // Because the begin vsync id is supposed to the next frame,
436         tracker.end(FrameTracker.REASON_END_NORMAL);
437 
438         // The begin vsync id (101) is larger than the end one (100), will be treat as cancel.
439         verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
440 
441         // Observers should be removed in this case, or FrameTracker object will be leaked.
442         verify(tracker).removeObservers();
443 
444         // Should never trigger Perfetto since it is a cancel.
445         verify(mTrackerListener, never()).triggerPerfetto(any());
446     }
447 
448     @Test
testCancelWhenSessionNeverBegun()449     public void testCancelWhenSessionNeverBegun() {
450         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
451 
452         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
453         verify(tracker).removeObservers();
454     }
455 
456     @Test
testEndWhenSessionNeverBegun()457     public void testEndWhenSessionNeverBegun() {
458         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
459 
460         tracker.end(FrameTracker.REASON_END_NORMAL);
461         verify(tracker).removeObservers();
462     }
463 
464     @Test
testSurfaceOnlyOtherFrameJanky()465     public void testSurfaceOnlyOtherFrameJanky() {
466         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
467 
468         when(mChoreographer.getVsyncId()).thenReturn(100L);
469         tracker.begin();
470         mRunnableArgumentCaptor.getValue().run();
471         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
472 
473         // First frame - not janky
474         sendFrame(tracker, JANK_NONE, 100L);
475         // normal frame - not janky
476         sendFrame(tracker, JANK_NONE, 101L);
477         // a janky frame
478         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
479 
480         when(mChoreographer.getVsyncId()).thenReturn(102L);
481         tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
482 
483         // an extra frame to trigger finish
484         sendFrame(tracker, JANK_NONE, 103L);
485 
486         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
487         verify(mTrackerListener).triggerPerfetto(any());
488 
489         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
490                 eq(42), /* displayId */
491                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
492                 eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
493                 eq(2L) /* totalFrames */,
494                 eq(1L) /* missedFrames */,
495                 eq(0L) /* maxFrameTimeNanos */,
496                 eq(0L) /* missedSfFramesCount */,
497                 eq(1L) /* missedAppFramesCount */,
498                 eq(1L) /* maxSuccessiveMissedFramesCount */);
499     }
500 
501     @Test
testSurfaceOnlyFirstFrameJanky()502     public void testSurfaceOnlyFirstFrameJanky() {
503         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
504 
505         when(mChoreographer.getVsyncId()).thenReturn(100L);
506         tracker.begin();
507         mRunnableArgumentCaptor.getValue().run();
508         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
509 
510         // First frame - janky
511         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 100L);
512         // normal frame - not janky
513         sendFrame(tracker, JANK_NONE, 101L);
514         // normal frame - not janky
515         sendFrame(tracker, JANK_NONE, 102L);
516 
517         when(mChoreographer.getVsyncId()).thenReturn(102L);
518         tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
519 
520         // an extra frame to trigger finish
521         sendFrame(tracker, JANK_NONE, 103L);
522 
523         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
524         verify(mTrackerListener, never()).triggerPerfetto(any());
525 
526         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
527                 eq(42), /* displayId */
528                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
529                 eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
530                 eq(2L) /* totalFrames */,
531                 eq(0L) /* missedFrames */,
532                 eq(0L) /* maxFrameTimeNanos */,
533                 eq(0L) /* missedSfFramesCount */,
534                 eq(0L) /* missedAppFramesCount */,
535                 eq(0L) /* maxSuccessiveMissedFramesCount */);
536     }
537 
538     @Test
testSurfaceOnlyLastFrameJanky()539     public void testSurfaceOnlyLastFrameJanky() {
540         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
541 
542         when(mChoreographer.getVsyncId()).thenReturn(100L);
543         tracker.begin();
544         mRunnableArgumentCaptor.getValue().run();
545         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
546 
547         // First frame - not janky
548         sendFrame(tracker, JANK_NONE, 100L);
549         // normal frame - not janky
550         sendFrame(tracker, JANK_NONE, 101L);
551         // normal frame - not janky
552         sendFrame(tracker, JANK_NONE, 102L);
553 
554         when(mChoreographer.getVsyncId()).thenReturn(102L);
555         tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
556 
557         // janky frame, should be ignored, trigger finish
558         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
559 
560         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
561         verify(mTrackerListener, never()).triggerPerfetto(any());
562 
563         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
564                 eq(42), /* displayId */
565                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
566                 eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
567                 eq(2L) /* totalFrames */,
568                 eq(0L) /* missedFrames */,
569                 eq(0L) /* maxFrameTimeNanos */,
570                 eq(0L) /* missedSfFramesCount */,
571                 eq(0L) /* missedAppFramesCount */,
572                 eq(0L) /* maxSuccessiveMissedFramesCount */);
573     }
574 
575     @Test
testMaxSuccessiveMissedFramesCount()576     public void testMaxSuccessiveMissedFramesCount() {
577         FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
578         when(mChoreographer.getVsyncId()).thenReturn(100L);
579         tracker.begin();
580         mRunnableArgumentCaptor.getValue().run();
581         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
582         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 100L);
583         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
584         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
585         sendFrame(tracker, JANK_NONE, 103L);
586         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 104L);
587         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 105L);
588         when(mChoreographer.getVsyncId()).thenReturn(106L);
589         tracker.end(FrameTracker.REASON_END_NORMAL);
590         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
591         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
592         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
593         verify(mTrackerListener).triggerPerfetto(any());
594         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
595                 eq(42), /* displayId */
596                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
597                 eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
598                 eq(6L) /* totalFrames */,
599                 eq(5L) /* missedFrames */,
600                 eq(0L) /* maxFrameTimeNanos */,
601                 eq(2L) /* missedSfFramesCount */,
602                 eq(3L) /* missedAppFramesCount */,
603                 eq(3L) /* maxSuccessiveMissedFramesCount */);
604     }
605 
sendFirstWindowFrame(FrameTracker tracker, long durationMillis, @JankType int jankType, long vsyncId)606     private void sendFirstWindowFrame(FrameTracker tracker, long durationMillis,
607             @JankType int jankType, long vsyncId) {
608         sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ true);
609     }
610 
sendFrame(FrameTracker tracker, long durationMillis, @JankType int jankType, long vsyncId)611     private void sendFrame(FrameTracker tracker, long durationMillis,
612             @JankType int jankType, long vsyncId) {
613         sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ false);
614     }
615 
616     /**
617      * Used for surface only test.
618      */
sendFrame(FrameTracker tracker, @JankType int jankType, long vsyncId)619     private void sendFrame(FrameTracker tracker, @JankType int jankType, long vsyncId) {
620         sendFrame(tracker, /* durationMillis= */ -1,
621                 jankType, vsyncId, /* firstWindowFrame= */ false);
622     }
623 
sendFrame(FrameTracker tracker, long durationMillis, @JankType int jankType, long vsyncId, boolean firstWindowFrame)624     private void sendFrame(FrameTracker tracker, long durationMillis,
625             @JankType int jankType, long vsyncId, boolean firstWindowFrame) {
626         if (!tracker.mSurfaceOnly) {
627             sendHwuiFrame(tracker, durationMillis, vsyncId, firstWindowFrame);
628         }
629         sendSfFrame(tracker, vsyncId, jankType);
630     }
631 
sendHwuiFrame(FrameTracker tracker, long durationMillis, long vsyncId, boolean firstWindowFrame)632     private void sendHwuiFrame(FrameTracker tracker, long durationMillis, long vsyncId,
633             boolean firstWindowFrame) {
634         when(mWrapper.getTiming()).thenReturn(new long[]{0, vsyncId});
635         doReturn(firstWindowFrame ? 1L : 0L).when(mWrapper)
636                 .getMetric(FrameMetrics.FIRST_DRAW_FRAME);
637         doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis))
638                 .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION);
639         final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
640         doNothing().when(tracker).postCallback(captor.capture());
641         tracker.onFrameMetricsAvailable(0);
642         captor.getValue().run();
643     }
644 
sendSfFrame(FrameTracker tracker, long vsyncId, @JankType int jankType)645     private void sendSfFrame(FrameTracker tracker, long vsyncId, @JankType int jankType) {
646         final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
647         doNothing().when(tracker).postCallback(captor.capture());
648         mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
649                 new JankData(vsyncId, jankType, FRAME_TIME_60Hz)
650         });
651         captor.getValue().run();
652     }
653 }
654