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