blob: 8ad117a163e2d9185fdf95f288daab786f0e6988 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.cts.device.graphicsstats;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.Choreographer;
import android.view.FrameMetrics;
import android.view.View;
import android.view.Window;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class DrawFramesActivity extends Activity implements Window.OnFrameMetricsAvailableListener {
public static final int FRAME_JANK_RECORD_DRAW = 1 << 0;
public static final int FRAME_JANK_ANIMATION = 1 << 1;
public static final int FRAME_JANK_LAYOUT = 1 << 2;
public static final int FRAME_JANK_DAVEY_JR = 1 << 3;
public static final int FRAME_JANK_DAVEY = 1 << 4;
public static final int FRAME_JANK_MISS_VSYNC = 1 << 5;
private static final String TAG = "GraphicsStatsDeviceTest";
private static final int[] COLORS = new int[] {
Color.RED,
Color.GREEN,
Color.BLUE,
};
private View mColorView;
private int mColorIndex;
private final CountDownLatch mReady = new CountDownLatch(1);
private Choreographer mChoreographer;
private CountDownLatch mFramesFinishedFence = mReady;
private int mFrameIndex;
private int[] mFramesToDraw;
private int mDroppedReportsCount = 0;
private int mRenderedFrames = 0;
private CountDownLatch mFrameSetupFence = new CountDownLatch(0);
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
getWindow().addOnFrameMetricsAvailableListener(this, new Handler());
mChoreographer = Choreographer.getInstance();
mColorView = new View(this) {
{
setWillNotDraw(false);
}
@Override
protected void onDraw(Canvas canvas) {
jankIf(FRAME_JANK_RECORD_DRAW);
}
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
jankIf(FRAME_JANK_LAYOUT);
}
};
updateColor();
setContentView(mColorView);
}
private void setupFrame() {
mFrameSetupFence.countDown();
updateColor();
if (isFrameFlagSet(FRAME_JANK_LAYOUT)) {
mColorView.requestLayout();
}
if (isFrameFlagSet(FRAME_JANK_DAVEY_JR)) {
spinSleep(150);
}
if (isFrameFlagSet(FRAME_JANK_DAVEY)) {
spinSleep(700);
}
}
private void updateColor() {
mColorView.setBackgroundColor(COLORS[mColorIndex]);
// allow COLORs to be length == 1 or have duplicates without breaking the test
mColorView.invalidate();
mColorIndex = (mColorIndex + 1) % COLORS.length;
}
private void jankIf(int flagIsSet) {
if (isFrameFlagSet(flagIsSet)) {
jank();
}
}
private boolean isFrameFlagSet(int flag) {
return mFramesToDraw != null && (mFramesToDraw[mFrameIndex] & flag) != 0;
}
private void jank() {
spinSleep(24);
}
private void spinSleep(int durationMs) {
long until = System.currentTimeMillis() + durationMs;
while (System.currentTimeMillis() < until) {}
}
private void scheduleDraw() {
mFrameSetupFence = new CountDownLatch(1);
mColorView.invalidate();
mChoreographer.postFrameCallback((long timestamp) -> {
setupFrame();
jankIf(FRAME_JANK_ANIMATION);
});
if (isFrameFlagSet(FRAME_JANK_MISS_VSYNC)) {
spinSleep(45);
}
}
private void onDrawFinished() {
if (mFramesToDraw != null && mFrameIndex < mFramesToDraw.length - 1) {
mFrameIndex++;
scheduleDraw();
} else if (mFramesFinishedFence != null) {
mFramesFinishedFence.countDown();
mFramesFinishedFence = null;
mFramesToDraw = null;
}
}
public void drawFrames(final int frameCount) throws InterruptedException, TimeoutException {
drawFrames(new int[frameCount]);
}
public void waitForReady() throws InterruptedException, TimeoutException {
if (!mReady.await(4, TimeUnit.SECONDS)) {
throw new TimeoutException();
}
}
public void drawFrames(final int[] framesToDraw) throws InterruptedException, TimeoutException {
if (!mReady.await(4, TimeUnit.SECONDS)) {
throw new TimeoutException();
}
final CountDownLatch fence = new CountDownLatch(1);
long timeoutDurationMs = 0;
for (int frame : framesToDraw) {
// 50ms base time + 20ms for every extra jank event
timeoutDurationMs += 50 + (24 * Integer.bitCount(frame));
if ((frame & FRAME_JANK_DAVEY_JR) != 0) {
timeoutDurationMs += 150;
}
if ((frame & FRAME_JANK_DAVEY) != 0) {
timeoutDurationMs += 700;
}
}
runOnUiThread(() -> {
mFramesToDraw = framesToDraw;
mFrameIndex = 0;
mFramesFinishedFence = fence;
scheduleDraw();
});
if (!fence.await(timeoutDurationMs, TimeUnit.MILLISECONDS)) {
throw new TimeoutException("Drawing " + framesToDraw.length + " frames timed out after "
+ timeoutDurationMs + "ms");
}
}
public int getRenderedFramesCount() {
return mRenderedFrames;
}
public int getDroppedReportsCount() {
return mDroppedReportsCount;
}
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
int dropCountSinceLastInvocation) {
if (mFramesFinishedFence == null || mFramesFinishedFence.getCount() <= 0) {
// Count Metrics only when drawFrames is being actively executed. Ignore.
return;
}
if (mFrameSetupFence.getCount() > 0) {
// This callback is for a frame that was rendered before the first frame of the
// drawFrames. Ignore.
return;
}
mDroppedReportsCount += dropCountSinceLastInvocation;
mRenderedFrames++;
onDrawFinished();
}
}