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