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