Batching support: Check & verify FIFO length

Change-Id: I50f933f9f89120a61af9f8ffdd62c2e733467aa5
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorBatchingFifoTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorBatchingFifoTest.java
new file mode 100644
index 0000000..4c6362a
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorBatchingFifoTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 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.hardware.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.FifoLengthVerification;
+
+/**
+ * Checks the minimum Hardware FIFO length for each of the Hardware sensor.
+ * Further verifies if the advertised FIFO (Sensor.getFifoMaxEventCount()) is actually allocated
+ * for the sensor.
+ *
+ */
+public class SensorBatchingFifoTest extends SensorTestCase {
+    private static final int ACCELEROMETER_MIN_FIFO_LENGTH = 3000;
+    private static final int UNCAL_MAGNETOMETER_MIN_FIFO_LENGTH = 600;
+    private static final int PRESSURE_MIN_FIFO_LENGTH = 300;
+    private static final int GAME_ROTATION_VECTOR_MIN_FIFO_LENGTH = 300;
+    private static final int PROXIMITY_SENSOR_MIN_FIFO_LENGTH = 300;
+    private static final int STEP_DETECTOR_MIN_FIFO_LENGTH = 100;
+
+    private static final int SAMPLING_INTERVAL = 1000; /* every 1ms */
+    private static final String TAG = "batching_fifo_test";
+
+    private SensorManager mSensorManager;
+    private boolean mHasHifiSensors;
+    @Override
+    protected void setUp() throws Exception {
+        mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+        mHasHifiSensors = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_HIFI_SENSORS);
+    }
+
+    public void testAccelerometerFifoLength() throws Throwable {
+        if (!mHasHifiSensors) return;
+        runBatchingSensorFifoTest(
+                Sensor.TYPE_ACCELEROMETER,
+                checkMinFifoLength(Sensor.TYPE_ACCELEROMETER, ACCELEROMETER_MIN_FIFO_LENGTH));
+    }
+
+    public void testUncalMagnetometerFifoLength() throws Throwable {
+        if (!mHasHifiSensors) return;
+        runBatchingSensorFifoTest(
+                Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+                checkMinFifoLength(
+                        Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+                        UNCAL_MAGNETOMETER_MIN_FIFO_LENGTH));
+    }
+
+    public void testPressureFifoLength() throws Throwable {
+        if (!mHasHifiSensors) return;
+        runBatchingSensorFifoTest(
+                Sensor.TYPE_PRESSURE,
+                checkMinFifoLength(Sensor.TYPE_PRESSURE, PRESSURE_MIN_FIFO_LENGTH));
+    }
+
+    public void testGameRotationVectorFifoLength() throws Throwable {
+        if (!mHasHifiSensors) return;
+        runBatchingSensorFifoTest(
+                Sensor.TYPE_GAME_ROTATION_VECTOR,
+                checkMinFifoLength(
+                        Sensor.TYPE_GAME_ROTATION_VECTOR, GAME_ROTATION_VECTOR_MIN_FIFO_LENGTH));
+    }
+
+    public void testProximityFifoLength() throws Throwable {
+        if (!mHasHifiSensors) return;
+        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        if (sensor != null) {
+            assertTrue(sensor.getFifoReservedEventCount() <= PROXIMITY_SENSOR_MIN_FIFO_LENGTH);
+        }
+    }
+
+    public void testStepDetectorFifoLength() throws Throwable {
+        if (!mHasHifiSensors) return;
+        checkMinFifoLength(Sensor.TYPE_STEP_DETECTOR, STEP_DETECTOR_MIN_FIFO_LENGTH);
+    }
+
+    private int checkMinFifoLength(int sensorType, int minRequiredLength) {
+        Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
+        assertTrue(String.format("sensor of type=%d (null)", sensorType), sensor != null);
+        int maxFifoLength = sensor.getFifoReservedEventCount();
+        assertTrue(String.format("Sensor=%s, min required fifo length=%d actual=%d",
+                    sensor.getName(), minRequiredLength, maxFifoLength),
+                    maxFifoLength >= minRequiredLength);
+        return maxFifoLength;
+    }
+
+    private void runBatchingSensorFifoTest(int sensorType, int fifoLength) throws Throwable {
+        if (fifoLength == 0) {
+            return;
+        }
+        Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
+        TestSensorEnvironment environment =  new TestSensorEnvironment(getContext(),
+                sensor,
+                false,
+                sensor.getMinDelay(),
+                Integer.MAX_VALUE);
+
+        TestSensorOperation op = TestSensorOperation.createOperation(environment,
+                sensor.getFifoReservedEventCount() * 2);
+        op.addVerification(FifoLengthVerification.getDefault(environment));
+        op.execute(getCurrentTestNode());
+        op.getStats().log(TAG);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
index f0f0186..413639a 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
@@ -41,6 +41,7 @@
     public static final String DELIMITER = "__";
 
     public static final String ERROR = "error";
+    public static final String EVENT_FIFO_LENGTH = "event_fifo_length_observed";
     public static final String EVENT_GAP_COUNT_KEY = "event_gap_count";
     public static final String EVENT_GAP_POSITIONS_KEY = "event_gap_positions";
     public static final String EVENT_OUT_OF_ORDER_COUNT_KEY = "event_out_of_order_count";
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FifoLengthVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FifoLengthVerification.java
new file mode 100644
index 0000000..09bbdc8
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FifoLengthVerification.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2015 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.hardware.cts.helpers.sensorverification;
+
+import junit.framework.Assert;
+
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+import android.util.Log;
+
+import java.util.LinkedList;
+
+/**
+ * A {@link ISensorVerification} which verifies that each batch of events has the FIFO
+ *  length within the 5% of the expected value.
+ */
+public class FifoLengthVerification extends AbstractSensorVerification {
+    public static final String PASSED_KEY = "fifo_length_passed";
+
+    private static double FIFO_LENGTH_TOLERANCE = 0.8;
+
+    private final int mExpectedFifoLength;
+
+    private int mIndex = 0;
+    private LinkedList<Long> mRecvdTimeStampDiffs = new LinkedList<>();
+    private long mPrevRecvdTimeStampMs = -1,  mExpectedReportLatencyUs;
+
+    /**
+     * Construct a {@link FifoLengthVerification}
+     *
+     * @param expectedLength the expected FIFO length for the batch.
+     */
+    public FifoLengthVerification(int expectedLength, long expectedReportLatencyUs) {
+        mExpectedFifoLength = expectedLength;
+        mExpectedReportLatencyUs = expectedReportLatencyUs;
+    }
+
+    /**
+     * Get the default {@link FifoLengthVerification}.
+     *
+     * @param environment the test environment
+     * @return the verification or null if the verification is not a continuous mode sensor.
+     */
+    public static FifoLengthVerification getDefault(
+            TestSensorEnvironment environment) {
+        if (environment.getSensor().getReportingMode() != Sensor.REPORTING_MODE_CONTINUOUS) {
+            return null;
+        }
+        long expectedReportLatencyUs = environment.getMaxReportLatencyUs();
+        long fifoMaxEventCount = environment.getSensor().getFifoMaxEventCount();
+        int maximumExpectedSamplingPeriodUs = environment.getMaximumExpectedSamplingPeriodUs();
+        if (fifoMaxEventCount > 0 && maximumExpectedSamplingPeriodUs != Integer.MAX_VALUE) {
+            long fifoBasedReportLatencyUs = fifoMaxEventCount * maximumExpectedSamplingPeriodUs;
+            // If the device goes into suspend mode and the sensor is a non wake-up sensor, the
+            // FIFO will keep overwriting itself and the reportLatency will be equal to the time
+            // it takes to fill up the FIFO.
+            if (environment.isDeviceSuspendTest() && !environment.getSensor().isWakeUpSensor()) {
+                expectedReportLatencyUs = fifoBasedReportLatencyUs;
+            } else {
+                // In this case the sensor under test is either a wake-up sensor OR it
+                // is a non wake-up sensor but the device does not go into suspend.
+                // So the expected delay of a sensor_event is the minimum of the
+                // fifoBasedReportLatencyUs and the requested latency by the application.
+                expectedReportLatencyUs = Math.min(expectedReportLatencyUs,
+                        fifoBasedReportLatencyUs);
+            }
+        }
+
+        return new FifoLengthVerification(environment.getSensor().getFifoMaxEventCount(),
+                expectedReportLatencyUs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void verify(TestSensorEnvironment environment, SensorStats stats) {
+        if (mExpectedFifoLength <= 0) {
+            // the expected length isn't defined.
+            stats.addValue(PASSED_KEY, "skipped (no fifo length requirements)");
+            return;
+        }
+        int batchCount = 0;
+        boolean success, endofbatch = false;
+        long maxTsDiff = -1;
+        for (long timestampDiff : mRecvdTimeStampDiffs) {
+            if (maxTsDiff < timestampDiff) maxTsDiff = timestampDiff;
+            // Any event that arrives within before 0.5*expectedReportLatency is considered
+            // to be in the same batch of events, else it is considered as the beginning of a new
+            // batch.
+            if (timestampDiff < 0.5 * mExpectedReportLatencyUs/1000) {
+                batchCount++;
+            } else {
+                endofbatch = true;
+                break;
+            }
+        }
+        Log.v("SensorFifoLengthVerification", "batchCount =" +batchCount + " mExpected=" +
+                mExpectedFifoLength + " maxTsDiff=" + maxTsDiff + " expectedReportLatency=" +
+                mExpectedReportLatencyUs/1000 + " recvdEventCount=" + mRecvdTimeStampDiffs.size());
+        // Fifo length must be at least 80% of the advertized FIFO length.
+        success = endofbatch && (batchCount >= mExpectedFifoLength * FIFO_LENGTH_TOLERANCE);
+
+        stats.addValue(PASSED_KEY, success);
+        stats.addValue(SensorStats.EVENT_FIFO_LENGTH, batchCount);
+
+        if (!success) {
+            StringBuilder sb = new StringBuilder();
+            if (endofbatch) {
+                sb.append(String.format("Fifo length verification error: Fifo length found=%d," +
+                            "expected fifo length ~%d, maxReportLatencyObserved=%dms, " +
+                            "expectedMaxReportLantency=%dms",
+                            batchCount, mExpectedFifoLength, maxTsDiff,
+                            mExpectedReportLatencyUs/1000));
+            } else {
+               sb.append(String.format("End of batch NOT observed maxReportLatencyObserved=%dms,"
+                            + " expectedMaxReportLantency=%dms", maxTsDiff,
+                            mExpectedReportLatencyUs/1000));
+            }
+            Assert.fail(sb.toString());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FifoLengthVerification clone() {
+        return new FifoLengthVerification(mExpectedFifoLength, mExpectedReportLatencyUs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void addSensorEventInternal(TestSensorEvent event) {
+        if (mPrevRecvdTimeStampMs == -1) {
+            mPrevRecvdTimeStampMs = (long)event.receivedTimestamp/(1000 * 1000);
+        } else {
+            long currRecvdTimeStampMs = (long) event.receivedTimestamp/(1000 * 1000);
+            mRecvdTimeStampDiffs.add(currRecvdTimeStampMs - mPrevRecvdTimeStampMs);
+            mPrevRecvdTimeStampMs = currRecvdTimeStampMs;
+        }
+        mIndex++;
+    }
+}