CTS: add test for Display.getRefreshRate()

Change-Id: I504b070ed3f2b2d29372d4b51a13c9b1b0ae587b
diff --git a/tests/tests/view/src/android/view/cts/DisplayRefreshRateTest.java b/tests/tests/view/src/android/view/cts/DisplayRefreshRateTest.java
new file mode 100644
index 0000000..a8ed650
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/DisplayRefreshRateTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2011 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 android.view.cts;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.opengl.cts.GLSurfaceViewStubActivity;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.view.Display;
+import android.view.WindowManager;
+import android.util.Log;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import javax.microedition.khronos.egl.EGLConfig;
+
+/**
+ * Test that the screen refresh rate claimed by
+ * android.view.Display.getRefreshRate() matches the steady-state framerate
+ * achieved by vsync-limited eglSwapBuffers(). The primary goal is to test
+ * Display.getRefreshRate() -- using GL is just an easy and hopefully reliable
+ * way of measuring the actual refresh rate.
+ */
+public class DisplayRefreshRateTest extends
+        ActivityInstrumentationTestCase2<GLSurfaceViewStubActivity> {
+
+    // The test passes if
+    //   abs(measured_fps - Display.getRefreshRate()) <= FPS_TOLERANCE.
+    // A smaller tolerance requires a more accurate measured_fps in order
+    // to avoid false negatives.
+    private static final float FPS_TOLERANCE = 2.0f;
+
+    private static final String TAG = "DisplayRefreshRateTest";
+
+    private class FpsResult {
+        private float mFps;
+        private boolean mValid = false;
+
+        public final synchronized void notifyResult(float fps) {
+            if (!mValid) {
+                mFps = fps;
+                mValid = true;
+                notifyAll();
+            }
+        }
+
+        public final synchronized float waitResult() {
+            while (!mValid) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {/* ignore and retry */}
+            }
+            return mFps;
+        }
+    }
+
+    private class Renderer implements GLSurfaceView.Renderer {
+        private static final int HISTORY_SIZE = 32;
+        private static final long MAX_TEST_NS = 5000000000L; // 5 seconds
+
+        // Keep trying until the standard deviation of frametimes is within 15%
+        // of the mean frametime. The goal is to establish confidence that the
+        // mean is accurate, not to achieve a highly stable framerate, so a
+        // relatively large standard deviation is okay. This value determined
+        // experimentally; adjust as needed.
+        private static final float MAX_STDDEV_DIV_MEAN = 0.15f;
+
+        private FpsResult mResult;
+        private long mStartNs;
+        private long mPrevNs;
+        private int mNumFrames = 0;
+        private float[] mFrameTimes;
+        private boolean mDone = false;
+
+        public Renderer(FpsResult result) {
+            mResult = result;
+            mStartNs = mPrevNs = System.nanoTime();
+            mFrameTimes = new float[HISTORY_SIZE];
+        }
+
+        public void onDrawFrame(GL10 gl) {
+            long timeNs = System.nanoTime();
+            if (timeNs - mStartNs >= MAX_TEST_NS) {
+                mResult.notifyResult(0.0f);
+                return;
+            }
+            float dt = (float)(timeNs - mPrevNs) * 1.0e-9f;
+            mFrameTimes[mNumFrames % HISTORY_SIZE] = dt;
+            mPrevNs = timeNs;
+
+            if (mNumFrames >= HISTORY_SIZE && !mDone) {
+                float mean = 0.0f;
+                for (int i = 0; i < HISTORY_SIZE; i++) {
+                    mean += mFrameTimes[i];
+                }
+                mean /= (float)HISTORY_SIZE;
+
+                float sumSqDiff = 0.0f;
+                for (int i = 0; i < HISTORY_SIZE; i++) {
+                    float d = mFrameTimes[i] - mean;
+                    sumSqDiff += d*d;
+                }
+                float stddev = (float)Math.sqrt(sumSqDiff / (float)HISTORY_SIZE);
+
+                if ((stddev / mean) <= MAX_STDDEV_DIV_MEAN) {
+                    Log.d(TAG, "mean:" + mean +
+                               " stddev:" + stddev +
+                               " div:" + (stddev / mean));
+                    mResult.notifyResult(1.0f / mean);
+                    mDone = true;
+                }
+            }
+
+            // prevent drivers from optimizing the frame away
+            gl.glClearColor(10*dt, 0.0f, 0.0f, 1.0f);
+            gl.glClear(gl.GL_COLOR_BUFFER_BIT);
+
+            mNumFrames++;
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            // Do nothing.
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            // Do nothing.
+        }
+    }
+
+    private FpsResult mResult;
+
+    public DisplayRefreshRateTest() {
+        super(GLSurfaceViewStubActivity.class);
+        mResult = new FpsResult();
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        GLSurfaceViewStubActivity.setRenderer(new Renderer(mResult));
+        GLSurfaceViewStubActivity.setRenderMode(
+                GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+    }
+
+    public void testRefreshRate() {
+        GLSurfaceViewStubActivity activity = getActivity();
+        float achievedFps = mResult.waitResult();
+        activity.finish();
+
+        WindowManager wm = (WindowManager)activity
+                .getView()
+                .getContext()
+                .getSystemService(Context.WINDOW_SERVICE);
+        Display dpy = wm.getDefaultDisplay();
+        float claimedFps = dpy.getRefreshRate();
+
+        Log.d(TAG, "claimed " + claimedFps + " fps, " +
+                   "achieved " + achievedFps + " fps");
+
+        assertTrue(Math.abs(claimedFps - achievedFps) <= FPS_TOLERANCE);
+    }
+
+}