Adding rate independency test for sensor direct report

Adding tests to verify rate independency,

  * between different sensors in one single channel.
  * between sensors in different channel.
  * between direct report and traditional poll report.

A few tests are added to known-failure list before various criteria
are finalized.

Test: test device passed SensorDirectReportTest
Bug: 38420898

Change-Id: If9b30025dfb8d6ff2798efe0b65288e3cb39c006
Merged-In: If9b30025dfb8d6ff2798efe0b65288e3cb39c006
diff --git a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
index 78ac59d..143b3a7 100644
--- a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
@@ -22,15 +22,21 @@
 import android.hardware.SensorAdditionalInfo;
 import android.hardware.SensorDirectChannel;
 import android.hardware.SensorEventCallback;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorCtsHelper.TestResultCollector;
 import android.os.MemoryFile;
+import android.os.SystemClock;
 import android.util.Log;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -43,6 +49,15 @@
  *   - SensorDirectChannel.*
  *   - Sensor.getHighestDirectReportRateLevel()
  *   - Sensor.isDirectChannelTypeSupported()
+ *
+ * Tests:
+ *   - 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.
+ *   - testRateIndependency<Sensor>MultiChannel
+ *     tests a sensor is able to be configured to different rate level for multiple channels.
+ *   - testRateIndependency<Sensor>MultiMode
  */
 public class SensorDirectReportTest extends SensorTestCase {
     private static final String TAG = "SensorDirectReportTest";
@@ -55,6 +70,10 @@
     private static final float FREQ_LOWER_BOUND = 0.55f;
     private static final float FREQ_UPPER_BOUND = 2.2f;
 
+    // actuall value is allowed to be 90% to 200% of nominal value in poll() interface
+    private static final float FREQ_LOWER_BOUND_POLL = 0.90f;
+    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;
@@ -65,21 +84,61 @@
     private static final int TEST_RUN_TIME_PERIOD_MILLISEC = 5000;
     private static final int ALLOWED_SENSOR_INIT_TIME_MILLISEC = 500;
     private static final int SENSORS_EVENT_SIZE = 104;
+    private static final int ATOMIC_COUNTER_OFFSET = 12;
+    private static final int ATOMIC_COUNTER_SIZE = 4;
     private static final int SENSORS_EVENT_COUNT = 10240; // 800Hz * 2.2 * 5 sec + extra
     private static final int SHARED_MEMORY_SIZE = SENSORS_EVENT_COUNT * SENSORS_EVENT_SIZE;
     private static final float MERCY_FACTOR = 0.1f;
+    private static final boolean CHECK_ABSOLUTE_LATENCY = false;
+
+    // list of rate levels being tested
+    private static final int[] POSSIBLE_RATE_LEVELS = new int[] {
+            SensorDirectChannel.RATE_NORMAL,
+            SensorDirectChannel.RATE_FAST,
+            SensorDirectChannel.RATE_VERY_FAST
+        };
+
+    // list of channel types being tested
+    private static final int[] POSSIBLE_CHANNEL_TYPES = new int [] {
+            SensorDirectChannel.TYPE_MEMORY_FILE,
+            SensorDirectChannel.TYPE_HARDWARE_BUFFER
+        };
+
+    // list of sensor types being tested
+    private static final int[] POSSIBLE_SENSOR_TYPES = new int [] {
+            Sensor.TYPE_ACCELEROMETER,
+            Sensor.TYPE_GYROSCOPE,
+            Sensor.TYPE_MAGNETIC_FIELD
+        };
+
+    // list of sampling period being tested
+    private static final int[] POSSIBLE_SAMPLE_PERIOD_US = new int [] {
+            200_000, // Normal 5 Hz
+            66_667,  // UI    15 Hz
+            20_000,  // Game  50 Hz
+            5_000,   // 200Hz
+            0        // fastest
+        };
+
+    private static final ByteOrder NATIVE_BYTE_ORDER = ByteOrder.nativeOrder();
 
     private static native boolean nativeReadHardwareBuffer(HardwareBuffer hardwareBuffer,
             byte[] buffer, int srcOffset, int destOffset, int count);
 
     private boolean mNeedMemoryFile;
     private MemoryFile mMemoryFile;
+    private MemoryFile mMemoryFileSecondary;
     private boolean mNeedHardwareBuffer;
     private HardwareBuffer mHardwareBuffer;
-    private byte[] mBuffer = new byte[SHARED_MEMORY_SIZE];
+    private HardwareBuffer mHardwareBufferSecondary;
+    private ByteBuffer mByteBuffer;
+    private byte[] mBuffer;
 
     private SensorManager mSensorManager;
     private SensorDirectChannel mChannel;
+    private SensorDirectChannel mChannelSecondary;
+
+    private EventPool mEventPool;
 
     static {
         System.loadLibrary("cts-sensors-ndk-jni");
@@ -87,18 +146,19 @@
 
     @Override
     protected void setUp() throws Exception {
+        super.setUp();
+
+        mByteBuffer = ByteBuffer.allocate(SHARED_MEMORY_SIZE);
+        mBuffer = mByteBuffer.array();
+        mByteBuffer.order(ByteOrder.nativeOrder());
+
+        mEventPool = new EventPool(10 * SENSORS_EVENT_COUNT);
         mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
 
         mNeedMemoryFile = isMemoryTypeNeeded(SensorDirectChannel.TYPE_MEMORY_FILE);
         mNeedHardwareBuffer = isMemoryTypeNeeded(SensorDirectChannel.TYPE_HARDWARE_BUFFER);
 
-        if (mNeedMemoryFile) {
-            mMemoryFile = allocateMemoryFile();
-        }
-
-        if (mNeedHardwareBuffer) {
-            mHardwareBuffer = allocateHardwareBuffer();
-        }
+        allocateSharedMemory();
     }
 
     @Override
@@ -108,22 +168,23 @@
             mChannel = null;
         }
 
-        if (mMemoryFile != null) {
-            mMemoryFile.close();
-            mMemoryFile = null;
+        if (mChannelSecondary != null) {
+            mChannelSecondary.close();
+            mChannelSecondary = null;
         }
 
-        if (mHardwareBuffer != null) {
-            mHardwareBuffer.close();
-            mHardwareBuffer = null;
-        }
+        freeSharedMemory();
+        super.tearDown();
     }
 
     public void testSharedMemoryAllocation() throws AssertionError {
-        assertTrue("allocating MemoryFile returned null",
-                !mNeedMemoryFile || mMemoryFile != null);
-        assertTrue("allocating HardwareBuffer returned null",
-                !mNeedHardwareBuffer || mHardwareBuffer != null);
+        assertTrue("allocating MemoryFile returned null: "
+                        + (mMemoryFile == null) + ", " + (mMemoryFileSecondary == null),
+                   !mNeedMemoryFile || (mMemoryFile != null && mMemoryFileSecondary != null));
+        assertTrue("allocating HardwareBuffer returned null: "
+                        + (mHardwareBuffer == null) + ", " + (mHardwareBufferSecondary == null),
+                   !mNeedHardwareBuffer ||
+                       (mHardwareBuffer != null && mHardwareBufferSecondary != null));
     }
 
     public void testAccelerometerAshmemNormal() {
@@ -254,6 +315,132 @@
                 SensorDirectChannel.RATE_VERY_FAST);
     }
 
+    public void testRateIndependencyAccelGyroSingleChannel() {
+        runSingleChannelRateIndependencyTestGroup(Sensor.TYPE_ACCELEROMETER,
+                                                  Sensor.TYPE_GYROSCOPE);
+    }
+
+    public void testRateIndependencyAccelMagSingleChannel() {
+        runSingleChannelRateIndependencyTestGroup(Sensor.TYPE_ACCELEROMETER,
+                                                  Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    public void testRateIndependencyGyroMagSingleChannel() {
+        runSingleChannelRateIndependencyTestGroup(Sensor.TYPE_GYROSCOPE,
+                                                  Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    public void testRateIndependencyAccelUncalAccelSingleChannel() {
+        runSingleChannelRateIndependencyTestGroup(Sensor.TYPE_ACCELEROMETER,
+                                             Sensor.TYPE_ACCELEROMETER_UNCALIBRATED);
+    }
+
+    public void testRateIndependencyGyroUncalGyroSingleChannel() {
+        runSingleChannelRateIndependencyTestGroup(Sensor.TYPE_GYROSCOPE,
+                                             Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
+    }
+
+    public void testRateIndependencyMagUncalMagSingleChannel() {
+        runSingleChannelRateIndependencyTestGroup(Sensor.TYPE_MAGNETIC_FIELD,
+                                             Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);
+    }
+
+    public void testRateIndependencyAccelMultiChannel() {
+        runMultiChannelRateIndependencyTestGroup(Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void testRateIndependencyGyroMultiChannel() {
+        runMultiChannelRateIndependencyTestGroup(Sensor.TYPE_GYROSCOPE);
+    }
+
+    public void testRateIndependencyMagMultiChannel() {
+        runMultiChannelRateIndependencyTestGroup(Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    public void testRateIndependencyAccelMultiMode() {
+        runMultiModeRateIndependencyTestGroup(Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void testRateIndependencyGyroMultiMode() {
+        runMultiModeRateIndependencyTestGroup(Sensor.TYPE_GYROSCOPE);
+    }
+
+    public void testRateIndependencyMagMultiMode() {
+        runMultiModeRateIndependencyTestGroup(Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    private void runSingleChannelRateIndependencyTestGroup(int type1, int type2) {
+        if (type1 == type2) {
+            throw new IllegalArgumentException("Cannot run single channel rate independency test "
+                    + "on type " + type1 + " and " + type2);
+        }
+        String stype1 = SensorCtsHelper.sensorTypeShortString(type1);
+        String stype2 = SensorCtsHelper.sensorTypeShortString(type2);
+
+        TestResultCollector c =
+                new TestResultCollector(
+                    "testRateIndependency" + stype1 + stype2 + "SingleChannel", TAG);
+
+        for (int rate1 : POSSIBLE_RATE_LEVELS) {
+            for (int rate2 : POSSIBLE_RATE_LEVELS) {
+                for (int memType : POSSIBLE_CHANNEL_TYPES) {
+                    c.perform(
+                        () -> {
+                            runSingleChannelRateIndependencyTest(
+                                    type1, rate1, type2, rate2,
+                                    SensorDirectChannel.TYPE_MEMORY_FILE);
+                        },
+                        String.format("(%s rate %d, %s rate %d, mem %d)",
+                                      stype1, rate1, stype2, rate2, memType));
+                }
+            }
+        }
+        c.judge();
+    }
+
+    public void runMultiChannelRateIndependencyTestGroup(int sensorType) {
+        TestResultCollector c = new TestResultCollector(
+                "testRateIndependency" + SensorCtsHelper.sensorTypeShortString(sensorType)
+                    + "MultiChannel", TAG);
+
+        for (int rate1 : POSSIBLE_RATE_LEVELS) {
+            for (int rate2 : POSSIBLE_RATE_LEVELS) {
+                for (int type1 : POSSIBLE_CHANNEL_TYPES) {
+                    for (int type2 : POSSIBLE_CHANNEL_TYPES) {
+                        // only test upper triangle
+                        if (rate1 > rate2 || type1 > type2) {
+                            continue;
+                        }
+                        c.perform(() -> {
+                                runMultiChannelRateIndependencyTest(
+                                        sensorType, rate1, rate2, type1, type2);},
+                                String.format("rate1 %d, rate2 %d, type1 %d, type2 %d",
+                                              rate1, rate2, type1, type2));
+                    }
+                }
+            }
+        }
+        c.judge();
+    }
+
+    public void runMultiModeRateIndependencyTestGroup(int sensorType) {
+        TestResultCollector c = new TestResultCollector(
+                "testRateIndependency" + SensorCtsHelper.sensorTypeShortString(sensorType)
+                    + "MultiMode", TAG);
+
+        for (int rate : POSSIBLE_RATE_LEVELS) {
+            for (int type : POSSIBLE_CHANNEL_TYPES) {
+                for (int samplingPeriodUs : POSSIBLE_SAMPLE_PERIOD_US) {
+                    c.perform(() -> {runMultiModeRateIndependencyTest(
+                                        sensorType, rate, type, samplingPeriodUs);},
+                              String.format("rateLevel %d, memType %d, period %d",
+                                            rate, type, samplingPeriodUs));
+                }
+            }
+        }
+        c.judge();
+    }
+
     private void runSensorDirectReportTest(int sensorType, int memType, int rateLevel)
             throws AssertionError {
         Sensor s = mSensorManager.getDefaultSensor(sensorType);
@@ -262,24 +449,9 @@
                 || !s.isDirectChannelTypeSupported(memType)) {
             return;
         }
+        resetEvent();
 
-        try {
-            switch(memType) {
-                case SensorDirectChannel.TYPE_MEMORY_FILE:
-                    assertTrue("MemoryFile is null", mMemoryFile != null);
-                    mChannel = mSensorManager.createDirectChannel(mMemoryFile);
-                    break;
-                case SensorDirectChannel.TYPE_HARDWARE_BUFFER:
-                    assertTrue("HardwareBuffer is null", mHardwareBuffer != null);
-                    mChannel = mSensorManager.createDirectChannel(mHardwareBuffer);
-                    break;
-                default:
-                    Log.e(TAG, "Specified illegal memory type " + memType);
-                    return;
-            }
-        } catch (IllegalStateException e) {
-            mChannel = null;
-        }
+        mChannel = prepareDirectChannel(memType, false /* secondary */);
         assertTrue("createDirectChannel failed", mChannel != null);
 
         try {
@@ -300,6 +472,164 @@
         }
     }
 
+    private void runSingleChannelRateIndependencyTest(
+            int type1, int rateLevel1, int type2, int rateLevel2, int memType)
+                throws AssertionError {
+        Sensor s1 = mSensorManager.getDefaultSensor(type1);
+        Sensor s2 = mSensorManager.getDefaultSensor(type2);
+        if (s1 == null
+                || s1.getHighestDirectReportRateLevel() < rateLevel1
+                || !s1.isDirectChannelTypeSupported(memType)) {
+            return;
+        }
+
+        if (s2 == null
+                || s2.getHighestDirectReportRateLevel() < rateLevel2
+                || !s2.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 token1 = mChannel.configure(s1, rateLevel1);
+            int token2 = mChannel.configure(s2, rateLevel2);
+            assertTrue("configure direct mChannel failed, token1 = " + token1, token1 > 0);
+            assertTrue("configure direct mChannel failed, token2 = " + token2, token2 > 0);
+
+            // run half amount of time so buffer is enough for both sensors
+            try {
+                SensorCtsHelper.sleep(TEST_RUN_TIME_PERIOD_MILLISEC / 2, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+
+            //stop sensor and analyze content
+            mChannel.configure(s1, SensorDirectChannel.RATE_STOP);
+            mChannel.configure(s2, SensorDirectChannel.RATE_STOP);
+
+            readSharedMemory(memType, false /*secondary*/);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC / 2, parseEntireBuffer(mBuffer, token1),
+                           type1, rateLevel1);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC / 2, parseEntireBuffer(mBuffer, token2),
+                           type2, rateLevel2);
+        } finally {
+            mChannel.close();
+            mChannel = null;
+        }
+    }
+
+    private void runMultiChannelRateIndependencyTest(
+            int type, int rateLevel1, int rateLevel2, int memType1, int memType2)
+                throws AssertionError {
+        Sensor s = mSensorManager.getDefaultSensor(type);
+        if (s == null
+                || s.getHighestDirectReportRateLevel() < Math.max(rateLevel1, rateLevel2)
+                || !s.isDirectChannelTypeSupported(memType1)
+                || !s.isDirectChannelTypeSupported(memType2)) {
+            return;
+        }
+        resetEvent();
+
+        mChannel = prepareDirectChannel(memType1, false /* secondary */);
+        mChannelSecondary = prepareDirectChannel(memType2, true /* secondary */);
+
+        try {
+            assertTrue("createDirectChannel failed", mChannel != null);
+            assertTrue("Shared memory is not formatted",
+                       isSharedMemoryFormatted(memType1));
+
+            assertTrue("createDirectChannel(secondary) failed", mChannelSecondary != null);
+            assertTrue("Shared memory(secondary) is not formatted",
+                       isSharedMemoryFormatted(memType2, true));
+
+            waitBeforeStartSensor();
+
+            int token1 = mChannel.configure(s, rateLevel1);
+            int token2 = mChannelSecondary.configure(s, rateLevel2);
+            assertTrue("configure direct mChannel failed", token1 > 0);
+            assertTrue("configure direct mChannelSecondary failed", token2 > 0);
+
+            waitSensorCollection();
+
+            //stop sensor and analyze content
+            mChannel.configure(s, SensorDirectChannel.RATE_STOP);
+            mChannelSecondary.configure(s, SensorDirectChannel.RATE_STOP);
+
+            // check rate
+            readSharedMemory(memType1, false /*secondary*/);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, parseEntireBuffer(mBuffer, token1),
+                           type, rateLevel1);
+
+            readSharedMemory(memType2, true /*secondary*/);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, parseEntireBuffer(mBuffer, token2),
+                           type, rateLevel2);
+        } finally {
+            if (mChannel != null) {
+                mChannel.close();
+                mChannel = null;
+            }
+            if (mChannelSecondary != null) {
+                mChannelSecondary.close();
+                mChannelSecondary = null;
+            }
+        }
+    }
+
+    private void runMultiModeRateIndependencyTest(
+            int type , int rateLevel, int memType, int samplingPeriodUs)
+                throws AssertionError {
+        final Sensor s = mSensorManager.getDefaultSensor(type);
+        if (s == null
+                || s.getHighestDirectReportRateLevel() < rateLevel
+                || !s.isDirectChannelTypeSupported(memType)) {
+            return;
+        }
+
+        if (samplingPeriodUs == 0) {
+            samplingPeriodUs = s.getMinDelay();
+        }
+
+        if (samplingPeriodUs < s.getMinDelay()) {
+            return;
+        }
+        resetEvent();
+
+        mChannel = prepareDirectChannel(memType, false /* secondary */);
+        assertTrue("createDirectChannel failed", mChannel != null);
+        SensorEventCollection listener = new SensorEventCollection(s);
+
+        try {
+            waitBeforeStartSensor();
+            int token = mChannel.configure(s, rateLevel);
+            boolean registerRet = mSensorManager.registerListener(listener, s, samplingPeriodUs);
+            assertTrue("Register listener failed", registerRet);
+
+            waitSensorCollection();
+
+            mChannel.configure(s, SensorDirectChannel.RATE_STOP);
+            mSensorManager.unregisterListener(listener);
+
+            // check direct report rate
+            readSharedMemory(memType, false /*secondary*/);
+            List<DirectReportSensorEvent> events = parseEntireBuffer(mBuffer, token);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, events, type, rateLevel);
+
+            // check callback interface rate
+            checkEventRateUs(TEST_RUN_TIME_PERIOD_MILLISEC, listener.getEvents(), type,
+                             samplingPeriodUs);
+        } finally {
+            mChannel.close();
+            mChannel = null;
+            mSensorManager.unregisterListener(listener);
+        }
+    }
+
     private void waitBeforeStartSensor() {
         // wait for sensor system to come to a rest after previous test to avoid flakiness.
         try {
@@ -310,7 +640,7 @@
     }
 
     private void waitSensorCollection() {
-        // wait for sensor system to come to a rest after previous test to avoid flakiness.
+        // wait for sensor collection to finish
         try {
             SensorCtsHelper.sleep(TEST_RUN_TIME_PERIOD_MILLISEC, TimeUnit.MILLISECONDS);
         } catch (InterruptedException e) {
@@ -338,6 +668,68 @@
         return hardwareBuffer;
     }
 
+    private SensorDirectChannel prepareDirectChannel(int memType, boolean secondary) {
+        SensorDirectChannel channel = null;
+
+        try {
+            switch(memType) {
+                case SensorDirectChannel.TYPE_MEMORY_FILE: {
+                    MemoryFile memoryFile = secondary ? mMemoryFileSecondary : mMemoryFile;
+                    assertTrue("MemoryFile" + (secondary ? "(secondary)" : "") + " is null",
+                               memoryFile != null);
+                    channel = mSensorManager.createDirectChannel(memoryFile);
+                    break;
+                }
+                case SensorDirectChannel.TYPE_HARDWARE_BUFFER: {
+                    HardwareBuffer hardwareBuffer
+                            = secondary ? mHardwareBufferSecondary : mHardwareBuffer;
+                    assertTrue("HardwareBuffer" + (secondary ? "(secondary)" : "") + " is null",
+                               hardwareBuffer != null);
+                    channel = mSensorManager.createDirectChannel(hardwareBuffer);
+                    break;
+                }
+                default:
+                    Log.e(TAG, "Specified illegal memory type " + memType);
+            }
+        } catch (IllegalStateException | UncheckedIOException e) {
+            Log.e(TAG, "Cannot initialize channel for memory type " + memType
+                    + ", details:" + e);
+            channel = null;
+        }
+        return channel;
+    }
+
+    private boolean readSharedMemory(int memType, boolean secondary, int offset, int length) {
+        switch(memType) {
+            case SensorDirectChannel.TYPE_MEMORY_FILE:
+                try {
+                    MemoryFile f = secondary ? mMemoryFileSecondary : mMemoryFile;
+                    if (f.readBytes(mBuffer, offset, offset, length) != length) {
+                        Log.e(TAG, "cannot read entire MemoryFile");
+                        return false;
+                    }
+                } catch (IOException e) {
+                    Log.e(TAG, "accessing MemoryFile causes IOException");
+                    return false;
+                }
+                return true;
+            case SensorDirectChannel.TYPE_HARDWARE_BUFFER:
+                return nativeReadHardwareBuffer(
+                        secondary ? mHardwareBufferSecondary : mHardwareBuffer,
+                        mBuffer, offset, offset, length);
+            default:
+                return false;
+        }
+    }
+
+    private boolean readSharedMemory(int memType, boolean secondary) {
+        return readSharedMemory(memType, secondary, 0, SHARED_MEMORY_SIZE);
+    }
+
+    private boolean readSharedMemory(int memType) {
+        return readSharedMemory(memType, false /*secondary*/);
+    }
+
     private boolean isMemoryTypeNeeded(int memType) {
         List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
         for (Sensor s : sensorList) {
@@ -349,17 +741,11 @@
     }
 
     private boolean isSharedMemoryFormatted(int memType) {
-        if (memType == SensorDirectChannel.TYPE_MEMORY_FILE) {
-            if (!readMemoryFileContent()) {
-                Log.e(TAG, "Read MemoryFile content fail");
-                return false;
-            }
-        } else {
-            if (!readHardwareBufferContent()) {
-                Log.e(TAG, "Read HardwareBuffer content fail");
-                return false;
-            }
-        }
+        return isSharedMemoryFormatted(memType, false /* secondary */);
+    }
+
+    private boolean isSharedMemoryFormatted(int memType, boolean secondary) {
+        readSharedMemory(memType, secondary);
 
         for (byte b : mBuffer) {
             if (b != 0) {
@@ -370,17 +756,13 @@
     }
 
     private void checkSharedMemoryContent(Sensor s, int memType, int rateLevel, int token) {
-        if (memType == SensorDirectChannel.TYPE_MEMORY_FILE) {
-            assertTrue("read MemoryFile content failed", readMemoryFileContent());
-        } else {
-            assertTrue("read HardwareBuffer content failed", readHardwareBufferContent());
-        }
+        assertTrue("read mem type " + memType + " content failed", readSharedMemory(memType));
 
         int offset = 0;
         int nextSerial = 1;
-        DirectReportSensorEvent e = new DirectReportSensorEvent();
+        DirectReportSensorEvent e = getEvent();
         while (offset <= SHARED_MEMORY_SIZE - SENSORS_EVENT_SIZE) {
-            parseSensorEvent(mBuffer, offset, e);
+            parseSensorEvent(offset, e);
 
             if (e.serial == 0) {
                 // reaches end of events
@@ -440,7 +822,7 @@
             maxEvents = (int) Math.ceil(
                     nominalFreq
                     * FREQ_UPPER_BOUND
-                    * (TEST_RUN_TIME_PERIOD_MILLISEC - ALLOWED_SENSOR_INIT_TIME_MILLISEC)
+                    * TEST_RUN_TIME_PERIOD_MILLISEC
                     * (1 + MERCY_FACTOR)
                     / 1000);
 
@@ -449,47 +831,345 @@
         }
     }
 
-    private boolean readMemoryFileContent() {
-        try {
-            if (mMemoryFile.readBytes(mBuffer, 0, 0, SHARED_MEMORY_SIZE)
-                    != SHARED_MEMORY_SIZE) {
-                Log.e(TAG, "cannot read entire MemoryFile");
-                return false;
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "accessing MemoryFile cause IOException");
-            return false;
+    private void checkEventRate(int testTimeMs, List<DirectReportSensorEvent> events,
+                                int type, int rateLevel) {
+        assertTrue("insufficient events of type " + type, events.size() > 1);
+        for (DirectReportSensorEvent e : events) {
+            assertTrue("incorrect type " + e.type + " expecting " + type, e.type == type);
         }
-        return true;
+
+        // check number of events
+        int[] minMax = calculateExpectedNEvents(testTimeMs, rateLevel);
+        assertTrue(
+                "Number of event of type " + type + " is " + events.size()
+                    + ", which is not in range [" + minMax[0] + ", " + minMax[1] + "].",
+                minMax[0] <= events.size() && events.size() <= minMax[1]);
+
+        // intervals
+        List<Long> intervals = new ArrayList<>(events.size() - 1);
+        long minInterval = Long.MAX_VALUE;
+        long maxInterval = Long.MIN_VALUE;
+        long averageInterval = 0;
+        for (int i = 1; i < events.size(); ++i) {
+            long d = events.get(i).ts - events.get(i-1).ts;
+            averageInterval += d;
+            minInterval = Math.min(d, minInterval);
+            maxInterval = Math.max(d, maxInterval);
+            intervals.add(d);
+        }
+        averageInterval /= (events.size() - 1);
+
+        // average rate
+        float averageFreq = 1e9f / averageInterval;
+        float nominalFreq = getNominalFreq(rateLevel);
+        Log.d(TAG, String.format(
+                "checkEventRate type %d: averageFreq %f, nominalFreq %f, lbound %f, ubound %f",
+                type, averageFreq, nominalFreq,
+                nominalFreq * FREQ_LOWER_BOUND,
+                nominalFreq * FREQ_UPPER_BOUND));
+        assertTrue("Average frequency of type " + type + " rateLevel " + rateLevel
+                        + " is " + averageFreq,
+                   nominalFreq * FREQ_LOWER_BOUND * (1 - MERCY_FACTOR) <= averageFreq &&
+                       averageFreq <= nominalFreq * FREQ_UPPER_BOUND * (1 + MERCY_FACTOR));
+
+        // jitter variance
+        List<Long> percentileValues =
+                SensorCtsHelper.getPercentileValue(intervals, 0.025f, (1 - 0.025f));
+        assertTrue("Timestamp jitter of type " + type + " rateLevel " + rateLevel + " is "
+                        + (percentileValues.get(1) - percentileValues.get(0) / 1000) + " us, "
+                        + "while average interval is " + (averageInterval / 1000) + "us, over-range",
+                   (percentileValues.get(1) - percentileValues.get(0)) / averageInterval < 0.05);
+        Log.d(TAG, String.format(
+                "checkEventRate type %d, timestamp interval range %f - %f ms, " +
+                    "span %f ms, %.2f%% of averageInterval",
+                    type, percentileValues.get(0)/1e6f, percentileValues.get(1)/1e6f,
+                    (percentileValues.get(1) - percentileValues.get(0))/1e6f,
+                    (percentileValues.get(1) - percentileValues.get(0)) / averageInterval * 100.f));
+
     }
 
-    private boolean readHardwareBufferContent() {
-        return nativeReadHardwareBuffer(mHardwareBuffer, mBuffer, 0, 0, SHARED_MEMORY_SIZE);
+    private void checkEventRateUs(int testTimeMs, List<DirectReportSensorEvent> events,
+                                  int type, int samplingPeriodUs) {
+        // samplingPeriodUs must be a valid one advertised by sensor
+        assertTrue("insufficient events of type " + type, events.size() > 1);
+        for (DirectReportSensorEvent e : events) {
+            assertTrue("incorrect type " + e.type + " expecting " + type, e.type == type);
+        }
+
+        // check number of events
+        int[] minMax = calculateExpectedNEventsUs(testTimeMs, samplingPeriodUs);
+        assertTrue(
+                "Number of event of type " + type + " is " + events.size()
+                    + ", which is not in range [" + minMax[0] + ", " + minMax[1] + "].",
+                minMax[0] <= events.size() && events.size() <= minMax[1]);
+
+        // intervals
+        List<Long> intervals = new ArrayList<>(events.size() - 1);
+        long minInterval = Long.MAX_VALUE;
+        long maxInterval = Long.MIN_VALUE;
+        long averageInterval = 0;
+        for (int i = 1; i < events.size(); ++i) {
+            long d = events.get(i).ts - events.get(i-1).ts;
+            averageInterval += d;
+            minInterval = Math.min(d, minInterval);
+            maxInterval = Math.max(d, maxInterval);
+            intervals.add(d);
+        }
+        averageInterval /= (events.size() - 1);
+
+        // average rate
+        float averageFreq = 1e9f / averageInterval;
+        float nominalFreq = 1e6f / samplingPeriodUs;
+        Log.d(TAG, String.format(
+                "checkEventRateUs type %d: averageFreq %f, nominalFreq %f, lbound %f, ubound %f",
+                type, averageFreq, nominalFreq,
+                nominalFreq * FREQ_LOWER_BOUND_POLL,
+                nominalFreq * FREQ_UPPER_BOUND_POLL));
+        assertTrue("Average frequency of type " + type
+                        + " is " + averageFreq,
+                   nominalFreq * FREQ_LOWER_BOUND_POLL * (1 - MERCY_FACTOR) <= averageFreq &&
+                       averageFreq <= nominalFreq * FREQ_UPPER_BOUND_POLL * (1 + MERCY_FACTOR));
+
+        // jitter variance
+        List<Long> percentileValues =
+                SensorCtsHelper.getPercentileValue(intervals, 0.025f, (1 - 0.025f));
+        assertTrue("Timestamp jitter of type " + type + " is "
+                        + (percentileValues.get(1) - percentileValues.get(0) / 1000) + " us, "
+                        + "while average interval is " + (averageInterval / 1000) + "us, over-range",
+                   (percentileValues.get(1) - percentileValues.get(0)) / averageInterval < 0.05);
+        Log.d(TAG, String.format(
+                "checkEventRateUs type %d, timestamp interval range %f - %f ms, " +
+                    "span %f ms, %.2f%% of averageInterval",
+                    type, percentileValues.get(0)/1e6f, percentileValues.get(1)/1e6f,
+                    (percentileValues.get(1) - percentileValues.get(0)) / 1e6f,
+                    (percentileValues.get(1) - percentileValues.get(0)) / averageInterval * 100.f));
     }
 
-    private class DirectReportSensorEvent {
-        int size;
-        int token;
-        int type;
-        int serial;
-        long ts;
-        float x;
-        float y;
-        float z;
+    private void allocateSharedMemory() {
+        if (mNeedMemoryFile) {
+            mMemoryFile = allocateMemoryFile();
+            mMemoryFileSecondary = allocateMemoryFile();
+        }
+
+        if (mNeedHardwareBuffer) {
+            mHardwareBuffer = allocateHardwareBuffer();
+            mHardwareBufferSecondary = allocateHardwareBuffer();
+        }
+    }
+
+    private void freeSharedMemory() {
+        if (mMemoryFile != null) {
+            mMemoryFile.close();
+            mMemoryFile = null;
+        }
+
+        if (mMemoryFileSecondary != null) {
+            mMemoryFileSecondary.close();
+            mMemoryFileSecondary = null;
+        }
+
+        if (mHardwareBuffer != null) {
+            mHardwareBuffer.close();
+            mHardwareBuffer = null;
+        }
+
+        if (mHardwareBufferSecondary != null) {
+            mHardwareBufferSecondary.close();
+            mHardwareBufferSecondary = null;
+        }
+    }
+
+    private float getNominalFreq(int rateLevel) {
+        float nominalFreq = 0;
+        switch (rateLevel) {
+            case SensorDirectChannel.RATE_NORMAL:
+                nominalFreq = RATE_NORMAL_NOMINAL;
+                break;
+            case SensorDirectChannel.RATE_FAST:
+                nominalFreq = RATE_FAST_NOMINAL;
+                break;
+            case SensorDirectChannel.RATE_VERY_FAST:
+                nominalFreq = RATE_VERY_FAST_NOMINAL;
+                break;
+        }
+        return nominalFreq;
+    }
+
+    private int[] calculateExpectedNEvents(int timeMs, int rateLevel) {
+        int[] minMax = new int[] { -1, Integer.MAX_VALUE };
+        float nominalFreq = getNominalFreq(rateLevel);
+        if (nominalFreq != 0) {
+            // min
+            if (timeMs > ALLOWED_SENSOR_INIT_TIME_MILLISEC) {
+                minMax[0] = (int) Math.floor(
+                        nominalFreq
+                        * FREQ_LOWER_BOUND
+                        * (timeMs - ALLOWED_SENSOR_INIT_TIME_MILLISEC)
+                        * (1 - MERCY_FACTOR)
+                        / 1000);
+            }
+            // max
+            minMax[1] = (int) Math.ceil(
+                    nominalFreq
+                    * FREQ_UPPER_BOUND
+                    * timeMs
+                    * (1 + MERCY_FACTOR)
+                    / 1000);
+        }
+        return minMax;
+    }
+
+    private int[] calculateExpectedNEventsUs(int timeMs, int samplingPeriodUs) {
+        int[] minMax = new int[2];
+        minMax[0] = Math.max((int) Math.floor(
+                (timeMs - ALLOWED_SENSOR_INIT_TIME_MILLISEC) * 1000/ samplingPeriodUs), 0);
+        minMax[1] = (int) Math.ceil(timeMs * 1000 * 2 / samplingPeriodUs);
+        return minMax;
+    }
+
+    private static class DirectReportSensorEvent {
+        public int size;
+        public int token;
+        public int type;
+        public long serial;
+        public long ts;
+        public float x;
+        public float y;
+        public float z;
+        public long arrivalTs;
     };
 
-    // 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(ByteOrder.nativeOrder());
+    // EventPool to avoid allocating too many event objects and hitting GC during test
+    private static class EventPool {
+        public EventPool(int n) {
+            mEvents = Arrays.asList(new DirectReportSensorEvent[n]);
+            for (int i = 0; i < n; ++i) {
+                mEvents.set(i, new DirectReportSensorEvent());
+            }
+            reset();
+        }
 
-        ev.size = b.getInt();
-        ev.token = b.getInt();
-        ev.type = b.getInt();
-        ev.serial = b.getInt();
-        ev.ts = b.getLong();
-        ev.x = b.getFloat();
-        ev.y = b.getFloat();
-        ev.z = b.getFloat();
+        public synchronized void reset() {
+            Log.d(TAG, "Reset EventPool (" + mIndex + " events used)");
+            mIndex = 0;
+        }
+
+        public synchronized DirectReportSensorEvent get() {
+            if (mIndex < mEvents.size()) {
+                return mEvents.get(mIndex++);
+            } else {
+                throw new IllegalStateException("EventPool depleted");
+            }
+        }
+
+        private List<DirectReportSensorEvent> mEvents;
+        private int mIndex;
+    };
+
+    private DirectReportSensorEvent getEvent() {
+        return mEventPool.get();
+    }
+
+    private DirectReportSensorEvent getEvent(DirectReportSensorEvent e) {
+        DirectReportSensorEvent event = mEventPool.get();
+        event.size = e.size;
+        event.token = e.token;
+        event.type = e.type;
+        event.serial = e.serial;
+        event.ts = e.ts;
+        event.x = e.x;
+        event.y = e.y;
+        event.z = e.z;
+        event.arrivalTs = e.arrivalTs;
+        return event;
+    }
+
+    private void resetEvent() {
+        mEventPool.reset();
+    }
+
+    private class SensorEventCollection implements SensorEventListener {
+        List<DirectReportSensorEvent> mEvents = new ArrayList<>();
+        Sensor mSensor;
+
+        public SensorEventCollection(Sensor s) {
+            mSensor = s;
+        }
+
+        List<DirectReportSensorEvent> getEvents() {
+            return mEvents;
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (mSensor == null || event.sensor == mSensor) {
+                DirectReportSensorEvent e = mEventPool.get();
+                e.size = SENSORS_EVENT_SIZE;
+                e.token = event.sensor.getType();
+                e.type = e.token;
+                e.serial = -1;
+                e.ts = event.timestamp;
+                e.arrivalTs = SystemClock.elapsedRealtimeNanos();
+
+                e.x = event.values[0];
+                if (event.values.length > 1) {
+                    e.y = event.values[1];
+                }
+                if (event.values.length > 2) {
+                    e.z = event.values[2];
+                }
+                mEvents.add(e);
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor s, int accuracy) {
+            // do nothing
+        }
+    };
+
+    private List<DirectReportSensorEvent> parseEntireBuffer(byte[] buffer, int token) {
+        int offset = 0;
+        int nextSerial = 1;
+        List<DirectReportSensorEvent> events = new ArrayList<>();
+
+        while (offset <= SHARED_MEMORY_SIZE - SENSORS_EVENT_SIZE) {
+            DirectReportSensorEvent e = getEvent();
+            parseSensorEvent(offset, e);
+
+            if (e.serial == 0) {
+                // reaches end of events
+                break;
+            }
+
+            assertTrue("incorrect size " + e.size + "  at offset " + offset,
+                    e.size == SENSORS_EVENT_SIZE);
+            assertTrue("incorrect serial " + e.serial + " at offset " + offset,
+                    e.serial == nextSerial);
+
+            if (e.token == token) {
+                events.add(e);
+            }
+
+            ++nextSerial;
+            offset += SENSORS_EVENT_SIZE;
+        }
+
+        return events;
+    }
+
+    // parse sensors_event_t from mBuffer and fill information into DirectReportSensorEvent
+    private void parseSensorEvent(int offset, DirectReportSensorEvent ev) {
+        mByteBuffer.position(offset);
+
+        ev.size = mByteBuffer.getInt();
+        ev.token = mByteBuffer.getInt();
+        ev.type = mByteBuffer.getInt();
+        ev.serial = ((long) mByteBuffer.getInt()) & 0xFFFFFFFFl; // signed=>unsigned
+        ev.ts = mByteBuffer.getLong();
+        ev.arrivalTs = SystemClock.elapsedRealtimeNanos();
+        ev.x = mByteBuffer.getFloat();
+        ev.y = mByteBuffer.getFloat();
+        ev.z = mByteBuffer.getFloat();
     }
 }
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index f68d8ec..9ff71a4 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -247,4 +247,12 @@
     <!-- b/38462487 -->
     <option name="compatibility:exclude-filter" value="CtsCompilationTestCases android.cts.compilation.AdbRootDependentCompilationTest#testCompile_curProfile" />
 
+    <!-- b/38420898 -->
+    <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyAccelMultiChannel" />
+    <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyGyroMultiChannel" />
+    <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyMagMultiChannel" />
+    <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyAccelMultiMode" />
+    <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyGyroMultiMode" />
+    <option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyMagMultiMode" />
+
 </configuration>