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