1 /*
2  * Copyright (C) 2017 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.server.cts.device.graphicsstats;
18 
19 import android.app.Activity;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.view.Choreographer;
25 import android.view.FrameMetrics;
26 import android.view.View;
27 import android.view.Window;
28 
29 import java.util.concurrent.CountDownLatch;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.TimeoutException;
32 
33 public class DrawFramesActivity extends Activity implements Window.OnFrameMetricsAvailableListener {
34 
35     public static final int FRAME_JANK_RECORD_DRAW = 1 << 0;
36     public static final int FRAME_JANK_ANIMATION = 1 << 1;
37     public static final int FRAME_JANK_LAYOUT = 1 << 2;
38     public static final int FRAME_JANK_DAVEY_JR = 1 << 3;
39     public static final int FRAME_JANK_DAVEY = 1 << 4;
40     public static final int FRAME_JANK_MISS_VSYNC = 1 << 5;
41 
42     private static final String TAG = "GraphicsStatsDeviceTest";
43 
44     private static final int[] COLORS = new int[] {
45             Color.RED,
46             Color.GREEN,
47             Color.BLUE,
48     };
49 
50     private View mColorView;
51     private int mColorIndex;
52     private final CountDownLatch mReady = new CountDownLatch(1);
53     private Choreographer mChoreographer;
54     private CountDownLatch mFramesFinishedFence = mReady;
55     private int mFrameIndex;
56     private int[] mFramesToDraw;
57     private int mDroppedReportsCount = 0;
58     private int mRenderedFrames = 0;
59     private CountDownLatch mFrameSetupFence = new CountDownLatch(0);
60 
61     @Override
onCreate(Bundle bundle)62     public void onCreate(Bundle bundle) {
63         super.onCreate(bundle);
64         getWindow().addOnFrameMetricsAvailableListener(this, new Handler());
65 
66         mChoreographer = Choreographer.getInstance();
67         mColorView = new View(this) {
68             {
69                 setWillNotDraw(false);
70             }
71 
72             @Override
73             protected void onDraw(Canvas canvas) {
74                 jankIf(FRAME_JANK_RECORD_DRAW);
75             }
76 
77             @Override
78             public void layout(int l, int t, int r, int b) {
79                 super.layout(l, t, r, b);
80                 jankIf(FRAME_JANK_LAYOUT);
81             }
82         };
83         updateColor();
84         setContentView(mColorView);
85     }
86 
setupFrame()87     private void setupFrame() {
88         mFrameSetupFence.countDown();
89         updateColor();
90         if (isFrameFlagSet(FRAME_JANK_LAYOUT)) {
91             mColorView.requestLayout();
92         }
93         if (isFrameFlagSet(FRAME_JANK_DAVEY_JR)) {
94             spinSleep(150);
95         }
96         if (isFrameFlagSet(FRAME_JANK_DAVEY)) {
97             spinSleep(700);
98         }
99     }
100 
updateColor()101     private void updateColor() {
102         mColorView.setBackgroundColor(COLORS[mColorIndex]);
103         // allow COLORs to be length == 1 or have duplicates without breaking the test
104         mColorView.invalidate();
105         mColorIndex = (mColorIndex + 1) % COLORS.length;
106     }
107 
jankIf(int flagIsSet)108     private void jankIf(int flagIsSet) {
109         if (isFrameFlagSet(flagIsSet)) {
110             jank();
111         }
112     }
113 
isFrameFlagSet(int flag)114     private boolean isFrameFlagSet(int flag) {
115         return mFramesToDraw != null && (mFramesToDraw[mFrameIndex] & flag) != 0;
116     }
117 
jank()118     private void jank() {
119         spinSleep(24);
120     }
121 
spinSleep(int durationMs)122     private void spinSleep(int durationMs) {
123         long until = System.currentTimeMillis() + durationMs;
124         while (System.currentTimeMillis() <= until) {}
125     }
126 
scheduleDraw()127     private void scheduleDraw() {
128         mFrameSetupFence = new CountDownLatch(1);
129         mColorView.invalidate();
130         mChoreographer.postFrameCallback((long timestamp) -> {
131             setupFrame();
132             jankIf(FRAME_JANK_ANIMATION);
133         });
134         if (isFrameFlagSet(FRAME_JANK_MISS_VSYNC)) {
135             spinSleep(45);
136         }
137     }
138 
onDrawFinished()139     private void onDrawFinished() {
140         if (mFramesToDraw != null && mFrameIndex < mFramesToDraw.length - 1) {
141             mFrameIndex++;
142             scheduleDraw();
143         } else if (mFramesFinishedFence != null) {
144             mFramesFinishedFence.countDown();
145             mFramesFinishedFence = null;
146             mFramesToDraw = null;
147         }
148     }
149 
waitForReady()150     public void waitForReady() throws InterruptedException, TimeoutException {
151         if (!mReady.await(4, TimeUnit.SECONDS)) {
152             throw new TimeoutException();
153         }
154     }
155 
drawFrames(final int[] framesToDraw)156     public void drawFrames(final int[] framesToDraw) throws InterruptedException, TimeoutException {
157         if (!mReady.await(4, TimeUnit.SECONDS)) {
158             throw new TimeoutException();
159         }
160         final CountDownLatch fence = new CountDownLatch(1);
161         long timeoutDurationMs = 0;
162         for (int frame : framesToDraw) {
163             // 50ms base time + 20ms for every extra jank event
164             timeoutDurationMs += 50 + (24 * Integer.bitCount(frame));
165             if ((frame & FRAME_JANK_DAVEY_JR) != 0) {
166                 timeoutDurationMs += 150;
167             }
168             if ((frame & FRAME_JANK_DAVEY) != 0) {
169                 timeoutDurationMs += 700;
170             }
171         }
172         runOnUiThread(() -> {
173             mFramesToDraw = framesToDraw;
174             mFrameIndex = 0;
175             mFramesFinishedFence = fence;
176             scheduleDraw();
177         });
178         if (!fence.await(timeoutDurationMs, TimeUnit.MILLISECONDS)) {
179             throw new TimeoutException("Drawing " + framesToDraw.length + " frames timed out after "
180                     + timeoutDurationMs + "ms");
181         }
182     }
183 
getRenderedFramesCount()184     public int getRenderedFramesCount() {
185         return mRenderedFrames;
186     }
187 
getDroppedReportsCount()188     public int getDroppedReportsCount() {
189         return mDroppedReportsCount;
190     }
191 
192     @Override
onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation)193     public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
194             int dropCountSinceLastInvocation) {
195         if (mFramesFinishedFence == null || mFramesFinishedFence.getCount() <= 0) {
196             // Count Metrics only when drawFrames is being actively executed. Ignore.
197             return;
198         }
199 
200         if (mFrameSetupFence.getCount() > 0) {
201             // This callback is for a frame that was rendered before the first frame of the
202             // drawFrames. Ignore.
203             return;
204         }
205 
206         mDroppedReportsCount += dropCountSinceLastInvocation;
207         mRenderedFrames++;
208         onDrawFinished();
209     }
210 }
211