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 60 @Override 61 public void onCreate(Bundle bundle) { 62 super.onCreate(bundle); 63 getWindow().addOnFrameMetricsAvailableListener(this, new Handler()); 64 65 mChoreographer = Choreographer.getInstance(); 66 mColorView = new View(this) { 67 { 68 setWillNotDraw(false); 69 } 70 71 @Override 72 protected void onDraw(Canvas canvas) { 73 jankIf(FRAME_JANK_RECORD_DRAW); 74 } 75 76 @Override 77 public void layout(int l, int t, int r, int b) { 78 super.layout(l, t, r, b); 79 jankIf(FRAME_JANK_LAYOUT); 80 } 81 }; 82 updateColor(); 83 setContentView(mColorView); 84 } 85 86 private void setupFrame() { 87 updateColor(); 88 if (isFrameFlagSet(FRAME_JANK_LAYOUT)) { 89 mColorView.requestLayout(); 90 } 91 if (isFrameFlagSet(FRAME_JANK_DAVEY_JR)) { 92 spinSleep(150); 93 } 94 if (isFrameFlagSet(FRAME_JANK_DAVEY)) { 95 spinSleep(700); 96 } 97 } 98 99 private void updateColor() { 100 mColorView.setBackgroundColor(COLORS[mColorIndex]); 101 // allow COLORs to be length == 1 or have duplicates without breaking the test 102 mColorView.invalidate(); 103 mColorIndex = (mColorIndex + 1) % COLORS.length; 104 } 105 106 private void jankIf(int flagIsSet) { 107 if (isFrameFlagSet(flagIsSet)) { 108 jank(); 109 } 110 } 111 112 private boolean isFrameFlagSet(int flag) { 113 return mFramesToDraw != null && (mFramesToDraw[mFrameIndex] & flag) != 0; 114 } 115 116 private void jank() { 117 spinSleep(24); 118 } 119 120 private void spinSleep(int durationMs) { 121 long until = System.currentTimeMillis() + durationMs; 122 while (System.currentTimeMillis() < until) {} 123 } 124 125 private void scheduleDraw() { 126 mChoreographer.postFrameCallback((long timestamp) -> { 127 setupFrame(); 128 jankIf(FRAME_JANK_ANIMATION); 129 }); 130 if (isFrameFlagSet(FRAME_JANK_MISS_VSYNC)) { 131 spinSleep(45); 132 } 133 } 134 135 private void onDrawFinished() { 136 if (mFramesToDraw != null && mFrameIndex < mFramesToDraw.length - 1) { 137 mFrameIndex++; 138 scheduleDraw(); 139 } else if (mFramesFinishedFence != null) { 140 mFramesFinishedFence.countDown(); 141 mFramesFinishedFence = null; 142 mFramesToDraw = null; 143 } 144 } 145 146 public void drawFrames(final int frameCount) throws InterruptedException, TimeoutException { 147 drawFrames(new int[frameCount]); 148 } 149 150 public void waitForReady() throws InterruptedException, TimeoutException { 151 if (!mReady.await(4, TimeUnit.SECONDS)) { 152 throw new TimeoutException(); 153 } 154 } 155 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 184 public int getRenderedFramesCount() { 185 return mRenderedFrames; 186 } 187 188 public int getDroppedReportsCount() { 189 return mDroppedReportsCount; 190 } 191 192 @Override 193 public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, 194 int dropCountSinceLastInvocation) { 195 mDroppedReportsCount += dropCountSinceLastInvocation; 196 mRenderedFrames++; 197 onDrawFinished(); 198 } 199 } 200