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.text.TextUtils.formatSimple;
20 
21 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
22 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
23 import static com.android.internal.jank.InteractionJankMonitor.Configuration.generateSessionName;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.anyInt;
29 import static org.mockito.ArgumentMatchers.anyLong;
30 import static org.mockito.Mockito.doNothing;
31 import static org.mockito.Mockito.doReturn;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.spy;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.SystemClock;
40 import android.provider.DeviceConfig;
41 import android.view.View;
42 import android.view.ViewAttachTestActivity;
43 
44 import androidx.test.ext.junit.rules.ActivityScenarioRule;
45 import androidx.test.filters.SmallTest;
46 
47 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
48 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
49 import com.android.internal.jank.FrameTracker.StatsLogWrapper;
50 import com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
51 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
52 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
53 import com.android.internal.jank.InteractionJankMonitor.Configuration;
54 
55 import com.google.common.truth.Expect;
56 
57 import org.junit.Before;
58 import org.junit.Rule;
59 import org.junit.Test;
60 import org.mockito.ArgumentCaptor;
61 
62 import java.util.HashMap;
63 
64 @SmallTest
65 public class InteractionJankMonitorTest {
66     private ViewAttachTestActivity mActivity;
67     private View mView;
68     private Handler mHandler;
69     private HandlerThread mWorker;
70 
71     @Rule
72     public ActivityScenarioRule<ViewAttachTestActivity> mRule =
73             new ActivityScenarioRule<>(ViewAttachTestActivity.class);
74 
75     @Rule
76     public final Expect mExpect = Expect.create();
77 
78     @Before
setup()79     public void setup() {
80         mRule.getScenario().onActivity(activity -> mActivity = activity);
81         mView = mActivity.getWindow().getDecorView();
82         assertThat(mView.isAttachedToWindow()).isTrue();
83 
84         mHandler = spy(new Handler(mActivity.getMainLooper()));
85         doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong());
86         mWorker = mock(HandlerThread.class);
87         doReturn(mHandler).when(mWorker).getThreadHandler();
88     }
89 
90     @Test
testBeginEnd()91     public void testBeginEnd() {
92         InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
93         FrameTracker tracker = createMockedFrameTracker();
94         doReturn(tracker).when(monitor).createFrameTracker(any());
95         doNothing().when(tracker).begin();
96         doReturn(true).when(tracker).end(anyInt());
97 
98         // Simulate a trace session and see if begin / end are invoked.
99         assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
100         verify(tracker).begin();
101         assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
102         verify(tracker).end(REASON_END_NORMAL);
103     }
104 
105     @Test
testDisabledThroughDeviceConfig()106     public void testDisabledThroughDeviceConfig() {
107         InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker);
108 
109         HashMap<String, String> propertiesValues = new HashMap<>();
110         propertiesValues.put("enabled", "false");
111         DeviceConfig.Properties properties = new DeviceConfig.Properties(
112                 DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues);
113         monitor.updateProperties(properties);
114 
115         assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
116         assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
117     }
118 
119     @Test
testCheckInitState()120     public void testCheckInitState() {
121         InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker);
122         View view = new View(mActivity);
123         assertThat(view.isAttachedToWindow()).isFalse();
124 
125         // Should return false if the view passed in is not attached to window yet.
126         assertThat(monitor.begin(view, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
127         assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
128     }
129 
130     @Test
testBeginTimeout()131     public void testBeginTimeout() {
132         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
133         InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
134         FrameTracker tracker = createMockedFrameTracker();
135         doReturn(tracker).when(monitor).createFrameTracker(any());
136         doNothing().when(tracker).begin();
137         doReturn(true).when(tracker).cancel(anyInt());
138 
139         assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
140         verify(tracker).begin();
141         verify(monitor).scheduleTimeoutAction(any(), captor.capture());
142         Runnable runnable = captor.getValue();
143         assertThat(runnable).isNotNull();
144         runnable.run();
145         verify(monitor).cancel(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
146         verify(tracker).cancel(REASON_CANCEL_TIMEOUT);
147     }
148 
149     @Test
testSessionNameLengthLimit()150     public void testSessionNameLengthLimit() {
151         final int cujType = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
152         final String cujName = Cuj.getNameOfCuj(cujType);
153         final String cujTag = "ThisIsTheCujTag";
154         final String tooLongTag = cujTag.repeat(10);
155 
156         // Normal case, no postfix.
157         assertThat(generateSessionName(cujName, "")).isEqualTo(formatSimple("J<%s>", cujName));
158 
159         // Normal case, with postfix.
160         assertThat(generateSessionName(cujName, cujTag))
161                 .isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag));
162 
163         // Since the length of the cuj name is tested in another test, no need to test it here.
164         // Too long postfix case, should trim the postfix and keep the cuj name completed.
165         final String expectedTrimmedName = formatSimple("J<%s::%s>", cujName,
166                 "ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThi...");
167         assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName);
168     }
169 
createMockedInteractionJankMonitor()170     private InteractionJankMonitor createMockedInteractionJankMonitor() {
171         InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
172         doReturn(true).when(monitor).shouldMonitor();
173         return monitor;
174     }
175 
createMockedFrameTracker()176     private FrameTracker createMockedFrameTracker() {
177         ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class);
178         doNothing().when(threadedRenderer).addObserver(any());
179         doNothing().when(threadedRenderer).removeObserver(any());
180 
181         ViewRootWrapper viewRoot = spy(new ViewRootWrapper(mView.getViewRootImpl()));
182         doNothing().when(viewRoot).addSurfaceChangedCallback(any());
183         doNothing().when(viewRoot).removeSurfaceChangedCallback(any());
184 
185         SurfaceControlWrapper surfaceControl = mock(SurfaceControlWrapper.class);
186         doNothing().when(surfaceControl).addJankStatsListener(any(), any());
187         doNothing().when(surfaceControl).removeJankStatsListener(any());
188 
189         final ChoreographerWrapper choreographer = mock(ChoreographerWrapper.class);
190         doReturn(SystemClock.elapsedRealtime()).when(choreographer).getVsyncId();
191 
192         Configuration configuration = mock(Configuration.class);
193         when(configuration.isSurfaceOnly()).thenReturn(false);
194         when(configuration.getView()).thenReturn(mView);
195         when(configuration.getDisplayId()).thenReturn(42);
196         when(configuration.logToStatsd()).thenReturn(false);
197         when(configuration.getHandler()).thenReturn(mHandler);
198 
199         FrameTracker tracker = spy(new FrameTracker(configuration,
200                 threadedRenderer, viewRoot, surfaceControl, choreographer,
201                 new FrameMetricsWrapper(), new StatsLogWrapper(null),
202                 /* traceThresholdMissedFrames= */ 1,
203                 /* traceThresholdFrameTimeMillis= */ -1,
204                 /* listener */ null));
205 
206         doNothing().when(tracker).postTraceStartMarker(any());
207 
208         return tracker;
209     }
210 }
211