Add timestamp, atomic counter test and test for corner cases

android.hardware.cts.SensorDirectReportTest
  * testTimestampAccel
  * testTimestampGyro
  * testTimestampMag
  * testAtomicCounterAccel
  * testAtomicCounterGyro
  * testAtomicCounterMag
  * testRegisterMultipleChannels
  * testReconfigure

Test: SensorDirectReportTest passes on test devices
Change-Id: I4730695d684ae52ae1243aa46332cff2e35be442
Merged-In: I4730695d684ae52ae1243aa46332cff2e35be442
diff --git a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
index 143b3a7..fd3935d 100644
--- a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
@@ -54,10 +54,22 @@
  *   - test<Sensor><SharedMemoryType><RateLevel>
  *     tests basic operation of sensor in direct report mode at various rate level specification.
  *   - testRateIndependency<Sensor1><Sensor2>SingleChannel
- *     tests two sensors in the same direct channel are able to run at different rate.
+ *     tests if two sensors in the same direct channel are able to run at different rates.
  *   - testRateIndependency<Sensor>MultiChannel
- *     tests a sensor is able to be configured to different rate level for multiple channels.
+ *     tests if a sensor is able to be configured to different rate levels for multiple channels.
  *   - testRateIndependency<Sensor>MultiMode
+ *     tests if a sensor is able to report at different rates in direct report mode and traditional
+ *     report mode (polling).
+ *   - testTimestamp<Sensor>
+ *     tests if the timestamp is correct both in absolute sense and relative to traditional report.
+ *   - testAtomicCounter<Sensor>
+ *     test if atomic counter is increased as specified and if sensor event content is fully updated
+ *     before update of atomic counter.
+ *   - testRegisterMultipleChannels
+ *     test scenarios when multiple channels are registered simultaneously.
+ *   - testReconfigure
+ *     test channel reconfiguration (configure to a rate level; configure to stop; configure to
+ *     another rate level)
  */
 public class SensorDirectReportTest extends SensorTestCase {
     private static final String TAG = "SensorDirectReportTest";
@@ -75,8 +87,8 @@
     private static final float FREQ_UPPER_BOUND_POLL = 2.00f;
 
     // sensor reading assumption
-    private static final float GRAVITY_MIN = 9.81f - 0.5f;
-    private static final float GRAVITY_MAX = 9.81f + 0.5f;
+    private static final float GRAVITY_MIN = 9.81f - 1.0f;
+    private static final float GRAVITY_MAX = 9.81f + 1.0f;
     private static final float GYRO_NORM_MAX = 0.1f;
 
     // test constants
@@ -369,6 +381,82 @@
         runMultiModeRateIndependencyTestGroup(Sensor.TYPE_MAGNETIC_FIELD);
     }
 
+    public void testTimestampAccel() {
+        runTimestampTestGroup(Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void testTimestampGyro() {
+        runTimestampTestGroup(Sensor.TYPE_GYROSCOPE);
+    }
+
+    public void testTimestampMag() {
+        runTimestampTestGroup(Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    public void testAtomicCounterAccel() {
+        for (int memType : POSSIBLE_CHANNEL_TYPES) {
+            runAtomicCounterTest(Sensor.TYPE_ACCELEROMETER, memType);
+        }
+    }
+
+    public void testAtomicCounterGyro() {
+        for (int memType : POSSIBLE_CHANNEL_TYPES) {
+            runAtomicCounterTest(Sensor.TYPE_GYROSCOPE, memType);
+        }
+    }
+
+    public void testAtomicCounterMag() {
+        for (int memType : POSSIBLE_CHANNEL_TYPES) {
+            runAtomicCounterTest(Sensor.TYPE_MAGNETIC_FIELD, memType);
+        }
+    }
+
+    public void testRegisterMultipleChannels() throws AssertionError {
+        resetEvent();
+        freeSharedMemory();
+
+        for (int memType : POSSIBLE_CHANNEL_TYPES) {
+            if (!isMemoryTypeNeeded(memType)) {
+                continue;
+            }
+
+            for (int repeat = 0; repeat < 10; ++repeat) {
+                // allocate new memory every time
+                allocateSharedMemory();
+
+                mChannel = prepareDirectChannel(memType, false /* secondary */);
+                assertNotNull("mChannel is null", mChannel);
+
+                mChannelSecondary = prepareDirectChannel(memType, true /* secondary */);
+                assertNotNull("mChannelSecondary is null", mChannelSecondary);
+
+                if (mChannel != null) {
+                    mChannel.close();
+                    mChannel = null;
+                }
+                if (mChannelSecondary != null) {
+                    mChannelSecondary.close();
+                    mChannelSecondary = null;
+                }
+
+                // free shared memory
+                freeSharedMemory();
+            }
+        }
+    }
+
+    public void testReconfigure() {
+        TestResultCollector c = new TestResultCollector("testReconfigure", TAG);
+
+        for (int type : POSSIBLE_SENSOR_TYPES) {
+            for (int memType : POSSIBLE_CHANNEL_TYPES) {
+                c.perform(() -> { runReconfigureTest(type, memType);},
+                        String.format("sensor type %d, mem type %d", type, memType));
+            }
+        }
+        c.judge();
+    }
+
     private void runSingleChannelRateIndependencyTestGroup(int type1, int type2) {
         if (type1 == type2) {
             throw new IllegalArgumentException("Cannot run single channel rate independency test "
@@ -441,6 +529,24 @@
         c.judge();
     }
 
+    private void runTimestampTestGroup(int sensorType) {
+        String stype = SensorCtsHelper.sensorTypeShortString(sensorType);
+
+        TestResultCollector c =
+                new TestResultCollector("testTimestamp" + stype, TAG);
+
+        for (int rateLevel : POSSIBLE_RATE_LEVELS) {
+            for (int memType : POSSIBLE_CHANNEL_TYPES) {
+                c.perform(
+                        () -> {
+                            runTimestampTest(sensorType, rateLevel, memType);
+                        },
+                        String.format("(%s, rate %d, memtype %d)", stype, rateLevel, memType));
+            }
+        }
+        c.judge();
+    }
+
     private void runSensorDirectReportTest(int sensorType, int memType, int rateLevel)
             throws AssertionError {
         Sensor s = mSensorManager.getDefaultSensor(sensorType);
@@ -630,6 +736,137 @@
         }
     }
 
+    private void runTimestampTest(int type, int rateLevel, int memType) {
+        Sensor s = mSensorManager.getDefaultSensor(type);
+        if (s == null
+                || s.getHighestDirectReportRateLevel() < rateLevel
+                || !s.isDirectChannelTypeSupported(memType)) {
+            return;
+        }
+        resetEvent();
+
+        mChannel = prepareDirectChannel(memType, false /* secondary */);
+        assertTrue("createDirectChannel failed", mChannel != null);
+
+        SensorEventCollection listener = new SensorEventCollection(s);
+
+        try {
+            float nominalFreq = getNominalFreq(rateLevel);
+            int samplingPeriodUs = Math.max((int) (1e6f / nominalFreq), s.getMinDelay());
+
+            assertTrue("Shared memory is not formatted",
+                       isSharedMemoryFormatted(memType));
+
+            int token = mChannel.configure(s, rateLevel);
+            assertTrue("configure direct mChannel failed", token > 0);
+
+            boolean registerRet = mSensorManager.registerListener(listener, s, samplingPeriodUs);
+            assertTrue("Register listener failed", registerRet);
+
+            List<DirectReportSensorEvent> events = collectSensorEventsRealtime(
+                    memType, false /*secondary*/, TEST_RUN_TIME_PERIOD_MILLISEC);
+            assertTrue("Realtime event collection failed", events != null);
+            assertTrue("Realtime event collection got no data", events.size() > 0);
+
+            //stop sensor and analyze content
+            mChannel.configure(s, SensorDirectChannel.RATE_STOP);
+            mSensorManager.unregisterListener(listener);
+
+            // check rate
+            checkTimestampRelative(events, listener.getEvents());
+            checkTimestampAbsolute(events);
+        } finally {
+            mChannel.close();
+            mChannel = null;
+        }
+    }
+
+    private void runAtomicCounterTest(int sensorType, int memType) throws AssertionError {
+        Sensor s = mSensorManager.getDefaultSensor(sensorType);
+        if (s == null
+                || s.getHighestDirectReportRateLevel() == SensorDirectChannel.RATE_STOP
+                || !s.isDirectChannelTypeSupported(memType)) {
+            return;
+        }
+        resetEvent();
+
+        mChannel = prepareDirectChannel(memType, false /* secondary */);
+        assertTrue("createDirectChannel failed", mChannel != null);
+
+        try {
+            assertTrue("Shared memory is not formatted", isSharedMemoryFormatted(memType));
+            waitBeforeStartSensor();
+
+            //int token = mChannel.configure(s, SensorDirectChannel.RATE_FAST);
+            int token = mChannel.configure(s, s.getHighestDirectReportRateLevel());
+            assertTrue("configure direct mChannel failed", token > 0);
+
+            checkAtomicCounterUpdate(memType, 30 * 1000); // half min
+
+            //stop sensor and analyze content
+            mChannel.configure(s, SensorDirectChannel.RATE_STOP);
+        } finally {
+            mChannel.close();
+            mChannel = null;
+        }
+    }
+
+    private void runReconfigureTest(int type, int memType) {
+        Sensor s = mSensorManager.getDefaultSensor(type);
+        if (s == null
+                || s.getHighestDirectReportRateLevel() == SensorDirectChannel.RATE_STOP
+                || !s.isDirectChannelTypeSupported(memType)) {
+            return;
+        }
+        resetEvent();
+
+        mChannel = prepareDirectChannel(memType, false /* secondary */);
+        assertTrue("createDirectChannel failed", mChannel != null);
+
+        try {
+            assertTrue("Shared memory is not formatted", isSharedMemoryFormatted(memType));
+            waitBeforeStartSensor();
+
+            int offset = 0;
+            long counter = 1;
+            List<Integer> rateLevels = new ArrayList<>();
+            List<DirectReportSensorEvent> events;
+
+            rateLevels.add(s.getHighestDirectReportRateLevel());
+            rateLevels.add(s.getHighestDirectReportRateLevel());
+            if (s.getHighestDirectReportRateLevel() != SensorDirectChannel.RATE_NORMAL) {
+                rateLevels.add(SensorDirectChannel.RATE_NORMAL);
+            }
+
+            for (int rateLevel : rateLevels) {
+                int token = mChannel.configure(s, rateLevel);
+                assertTrue("configure direct mChannel failed", token > 0);
+
+                events = collectSensorEventsRealtime(memType, false /*secondary*/,
+                                                     TEST_RUN_TIME_PERIOD_MILLISEC,
+                                                     offset, counter);
+                // stop sensor
+                mChannel.configure(s, SensorDirectChannel.RATE_STOP);
+                checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, events, type, rateLevel);
+
+                // collect all events after stop
+                events = collectSensorEventsRealtime(memType, false /*secondary*/,
+                                                     REST_PERIOD_BEFORE_TEST_MILLISEC,
+                                                     offset, counter);
+                if (events.size() > 0) {
+                    offset += (events.size() * SENSORS_EVENT_SIZE ) % SHARED_MEMORY_SIZE;
+                    counter = events.get(events.size() - 1).serial;
+                }
+            }
+
+            // finally stop the report
+            mChannel.configure(s, SensorDirectChannel.RATE_STOP);
+        } finally {
+            mChannel.close();
+            mChannel = null;
+        }
+    }
+
     private void waitBeforeStartSensor() {
         // wait for sensor system to come to a rest after previous test to avoid flakiness.
         try {
@@ -648,6 +885,147 @@
         }
     }
 
+    private List<DirectReportSensorEvent> collectSensorEventsRealtime(
+            int memType, boolean secondary, int timeoutMs) {
+        return collectSensorEventsRealtime(memType, secondary, timeoutMs,
+                                          0 /*initialOffset*/, 1l /*initialCounter*/);
+    }
+
+    private List<DirectReportSensorEvent> collectSensorEventsRealtime(
+            int memType, boolean secondary, int timeoutMs, int initialOffset, long initialCounter) {
+        List<DirectReportSensorEvent> events = new ArrayList<>();
+        long endTime = SystemClock.elapsedRealtime() + timeoutMs;
+
+        long atomicCounter = initialCounter;
+        int offset = initialOffset;
+
+        long timeA = SystemClock.elapsedRealtimeNanos();
+        boolean synced = false;
+        int filtered = 0;
+
+        while (SystemClock.elapsedRealtime() < endTime) {
+            if (!readSharedMemory(
+                    memType, secondary, offset + ATOMIC_COUNTER_OFFSET, ATOMIC_COUNTER_SIZE)) {
+                return null;
+            }
+
+            long timeB = SystemClock.elapsedRealtimeNanos();
+            if (timeB - timeA > 1_000_000L ) { // > 1ms
+                synced = false;
+            }
+            timeA = timeB;
+
+            if (readAtomicCounter(offset) == atomicCounter) {
+                // read entire event again and parse
+                if (!readSharedMemory(memType, secondary, offset, SENSORS_EVENT_SIZE)) {
+                    return null;
+                }
+                DirectReportSensorEvent e = mEventPool.get();
+                assertNotNull("cannot get event from reserve", e);
+                parseSensorEvent(offset, e);
+
+                atomicCounter += 1;
+                if (synced) {
+                    events.add(e);
+                } else {
+                    ++filtered;
+                }
+
+                offset += SENSORS_EVENT_SIZE;
+                if (offset + SENSORS_EVENT_SIZE > SHARED_MEMORY_SIZE) {
+                    offset = 0;
+                }
+            } else {
+                synced = true;
+            }
+        }
+        Log.d(TAG, "filtered " + filtered + " events, remain " + events.size() + " events");
+        return events;
+    }
+
+    private void checkAtomicCounterUpdate(int memType, int timeoutMs) {
+        List<DirectReportSensorEvent> events = new ArrayList<>();
+        long endTime = SystemClock.elapsedRealtime() + timeoutMs;
+
+        boolean lastValid = false;
+        long atomicCounter = 1;
+        int lastOffset = 0;
+        int offset = 0;
+
+        byte[] lastArray = new byte[SENSORS_EVENT_SIZE];
+        DirectReportSensorEvent e = getEvent();
+
+        while (SystemClock.elapsedRealtime() < endTime) {
+            if (!readSharedMemory(memType, false/*secondary*/, lastOffset, SENSORS_EVENT_SIZE)
+                    || !readSharedMemory(memType, false/*secondary*/,
+                                         offset + ATOMIC_COUNTER_OFFSET, ATOMIC_COUNTER_SIZE)) {
+                throw new IllegalStateException("cannot read shared memory, type " + memType);
+            }
+
+            if (lastValid) {
+                boolean failed = false;
+                int i;
+                for (i = 0; i < SENSORS_EVENT_SIZE; ++i) {
+                    if (lastArray[i] != mBuffer[lastOffset + i]) {
+                        failed = true;
+                        break;
+                    }
+                }
+
+                if (failed) {
+                    byte[] currentArray = new byte[SENSORS_EVENT_SIZE];
+                    System.arraycopy(mBuffer, lastOffset, currentArray, 0, SENSORS_EVENT_SIZE);
+
+                    // wait for 100ms and read again to see if the change settle
+                    try {
+                        SensorCtsHelper.sleep(100, TimeUnit.MILLISECONDS);
+                    } catch (InterruptedException ex) {
+                        Thread.currentThread().interrupt();
+                    }
+
+                    byte[] delayedRead = new byte[SENSORS_EVENT_SIZE];
+                    if (!readSharedMemory(
+                                memType, false/*secondary*/, lastOffset, SENSORS_EVENT_SIZE)) {
+                        throw new IllegalStateException(
+                                "cannot read shared memory, type " + memType);
+                    }
+                    System.arraycopy(mBuffer, lastOffset, delayedRead, 0, SENSORS_EVENT_SIZE);
+
+                    fail(String.format(
+                            "At offset %d(0x%x), byte %d(0x%x) changed after atomicCounter"
+                                + "(expecting %d, 0x%x) update, old = [%s], new = [%s], "
+                                + "delayed = [%s]",
+                            lastOffset, lastOffset, i, i, atomicCounter, atomicCounter,
+                            SensorCtsHelper.bytesToHex(lastArray, -1, -1),
+                            SensorCtsHelper.bytesToHex(currentArray, -1, -1),
+                            SensorCtsHelper.bytesToHex(delayedRead, -1, -1)));
+                }
+            }
+
+            if (readAtomicCounter(offset) == atomicCounter) {
+                // read entire event again and parse
+                if (!readSharedMemory(memType, false/*secondary*/, offset, SENSORS_EVENT_SIZE)) {
+                    throw new IllegalStateException("cannot read shared memory, type " + memType);
+                }
+                parseSensorEvent(offset, e);
+
+                atomicCounter += 1;
+
+                lastOffset = offset;
+                System.arraycopy(mBuffer, lastOffset, lastArray, 0, SENSORS_EVENT_SIZE);
+                lastValid = true;
+
+                offset += SENSORS_EVENT_SIZE;
+                if (offset + SENSORS_EVENT_SIZE > SHARED_MEMORY_SIZE) {
+                    offset = 0;
+                }
+            }
+        }
+        Log.d(TAG, "at finish checkAtomicCounterUpdate has atomic counter = " + atomicCounter);
+        // atomicCounter will not wrap back in reasonable amount of time
+        assertTrue("Realtime event collection got no data", atomicCounter != 1);
+    }
+
     private MemoryFile allocateMemoryFile() {
         MemoryFile memFile = null;
         try {
@@ -1019,6 +1397,76 @@
         return minMax;
     }
 
+    private void checkTimestampAbsolute(List<DirectReportSensorEvent> events) {
+        final int MAX_DETAIL_ITEM = 10;
+
+        StringBuffer buf = new StringBuffer();
+        int oneMsEarlyCount = 0;
+        int fiveMsLateCount = 0;
+        int tenMsLateCount = 0;
+        int errorCount = 0;
+
+        for (int i = 0; i < events.size(); ++i) {
+            DirectReportSensorEvent e = events.get(i);
+            long d = e.arrivalTs - e.ts;
+            boolean oneMsEarly = d < -1000_000;
+            boolean fiveMsLate = d > 5000_000;
+            boolean tenMsLate = d > 10_000_000;
+
+            if (oneMsEarly || fiveMsLate || tenMsLate) {
+                oneMsEarlyCount += oneMsEarly ? 1 : 0;
+                fiveMsLateCount += fiveMsLate ? 1 : 0;
+                tenMsLateCount += tenMsLate ? 1 : 0;
+
+                if (errorCount++ < MAX_DETAIL_ITEM) {
+                    buf.append("[").append(i).append("] diff = ").append(d / 1e6f).append(" ms; ");
+                }
+            }
+        }
+
+        Log.d(TAG, String.format("Irregular timestamp, %d, %d, %d out of %d",
+                    oneMsEarlyCount, fiveMsLateCount, tenMsLateCount, events.size()));
+
+        if (CHECK_ABSOLUTE_LATENCY) {
+            assertTrue(String.format(
+                    "Timestamp error, out of %d events, %d is >1ms early, %d is >5ms late, "
+                        + "%d is >10ms late, details: %s%s",
+                        events.size(), oneMsEarlyCount, fiveMsLateCount, tenMsLateCount,
+                        buf.toString(), errorCount > MAX_DETAIL_ITEM ? "..." : ""),
+                    oneMsEarlyCount == 0
+                        && fiveMsLateCount <= events.size() / 20
+                        && tenMsLateCount <= events.size() / 100);
+        }
+    }
+
+    private void checkTimestampRelative(List<DirectReportSensorEvent> directEvents,
+                                        List<DirectReportSensorEvent> pollEvents) {
+        if (directEvents.size() < 10 || pollEvents.size() < 10) {
+            // cannot check with so few data points
+            return;
+        }
+
+        long directAverageLatency = 0;
+        for (DirectReportSensorEvent e : directEvents) {
+            directAverageLatency += e.arrivalTs - e.ts;
+        }
+        directAverageLatency /= directEvents.size();
+
+        long pollAverageLatency = 0;
+        for (DirectReportSensorEvent e : pollEvents) {
+            pollAverageLatency += e.arrivalTs - e.ts;
+        }
+        pollAverageLatency /= pollEvents.size();
+
+        Log.d(TAG, String.format("Direct, poll latency = %f, %f ms",
+                directAverageLatency / 1e6f, pollAverageLatency / 1e6f));
+        assertTrue(
+                String.format("Direct, poll latency = %f, %f ms, expect direct < poll",
+                    directAverageLatency / 1e6f,
+                    pollAverageLatency / 1e6f),
+                directAverageLatency < pollAverageLatency + 1000_000);
+    }
+
     private int[] calculateExpectedNEventsUs(int timeMs, int samplingPeriodUs) {
         int[] minMax = new int[2];
         minMax[0] = Math.max((int) Math.floor(
@@ -1172,4 +1620,32 @@
         ev.y = mByteBuffer.getFloat();
         ev.z = mByteBuffer.getFloat();
     }
+
+    // parse sensors_event_t and fill information into DirectReportSensorEvent
+    private static void parseSensorEvent(byte [] buf, int offset, DirectReportSensorEvent ev) {
+        ByteBuffer b = ByteBuffer.wrap(buf, offset, SENSORS_EVENT_SIZE);
+        b.order(NATIVE_BYTE_ORDER);
+
+        ev.size = b.getInt();
+        ev.token = b.getInt();
+        ev.type = b.getInt();
+        ev.serial = ((long) b.getInt()) & 0xFFFFFFFFl; // signed=>unsigned
+        ev.ts = b.getLong();
+        ev.arrivalTs = SystemClock.elapsedRealtimeNanos();
+        ev.x = b.getFloat();
+        ev.y = b.getFloat();
+        ev.z = b.getFloat();
+    }
+
+    private long readAtomicCounter(int offset) {
+        mByteBuffer.position(offset + ATOMIC_COUNTER_OFFSET);
+        return ((long) mByteBuffer.getInt()) & 0xFFFFFFFFl; // signed => unsigned
+    }
+
+    private static long readAtomicCounter(byte [] buf, int offset) {
+        ByteBuffer b = ByteBuffer.wrap(buf, offset + ATOMIC_COUNTER_OFFSET, ATOMIC_COUNTER_SIZE);
+        b.order(ByteOrder.nativeOrder());
+
+        return ((long) b.getInt()) & 0xFFFFFFFFl; // signed => unsigned
+    }
 }