1 /* 2 * Copyright (C) 2016 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 android.view.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 22 import android.Manifest; 23 import android.animation.ValueAnimator; 24 import android.app.Activity; 25 import android.app.Instrumentation; 26 import android.os.Handler; 27 import android.os.HandlerThread; 28 import android.os.Looper; 29 import android.os.SystemClock; 30 import android.platform.test.annotations.AppModeSdkSandbox; 31 import android.view.FrameMetrics; 32 import android.view.Window; 33 import android.widget.ScrollView; 34 35 import androidx.test.InstrumentationRegistry; 36 import androidx.test.filters.MediumTest; 37 import androidx.test.rule.ActivityTestRule; 38 import androidx.test.runner.AndroidJUnit4; 39 40 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 41 import com.android.compatibility.common.util.PollingCheck; 42 import com.android.compatibility.common.util.WidgetTestUtils; 43 44 import org.junit.After; 45 import org.junit.Before; 46 import org.junit.Rule; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 import java.util.ArrayList; 51 import java.util.concurrent.atomic.AtomicInteger; 52 53 @MediumTest 54 @RunWith(AndroidJUnit4.class) 55 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") 56 public class FrameMetricsListenerTest { 57 private Instrumentation mInstrumentation; 58 private Activity mActivity; 59 private float mPreviousDurationScale; 60 61 @Rule(order = 0) 62 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( 63 androidx.test.platform.app.InstrumentationRegistry 64 .getInstrumentation().getUiAutomation(), 65 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); 66 67 @Rule(order = 1) 68 public ActivityTestRule<MockActivity> mActivityRule = 69 new ActivityTestRule<>(MockActivity.class); 70 71 @Before setup()72 public void setup() { 73 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 74 mActivity = mActivityRule.getActivity(); 75 mPreviousDurationScale = ValueAnimator.getDurationScale(); 76 ValueAnimator.setDurationScale(0.0f); // Disable animations during frame metrics tests 77 } 78 79 @After tearDown()80 public void tearDown() { 81 // Restore animations to previous animation scale after tests are run 82 ValueAnimator.setDurationScale(mPreviousDurationScale); 83 } 84 layout(final int layoutId)85 private void layout(final int layoutId) throws Throwable { 86 mActivityRule.runOnUiThread(() -> mActivity.setContentView(layoutId)); 87 mInstrumentation.waitForIdleSync(); 88 } 89 90 @Test testReceiveData()91 public void testReceiveData() throws Throwable { 92 layout(R.layout.scrollview_layout); 93 final ScrollView scrollView = (ScrollView) mActivity.findViewById(R.id.scroll_view); 94 final ArrayList<FrameMetrics> data = new ArrayList<>(); 95 final Handler handler = new Handler(Looper.getMainLooper()); 96 final Window myWindow = mActivity.getWindow(); 97 final Window.OnFrameMetricsAvailableListener listener = 98 (Window window, FrameMetrics frameMetrics, int dropCount) -> { 99 assertEquals(myWindow, window); 100 assertEquals(0, dropCount); 101 callGetMetric(frameMetrics); 102 data.add(new FrameMetrics(frameMetrics)); 103 }; 104 mActivityRule.runOnUiThread(() -> mActivity.getWindow(). 105 addOnFrameMetricsAvailableListener(listener, handler)); 106 107 scrollView.postInvalidate(); 108 109 PollingCheck.waitFor(() -> data.size() != 0); 110 111 mActivityRule.runOnUiThread(() -> { 112 mActivity.getWindow().removeOnFrameMetricsAvailableListener(listener); 113 }); 114 115 data.clear(); 116 117 // Produce 5 frames and assert no metric listeners were invoked 118 for (int i = 0; i < 5; i++) { 119 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, scrollView, null); 120 } 121 assertEquals(0, data.size()); 122 } 123 124 @Test testMultipleListeners()125 public void testMultipleListeners() throws Throwable { 126 layout(R.layout.scrollview_layout); 127 final ScrollView scrollView = (ScrollView) mActivity.findViewById(R.id.scroll_view); 128 final ArrayList<FrameMetrics> data1 = new ArrayList<>(); 129 final Handler handler = new Handler(Looper.getMainLooper()); 130 final Window myWindow = mActivity.getWindow(); 131 132 final Window.OnFrameMetricsAvailableListener frameMetricsListener1 = 133 (Window window, FrameMetrics frameMetrics, int dropCount) -> { 134 assertEquals(myWindow, window); 135 assertEquals(0, dropCount); 136 callGetMetric(frameMetrics); 137 data1.add(new FrameMetrics(frameMetrics)); 138 }; 139 final ArrayList<FrameMetrics> data2 = new ArrayList<>(); 140 final Window.OnFrameMetricsAvailableListener frameMetricsListener2 = 141 (Window window, FrameMetrics frameMetrics, int dropCount) -> { 142 assertEquals(myWindow, window); 143 assertEquals(0, dropCount); 144 callGetMetric(frameMetrics); 145 data2.add(new FrameMetrics(frameMetrics)); 146 }; 147 mActivityRule.runOnUiThread(() -> { 148 mActivity.getWindow().addOnFrameMetricsAvailableListener( 149 frameMetricsListener1, handler); 150 mActivity.getWindow().addOnFrameMetricsAvailableListener( 151 frameMetricsListener2, handler); 152 }); 153 154 mInstrumentation.waitForIdleSync(); 155 156 mActivityRule.runOnUiThread(() -> scrollView.fling(-100)); 157 158 mInstrumentation.waitForIdleSync(); 159 PollingCheck.waitFor(() -> data1.size() != 0 && data1.size() == data2.size()); 160 161 mActivityRule.runOnUiThread(() -> { 162 mActivity.getWindow().removeOnFrameMetricsAvailableListener(frameMetricsListener1); 163 mActivity.getWindow().removeOnFrameMetricsAvailableListener(frameMetricsListener2); 164 }); 165 } 166 167 @Test testDropCount()168 public void testDropCount() throws Throwable { 169 layout(R.layout.scrollview_layout); 170 final ScrollView scrollView = (ScrollView) mActivity.findViewById(R.id.scroll_view); 171 172 final AtomicInteger framesDropped = new AtomicInteger(); 173 174 final HandlerThread thread = new HandlerThread("Listener"); 175 thread.start(); 176 final Window.OnFrameMetricsAvailableListener frameMetricsListener = 177 (Window window, FrameMetrics frameMetrics, int dropCount) -> { 178 SystemClock.sleep(100); 179 callGetMetric(frameMetrics); 180 framesDropped.addAndGet(dropCount); 181 }; 182 183 mActivityRule.runOnUiThread(() -> mActivity.getWindow(). 184 addOnFrameMetricsAvailableListener(frameMetricsListener, 185 new Handler(thread.getLooper()))); 186 187 mInstrumentation.waitForIdleSync(); 188 189 mActivityRule.runOnUiThread(() -> scrollView.fling(-100)); 190 191 mInstrumentation.waitForIdleSync(); 192 PollingCheck.waitFor(() -> framesDropped.get() > 0); 193 194 mActivityRule.runOnUiThread(() -> mActivity.getWindow(). 195 removeOnFrameMetricsAvailableListener(frameMetricsListener)); 196 } 197 callGetMetric(FrameMetrics frameMetrics)198 private void callGetMetric(FrameMetrics frameMetrics) { 199 200 // Perform basic checks on timestamp values. 201 long unknownDelay = frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION); 202 long input = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION); 203 long animation = frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION); 204 long layoutMeasure = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION); 205 long draw = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION); 206 long sync = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION); 207 long commandIssue = frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION); 208 long swapBuffers = frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION); 209 long gpuDuration = frameMetrics.getMetric(FrameMetrics.GPU_DURATION); 210 long deadline = frameMetrics.getMetric(FrameMetrics.DEADLINE); 211 long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION); 212 long intended_vsync = frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP); 213 long vsync = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP); 214 215 assertTrue(unknownDelay > 0); 216 assertTrue(input > 0); 217 assertTrue(animation > 0); 218 assertTrue(layoutMeasure > 0); 219 assertTrue(draw > 0); 220 assertTrue(sync > 0); 221 assertTrue(commandIssue > 0); 222 assertTrue(swapBuffers > 0); 223 assertTrue(intended_vsync > 0); 224 assertTrue(vsync > 0); 225 assertTrue(gpuDuration >= 0); 226 assertTrue(totalDuration > 0); 227 assertTrue(deadline > 0); 228 229 long now = System.nanoTime(); 230 assertTrue(intended_vsync < now); 231 assertTrue(vsync < now); 232 assertTrue(vsync >= intended_vsync); 233 234 // swapBuffers and gpuDuration may happen in parallel, so instead of counting both we need 235 // to take the longer of the two. 236 assertTrue(totalDuration >= unknownDelay + input + animation + layoutMeasure + draw + sync 237 + commandIssue + Math.max(gpuDuration, swapBuffers)); 238 239 // This is the only boolean metric so far 240 final long firstDrawFrameMetric = frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME); 241 assertTrue("First draw frame metric should be boolean but is " + firstDrawFrameMetric, 242 (firstDrawFrameMetric == 0) || (firstDrawFrameMetric == 1)); 243 } 244 } 245