Merge "Adding support for GLES3" into klp-dev
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index d7ef39f..6b0f214 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -6,5 +6,13 @@
 {
   name: "android.holo.cts.HoloTest",
   bug: 8148617
+},
+{
+  name: "android.hardware.camera2.cts.ImageReaderTest",
+  name: "android.hardware.camera2.cts.CameraCharacteristicsTest",
+  name: "android.hardware.camera2.cts.CameraCaptureResultTest",
+  name: "android.hardware.camera2.cts.CameraDeviceTest",
+  name: "android.hardware.camera2.cts.CameraManagerTest",
+  bug: 11141002
 }
 ]
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
new file mode 100644
index 0000000..6a708e3
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2013 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.camera2.cts;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.Size;
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.ex.camera2.blocking.BlockingStateListener;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class CameraCaptureResultTest extends AndroidTestCase {
+    private static final String TAG = "CameraCaptureResultTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private CameraManager mCameraManager;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private ImageReader mImageReader;
+    private Surface mSurface;
+    private BlockingStateListener mCameraListener;
+
+    private static final int MAX_NUM_IMAGES = 5;
+    private static final int NUM_FRAMES_VERIFIED = 300;
+    private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
+
+    // List that includes all public keys from CaptureResult
+    List<CameraMetadata.Key<?>> mAllKeys;
+
+    // List tracking the failed test keys.
+    List<CameraMetadata.Key<?>> mFailedKeys = new ArrayList<CameraMetadata.Key<?>>();
+
+    @Override
+    public void setContext(Context context) {
+        mAllKeys = getAllCaptureResultKeys();
+        super.setContext(context);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+        assertNotNull("Can't connect to camera manager", mCameraManager);
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mCameraListener = new BlockingStateListener();
+        mFailedKeys.clear();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mHandlerThread.quitSafely();
+        super.tearDown();
+    }
+
+    public void testCameraCaptureResultAllKeys() throws Exception {
+        /**
+         * Hardcode a key waiver list for the keys we want to skip the sanity check.
+         * FIXME: We need get ride of this list, see bug 11116270.
+         */
+        List<CameraMetadata.Key<?>> waiverkeys = new ArrayList<CameraMetadata.Key<?>>();
+        waiverkeys.add(CaptureResult.EDGE_MODE);
+        waiverkeys.add(CaptureResult.JPEG_GPS_COORDINATES);
+        waiverkeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
+        waiverkeys.add(CaptureResult.JPEG_GPS_TIMESTAMP);
+        waiverkeys.add(CaptureResult.JPEG_ORIENTATION);
+        waiverkeys.add(CaptureResult.JPEG_QUALITY);
+        waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
+        waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
+        waiverkeys.add(CaptureResult.SENSOR_TEMPERATURE);
+        waiverkeys.add(CaptureResult.TONEMAP_CURVE_BLUE);
+        waiverkeys.add(CaptureResult.TONEMAP_CURVE_GREEN);
+        waiverkeys.add(CaptureResult.TONEMAP_CURVE_RED);
+        waiverkeys.add(CaptureResult.TONEMAP_MODE);
+        waiverkeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_GAINS);
+        waiverkeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_TRANSFORM);
+        waiverkeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
+
+        String[] ids = mCameraManager.getCameraIdList();
+        for (int i = 0; i < ids.length; i++) {
+            CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+            assertNotNull("CameraCharacteristics shouldn't be null", props);
+            Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+            if (hwLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL) {
+                continue;
+            }
+            // TODO: check for LIMITED keys
+
+            CameraDevice camera = null;
+            try {
+                Size[] sizes = CameraTestUtils.getSupportedSizeForFormat(
+                        ImageFormat.YUV_420_888, ids[i], mCameraManager);
+                CameraTestUtils.assertArrayNotEmpty(sizes, "Available sizes shouldn't be empty");
+                createDefaultSurface(sizes[0]);
+
+                if (VERBOSE) {
+                    Log.v(TAG, "Testing camera " + ids[i] + "for size " + sizes[0].toString());
+                }
+
+                camera = CameraTestUtils.openCamera(
+                        mCameraManager, ids[i], mCameraListener, mHandler);
+                assertNotNull(
+                        String.format("Failed to open camera device %s", ids[i]), camera);
+                mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
+
+                List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+                outputSurfaces.add(mSurface);
+                camera.configureOutputs(outputSurfaces);
+                mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+                mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+
+                CaptureRequest.Builder requestBuilder =
+                        camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                assertNotNull("Failed to create capture request", requestBuilder);
+                requestBuilder.addTarget(mSurface);
+
+                // Enable face detection if supported
+                byte[] faceModes = props.get(
+                        CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);
+                assertNotNull("Available face detection modes shouldn't be null", faceModes);
+                for (int m = 0; m < faceModes.length; m++) {
+                    if (faceModes[m] == CameraMetadata.STATISTICS_FACE_DETECT_MODE_FULL) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "testCameraCaptureResultAllKeys - " +
+                                    "setting facedetection mode to full");
+                        }
+                        requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE,
+                                (int)faceModes[m]);
+                    }
+                }
+
+                // Enable lensShading mode, it should be supported by full mode device.
+                requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+                        CameraMetadata.STATISTICS_LENS_SHADING_MAP_MODE_ON);
+
+                SimpleCaptureListener captureListener = new SimpleCaptureListener();
+                camera.setRepeatingRequest(requestBuilder.build(), captureListener, mHandler);
+
+                for (int m = 0; m < NUM_FRAMES_VERIFIED; m++) {
+                    if(VERBOSE) {
+                        Log.v(TAG, "Testing frame " + m);
+                    }
+                    validateCaptureResult(
+                            captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS),
+                            waiverkeys);
+                }
+
+                // Stop repeat, wait for captures to complete, and disconnect from surfaces
+                camera.configureOutputs(/*outputs*/ null);
+                mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+                mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
+                // Camera has disconnected, clear out the reader
+                mSurface.release();
+                mImageReader.close();
+            } finally {
+                if (camera != null) {
+                    camera.close();
+                }
+            }
+
+        }
+    }
+
+    private void validateCaptureResult(CaptureResult result,
+            List<CameraMetadata.Key<?>> skippedKeys) throws Exception {
+        for (CameraMetadata.Key<?> key : mAllKeys) {
+            if (!skippedKeys.contains(key) && result.get(key) == null) {
+                mFailedKeys.add(key);
+            }
+        }
+
+        StringBuffer failedKeyNames = new StringBuffer("Below Keys have null values:\n");
+        for (CameraMetadata.Key<?> key : mFailedKeys) {
+            failedKeyNames.append(key.getName() + "\n");
+        }
+
+        assertTrue("Some keys have null values, " + failedKeyNames.toString(),
+                mFailedKeys.isEmpty());
+    }
+
+    private static class SimpleCaptureListener extends CameraDevice.CaptureListener {
+        LinkedBlockingQueue<CaptureResult> mQueue = new LinkedBlockingQueue<CaptureResult>();
+
+        @Override
+        public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp)
+        {
+        }
+
+        @Override
+        public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
+                CaptureResult result) {
+            try {
+                mQueue.put(result);
+            } catch (InterruptedException e) {
+                throw new UnsupportedOperationException(
+                        "Can't handle InterruptedException in onCaptureCompleted");
+            }
+        }
+
+        @Override
+        public void onCaptureFailed(CameraDevice camera, CaptureRequest request,
+                CaptureFailure failure) {
+        }
+
+        @Override
+        public void onCaptureSequenceCompleted(CameraDevice camera, int sequenceId,
+                int frameNumber) {
+        }
+
+        public CaptureResult getCaptureResult(long timeout) throws InterruptedException {
+            CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+            assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
+            return result;
+        }
+    }
+
+    private void createDefaultSurface(Size sz) {
+        mImageReader =
+                ImageReader.newInstance(sz.getWidth(),
+                        sz.getHeight(),
+                        ImageFormat.YUV_420_888,
+                        MAX_NUM_IMAGES);
+        mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
+        mSurface = mImageReader.getSurface();
+    }
+
+    /**
+     * TODO: Use CameraCharacteristics.getAvailableCaptureResultKeys() once we can filter out
+     * @hide keys.
+     *
+     */
+
+    /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
+     * The key entries below this point are generated from metadata
+     * definitions in /system/media/camera/docs. Do not modify by hand or
+     * modify the comment blocks at the start or end.
+     *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
+
+    private static List<CameraMetadata.Key<?>> getAllCaptureResultKeys() {
+        ArrayList<CameraMetadata.Key<?>> resultKeys = new ArrayList<CameraMetadata.Key<?>>();
+        resultKeys.add(CaptureResult.COLOR_CORRECTION_TRANSFORM);
+        resultKeys.add(CaptureResult.COLOR_CORRECTION_GAINS);
+        resultKeys.add(CaptureResult.CONTROL_AE_REGIONS);
+        resultKeys.add(CaptureResult.CONTROL_AF_MODE);
+        resultKeys.add(CaptureResult.CONTROL_AF_REGIONS);
+        resultKeys.add(CaptureResult.CONTROL_AWB_MODE);
+        resultKeys.add(CaptureResult.CONTROL_AWB_REGIONS);
+        resultKeys.add(CaptureResult.CONTROL_MODE);
+        resultKeys.add(CaptureResult.CONTROL_AE_STATE);
+        resultKeys.add(CaptureResult.CONTROL_AF_STATE);
+        resultKeys.add(CaptureResult.CONTROL_AWB_STATE);
+        resultKeys.add(CaptureResult.EDGE_MODE);
+        resultKeys.add(CaptureResult.FLASH_MODE);
+        resultKeys.add(CaptureResult.FLASH_STATE);
+        resultKeys.add(CaptureResult.JPEG_GPS_COORDINATES);
+        resultKeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
+        resultKeys.add(CaptureResult.JPEG_GPS_TIMESTAMP);
+        resultKeys.add(CaptureResult.JPEG_ORIENTATION);
+        resultKeys.add(CaptureResult.JPEG_QUALITY);
+        resultKeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
+        resultKeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
+        resultKeys.add(CaptureResult.LENS_APERTURE);
+        resultKeys.add(CaptureResult.LENS_FILTER_DENSITY);
+        resultKeys.add(CaptureResult.LENS_FOCAL_LENGTH);
+        resultKeys.add(CaptureResult.LENS_FOCUS_DISTANCE);
+        resultKeys.add(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE);
+        resultKeys.add(CaptureResult.LENS_FOCUS_RANGE);
+        resultKeys.add(CaptureResult.LENS_STATE);
+        resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
+        resultKeys.add(CaptureResult.REQUEST_FRAME_COUNT);
+        resultKeys.add(CaptureResult.SCALER_CROP_REGION);
+        resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
+        resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
+        resultKeys.add(CaptureResult.SENSOR_SENSITIVITY);
+        resultKeys.add(CaptureResult.SENSOR_TIMESTAMP);
+        resultKeys.add(CaptureResult.SENSOR_TEMPERATURE);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_IDS);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_LANDMARKS);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_RECTANGLES);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_SCORES);
+        resultKeys.add(CaptureResult.STATISTICS_LENS_SHADING_MAP);
+        resultKeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_GAINS);
+        resultKeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_TRANSFORM);
+        resultKeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
+        resultKeys.add(CaptureResult.TONEMAP_CURVE_BLUE);
+        resultKeys.add(CaptureResult.TONEMAP_CURVE_GREEN);
+        resultKeys.add(CaptureResult.TONEMAP_CURVE_RED);
+        resultKeys.add(CaptureResult.TONEMAP_MODE);
+        resultKeys.add(CaptureResult.BLACK_LEVEL_LOCK);
+
+        // Add STATISTICS_FACES key separately here because it is not
+        // defined in metadata xml file.
+        resultKeys.add(CaptureResult.STATISTICS_FACES);
+
+        return resultKeys;
+    }
+
+    /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
+     * End generated code
+     *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index a2d965c..7f10cb8 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -24,6 +24,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.Size;
 import android.media.Image;
+import android.media.ImageReader;
 import android.media.Image.Plane;
 import android.os.Handler;
 import android.util.Log;
@@ -52,6 +53,20 @@
     public static final int CAMERA_ACTIVE_TIMEOUT_MS = 500;
     public static final int CAMERA_BUSY_TIMEOUT_MS = 500;
 
+    public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            Image image = null;
+            try {
+                image = reader.acquireNextImage();
+            } finally {
+                if (image != null) {
+                    image.close();
+                }
+            }
+        }
+    }
+
     /**
      * Block until the camera is opened.
      *
@@ -214,21 +229,21 @@
         int format = image.getFormat();
         Plane[] planes = image.getPlanes();
         switch (format) {
-          case ImageFormat.YUV_420_888:
-          case ImageFormat.NV21:
-          case ImageFormat.YV12:
-              assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
-              break;
-          case ImageFormat.Y8:
-          case ImageFormat.Y16:
-              assertEquals("Y8/Y16 Image should have 1 plane", 1, planes.length);
-              break;
-          case ImageFormat.JPEG:
-              assertEquals("Jpeg Image should have one plane", 1, planes.length);
-              break;
-          default:
-              fail("Unsupported Image Format: " + format);
-      }
+            case ImageFormat.YUV_420_888:
+            case ImageFormat.NV21:
+            case ImageFormat.YV12:
+                assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
+                break;
+            case ImageFormat.Y8:
+            case ImageFormat.Y16:
+                assertEquals("Y8/Y16 Image should have 1 plane", 1, planes.length);
+                break;
+            case ImageFormat.JPEG:
+                assertEquals("Jpeg Image should have one plane", 1, planes.length);
+                break;
+            default:
+                fail("Unsupported Image Format: " + format);
+        }
     }
 
     public static void dumpFile(String fileName, byte[] data) {
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorAccelerometerTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorAccelerometerTest.java
index 51fd5f6..2e33a5d 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorAccelerometerTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorAccelerometerTest.java
@@ -20,6 +20,8 @@
 import android.hardware.SensorManager;
 
 public class SensorAccelerometerTest extends SensorCommonTests {
+    private final int AXIS_COUNT = 3;
+
     @Override
     protected int getMaxFrequencySupportedInuS() {
         return 10000; // 100Hz
@@ -37,21 +39,13 @@
      */
     @Override
     public void testEventValidity() {
-        final float THRESHOLD = 0.25f; // m / s^2
-        validateSensorEvent(
-                0 /*x-axis*/,
-                0 /*y-axis*/,
-                SensorManager.STANDARD_GRAVITY /*z-axis*/,
-                THRESHOLD);
+        final float THRESHOLD = 0.5f; // m / s^2
+        validateNormForSensorEvent(SensorManager.STANDARD_GRAVITY, THRESHOLD, AXIS_COUNT);
     }
 
     @Override
-    public void testVarianceWhileStatic() {
-        final float THRESHOLD = 0.25f; // m / s^2
-        validateVarianceWhileStatic(
-                0 /*x-axis*/,
-                0 /*y-axis*/,
-                SensorManager.STANDARD_GRAVITY /*z-axis*/,
-                THRESHOLD);
+    public void testStandardDeviationWhileStatic() {
+        final float STANDARD_DEVIATION = 1f; // m / s^2
+        validateStandardDeviationWhileStatic(STANDARD_DEVIATION, AXIS_COUNT);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorCommonTests.java b/tests/tests/hardware/src/android/hardware/cts/SensorCommonTests.java
index 5cc65bb..326963c 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorCommonTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorCommonTests.java
@@ -18,45 +18,34 @@
 
 import android.content.Context;
 
-import android.hardware.FlushCompleteListener;
 import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
 
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.TestSensorManager;
+
 import android.os.PowerManager;
 
+import android.os.SystemClock;
 import android.test.AndroidTestCase;
 
-import java.util.List;
 import android.util.Log;
 
-import java.io.DataOutputStream;
-import java.io.IOException;
-
-import java.text.SimpleDateFormat;
-
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
+import java.util.List;
 import java.util.Random;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import junit.framework.Assert;
+
 /**
  * Class is not marked public to avoid TestRunner to pick the tests in it
  */
 abstract class SensorCommonTests extends AndroidTestCase {
     protected final String LOG_TAG = "TestRunner";
-
-    protected SensorManager mSensorManager;
-    protected Sensor mSensorUnderTest;
-    protected TestSensorListener mEventListener;
-
-    private FlushCompleteListener mFlushListener;
+    protected TestSensorManager mTestSensorManager;
     private PowerManager.WakeLock mWakeLock;
 
     protected SensorCommonTests() {}
@@ -71,15 +60,11 @@
      * Abstract test methods that sensors need to verify
      */
     public abstract void testEventValidity();
-    public abstract void testVarianceWhileStatic();
+    public abstract void testStandardDeviationWhileStatic();
 
     /**
      * Methods to control the behavior of the tests by concrete sensor tests
      */
-    protected int getWaitTimeoutInSeconds() {
-        return 30;
-    }
-
     protected int getHighNumberOfIterationsToExecute() {
         return 100;
     }
@@ -101,31 +86,27 @@
                 Context.POWER_SERVICE);
         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getLogTag());
         mWakeLock.acquire();
-
-        mEventListener = new TestSensorListener();
-        mFlushListener = new TestFlushListener();
     }
 
     @Override
     protected void tearDown() throws Exception {
-        mSensorManager.unregisterListener(mEventListener, mSensorUnderTest);
-
-        mFlushListener = null;
-        mEventListener = null;
-        mSensorUnderTest = null;
+        if(mTestSensorManager != null) {
+            mTestSensorManager.close();
+            mTestSensorManager = null;
+        }
 
         releaseWakeLock();
     }
 
     @Override
     public void runBare() throws Throwable {
-        mSensorManager = (SensorManager) this.getContext().getSystemService(Context.SENSOR_SERVICE);
-        assertNotNull("getSystemService#Sensor_Service", mSensorManager);
+        SensorManager sensorManager = (SensorManager) this.getContext().getSystemService(Context.SENSOR_SERVICE);
+        assertNotNull("getSystemService#Sensor_Service", sensorManager);
 
-        List<Sensor> availableSensors = mSensorManager.getSensorList(this.getSensorType());
+        List<Sensor> availableSensors = sensorManager.getSensorList(this.getSensorType());
         // it is OK if there are no sensors available
         for(Sensor sensor : availableSensors) {
-            mSensorUnderTest = sensor;
+            mTestSensorManager = new TestSensorManager(this, sensorManager, sensor);
             super.runBare();
         }
     }
@@ -134,49 +115,34 @@
      * Test cases continuous mode.
      */
     public void testCanRegisterListener() {
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                SensorManager.SENSOR_DELAY_NORMAL);
-        assertTrue("registerListener", result);
+        mTestSensorManager.registerListener(SensorManager.SENSOR_DELAY_NORMAL);
     }
 
     public void testNotTriggerSensor() {
         TestTriggerListener listener = new TestTriggerListener();
-        assertFalse(
-                "requestTriggerSensor",
-                mSensorManager.requestTriggerSensor(listener, mSensorUnderTest));
+        boolean result = mTestSensorManager.getUnderlyingSensorManager().requestTriggerSensor(
+                listener,
+                mTestSensorManager.getSensorUnderTest());
+        assertFalse("requestTriggerSensor", result);
     }
 
     public void testCanReceiveEvents() {
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                SensorManager.SENSOR_DELAY_NORMAL);
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(5);
+        mTestSensorManager.collectEvents(SensorManager.SENSOR_DELAY_NORMAL, 5);
     }
 
     public void testMaxFrequency() {
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                this.getMaxFrequencySupportedInuS());
-        assertTrue("registerListener", result);
+        // TODO: verify that events do arrive at the proper rate
+        mTestSensorManager.registerListener(this.getMaxFrequencySupportedInuS());
     }
 
     public void testEventsArriveInOrder() {
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                SensorManager.SENSOR_DELAY_FASTEST);
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(100);
-
-        SensorEventForTest[] events = mEventListener.getAllEvents();
+        // TODO: test for other sensor frequencies, rely on helper test classes for sensors
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.collectEvents(
+                SensorManager.SENSOR_DELAY_FASTEST,
+                100);
         for(int i = 1; i < events.length; ++i) {
-            long previousTimestamp = events[i-1].getTimestamp();
-            long timestamp = events[i].getTimestamp();
+            long previousTimestamp = events[i-1].timestamp;
+            long timestamp = events[i].timestamp;
             assertTrue(
                     String.format("[timestamp:%d] %d >= %d", i, previousTimestamp, timestamp),
                     previousTimestamp < timestamp);
@@ -184,17 +150,7 @@
     }
 
     public void testStartStopRepeatedly() {
-        for(int i = 0; i < this.getLowNumberOfIterationsToExecute(); ++i) {
-            String iterationInfo = String.format("registerListener:%d", i);
-            boolean result = mSensorManager.registerListener(
-                    mEventListener,
-                    mSensorUnderTest,
-                    SensorManager.SENSOR_DELAY_FASTEST);
-            assertTrue(iterationInfo, result);
-            mEventListener.waitForEvents(1, iterationInfo);
-
-            mSensorManager.unregisterListener(mEventListener, mSensorUnderTest);
-        }
+        validateRegisterUnregisterRepeteadly(mTestSensorManager);
     }
 
     public void testUpdateRate() {
@@ -221,33 +177,87 @@
                     rate = this.getMaxFrequencySupportedInuS() * generator.nextInt(10);
             }
 
-            String iterationInfo = String.format("registerListener:%d, rate:%d", i, rate);
-            assertTrue(
-                    iterationInfo,
-                    mSensorManager.registerListener(mEventListener, mSensorUnderTest, rate));
-
-            mEventListener.waitForEvents(generator.nextInt(5) + 1, iterationInfo);
-            mEventListener.clearEvents();
-
-            mSensorManager.unregisterListener(mEventListener, mSensorUnderTest);
+            // TODO: check that the rate has indeed changed
+            mTestSensorManager.collectEvents(
+                    rate,
+                    generator.nextInt(5) + 1,
+                    String.format("iteration:%d, rate:%d", i, rate));
         }
     }
 
-    public void testSeveralClients() throws InterruptedException {
-        ArrayList<Thread> threads = new ArrayList<Thread>();
-        for(int i = 0; i < this.getNumberOfThreadsToUse(); ++i) {
-            threads.add(new Thread() {
-                @Override
-                public void run() {
-                    testStartStopRepeatedly();
-                }
-            });
-        }
+    public void testOneClientSeveralThreads() throws InterruptedException {
+        Runnable operation = new Runnable() {
+            @Override
+            public void run() {
+                validateRegisterUnregisterRepeteadly(mTestSensorManager);
+            }
+        };
+        SensorCtsHelper.performOperationInThreads(this.getNumberOfThreadsToUse(), operation);
+    }
 
-        while(!threads.isEmpty()) {
-            Thread thread = threads.remove(0);
-            thread.join();
-        }
+    public void testSeveralClients() throws InterruptedException {
+        final Assert assertionObject = this;
+        Runnable operation = new Runnable() {
+            @Override
+            public void run() {
+                TestSensorManager testSensorManager = new TestSensorManager(
+                        assertionObject,
+                        mTestSensorManager.getUnderlyingSensorManager(),
+                        mTestSensorManager.getSensorUnderTest());
+                validateRegisterUnregisterRepeteadly(testSensorManager);
+            }
+        };
+        SensorCtsHelper.performOperationInThreads(this.getNumberOfThreadsToUse(), operation);
+    }
+
+    public void testStoppingOtherClients() {
+        // TODO: use a higher test abstraction and move these to integration tests
+        final int EVENT_COUNT = 1;
+        final int SECOND_EVENT_COUNT = 5;
+        TestSensorManager sensorManager2 = new TestSensorManager(
+                this,
+                mTestSensorManager.getUnderlyingSensorManager(),
+                mTestSensorManager.getSensorUnderTest());
+
+        mTestSensorManager.registerListener(SensorManager.SENSOR_DELAY_NORMAL);
+
+        // is receiving events
+        mTestSensorManager.getEvents(EVENT_COUNT);
+
+        // operate in a different client
+        sensorManager2.collectEvents(SensorManager.SENSOR_DELAY_FASTEST, SECOND_EVENT_COUNT);
+
+        // verify first client is still operating
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.getEvents(EVENT_COUNT);
+        assertTrue(
+                String.format("Events| expected:%d, actual:%d", EVENT_COUNT, events.length),
+                events.length >= EVENT_COUNT);
+    }
+
+    public void testStoppingOtherClientsBatching() {
+        final int EVENT_COUNT = 1;
+        final int SECOND_EVENT_COUNT = 5;
+        TestSensorManager sensorManager2 = new TestSensorManager(
+                this,
+                mTestSensorManager.getUnderlyingSensorManager(),
+                mTestSensorManager.getSensorUnderTest());
+
+        mTestSensorManager.registerListener(SensorManager.SENSOR_DELAY_NORMAL);
+
+        // is receiving events
+        mTestSensorManager.getEvents(EVENT_COUNT);
+
+        // operate in a different client
+        sensorManager2.collectBatchEvents(
+                SensorManager.SENSOR_DELAY_FASTEST,
+                SensorCtsHelper.getSecondsAsMicroSeconds(1),
+                SECOND_EVENT_COUNT);
+
+        // verify first client is still operating
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.getEvents(EVENT_COUNT);
+        assertTrue(
+                String.format("Events| expected:%d, actual:%d", EVENT_COUNT, events.length),
+                events.length >= EVENT_COUNT);
     }
 
     /**
@@ -255,50 +265,52 @@
      */
     public void testRegisterForBatchingZeroReport() {
         releaseWakeLock();
-
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                SensorManager.SENSOR_DELAY_NORMAL,
-                0 /*maxBatchReportLatencyUs*/,
-                0 /*reservedFlags*/,
-                mFlushListener);
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(10);
+        // TODO: use test wrappers to verify for reportLatency ==0 !=0
+        mTestSensorManager.collectBatchEvents(SensorManager.SENSOR_DELAY_NORMAL, 0, 10);
     }
 
     public void testCanReceiveBatchEvents() {
         releaseWakeLock();
-
-        // TODO: refactor out common code across tests that register for events and do post-process
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
+        mTestSensorManager.collectBatchEvents(
                 SensorManager.SENSOR_DELAY_NORMAL,
-                5 * 1000000 /*maxBatchReportLatencyUs*/,
-                0 /*reservedFlags*/,
-                mFlushListener);
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(10);
+                SensorCtsHelper.getSecondsAsMicroSeconds(5),
+                10 /*eventCount*/);
+    }
+
+    /**
+     * Regress:
+     * -b/10790905
+     */
+    public void ignore_testBatchingReportLatency() {
+        long startTime = SystemClock.elapsedRealtimeNanos();
+        // TODO: define the sensor frequency per sensor
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.collectBatchEvents(
+                SensorCtsHelper.getSecondsAsMicroSeconds(1),
+                SensorCtsHelper.getSecondsAsMicroSeconds(5),
+                1 /*eventCount*/);
+        long elapsedTime = SystemClock.elapsedRealtimeNanos() - startTime;
+        long expectedTime =
+                TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS) +
+                TimeUnit.NANOSECONDS.convert(500, TimeUnit.MILLISECONDS);
+
+        // TODO: ensure the proper batching time considers the size of the FIFO (fifoMaxEventCount),
+        //       and make sure that no other application is registered
+        assertTrue(
+                String.format("WaitTime| expected:%d, actual:%d", expectedTime, elapsedTime),
+                elapsedTime <= expectedTime);
     }
 
     public void testBatchEventsArriveInOrder() {
         releaseWakeLock();
 
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
+        // TODO: identify if we can reuse code from the non-batching case, same for other batch tests
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.collectBatchEvents(
                 SensorManager.SENSOR_DELAY_NORMAL,
-                5 * 1000000 /*maxBatchReportLatencyUs*/,
-                0 /*reservedFlags*/,
-                mFlushListener);
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(100);
-
-        SensorEventForTest[] events = mEventListener.getAllEvents();
+                SensorCtsHelper.getSecondsAsMicroSeconds(5),
+                100);
         for(int i = 1; i < events.length; ++i) {
-            long previousTimestamp = events[i-1].getTimestamp();
-            long timestamp = events[i].getTimestamp();
+            long previousTimestamp = events[i-1].timestamp;
+            long timestamp = events[i].timestamp;
             assertTrue(
                     String.format("[timestamp:%d] %d >= %d", i, previousTimestamp, timestamp),
                     previousTimestamp < timestamp);
@@ -307,22 +319,7 @@
 
     public void testStartStopBatchingRepeatedly() {
         releaseWakeLock();
-
-        for(int i = 0; i < this.getLowNumberOfIterationsToExecute(); ++i) {
-            String iterationInfo = String.format("registerListener:%d", i);
-            boolean result = mSensorManager.registerListener(
-                    mEventListener,
-                    mSensorUnderTest,
-                    SensorManager.SENSOR_DELAY_FASTEST,
-                    5 * 1000000 /*maxBatchReportLatencyUs*/,
-                    0 /*reservedFlags*/,
-                    mFlushListener);
-
-            assertTrue(iterationInfo, result);
-            mEventListener.waitForEvents(5, iterationInfo);
-
-            mSensorManager.unregisterListener(mEventListener, mSensorUnderTest);
-        }
+        validateRegisterUnregisterRepeteadlyBatching(mTestSensorManager);
     }
 
     public void testUpdateBatchRate() {
@@ -351,41 +348,91 @@
                     rate = this.getMaxFrequencySupportedInuS() * generator.nextInt(10);
             }
 
-            String iterationInfo = String.format("registerListener:%d, rate:%d", i, rate);
-            boolean result = mSensorManager.registerListener(
-                    mEventListener,
-                    mSensorUnderTest,
+            String iterationInfo = String.format("iteration:%d, rate:%d", i, rate);
+            mTestSensorManager.collectBatchEvents(
                     rate,
-                    generator.nextInt(5 * 1000000),
-                    0 /*reservedFlags*/,
-                    mFlushListener);
-            assertTrue(iterationInfo, result);
-
-            mEventListener.waitForEvents(generator.nextInt(5) + 1, iterationInfo);
-            mSensorManager.unregisterListener(mEventListener, mSensorUnderTest);
-            mEventListener.clearEvents();
+                    generator.nextInt(SensorCtsHelper.getSecondsAsMicroSeconds(5)),
+                    generator.nextInt(5) + 1,
+                    iterationInfo);
         }
     }
 
-    public void testSeveralClientsBatching() {
-        ArrayList<Thread> threads = new ArrayList<Thread>();
-        for(int i = 0; i < this.getNumberOfThreadsToUse(); ++i) {
-            threads.add(new Thread() {
-                @Override
-                public void run() {
-                    testStartStopBatchingRepeatedly();
-                }
-            });
-        }
-
-        while(!threads.isEmpty()) {
-            Thread thread = threads.remove(0);
-            try {
-                thread.join();
-            } catch(InterruptedException e) {
-                // just continue
+    public void testOneClientSeveralThreadsBatching() throws InterruptedException {
+        Runnable operation = new Runnable() {
+            @Override
+            public void run() {
+                validateRegisterUnregisterRepeteadlyBatching(mTestSensorManager);
             }
-        }
+        };
+        SensorCtsHelper.performOperationInThreads(this.getNumberOfThreadsToUse(), operation);
+    }
+
+    public void testSeveralClientsBatching() throws InterruptedException {
+        final Assert assertionObject = this;
+        Runnable operation = new Runnable() {
+            @Override
+            public void run() {
+                TestSensorManager testSensorManager = new TestSensorManager(
+                        assertionObject,
+                        mTestSensorManager.getUnderlyingSensorManager(),
+                        mTestSensorManager.getSensorUnderTest());
+                validateRegisterUnregisterRepeteadlyBatching(testSensorManager);
+            }
+        };
+        SensorCtsHelper.performOperationInThreads(this.getNumberOfThreadsToUse(), operation);
+    }
+
+    public void testBatchingStoppingOtherClients() {
+        final int EVENT_COUNT = 1;
+        final int SECOND_EVENT_COUNT = 5;
+        TestSensorManager sensorManager2 = new TestSensorManager(
+                this,
+                mTestSensorManager.getUnderlyingSensorManager(),
+                mTestSensorManager.getSensorUnderTest());
+
+        mTestSensorManager.registerBatchListener(
+                SensorManager.SENSOR_DELAY_NORMAL,
+                SensorCtsHelper.getSecondsAsMicroSeconds(5));
+
+        // is receiving events
+        mTestSensorManager.getEvents(EVENT_COUNT);
+
+        // operate in a different client
+        sensorManager2.collectEvents(SensorManager.SENSOR_DELAY_FASTEST, SECOND_EVENT_COUNT);
+
+        // verify first client is still operating
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.getEvents(EVENT_COUNT);
+        assertTrue(
+                String.format("Events| expected:%d, actual:%d", EVENT_COUNT, events.length),
+                events.length >= EVENT_COUNT);
+    }
+
+    public void testBatchingStoppingOtherClientsBatching() {
+        final int EVENT_COUNT = 1;
+        final int SECOND_EVENT_COUNT = 5;
+        TestSensorManager sensorManager2 = new TestSensorManager(
+                this,
+                mTestSensorManager.getUnderlyingSensorManager(),
+                mTestSensorManager.getSensorUnderTest());
+
+        mTestSensorManager.registerBatchListener(
+                SensorManager.SENSOR_DELAY_NORMAL,
+                SensorCtsHelper.getSecondsAsMicroSeconds(5));
+
+        // is receiving events
+        mTestSensorManager.getEvents(EVENT_COUNT);
+
+        // operate in a different client
+        sensorManager2.collectBatchEvents(
+                SensorManager.SENSOR_DELAY_FASTEST,
+                SensorCtsHelper.getSecondsAsMicroSeconds(1),
+                SECOND_EVENT_COUNT);
+
+        // verify first client is still operating
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.getEvents(EVENT_COUNT);
+        assertTrue(
+                String.format("Events| expected:%d, actual:%d", EVENT_COUNT, events.length),
+                events.length >= EVENT_COUNT);
     }
 
     /**
@@ -394,33 +441,21 @@
     public void testEventJittering() {
         final long EXPECTED_TIMESTAMP_NS = this.getMaxFrequencySupportedInuS() * 1000;
         final long THRESHOLD_IN_NS = EXPECTED_TIMESTAMP_NS / 10; // 10%
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                this.getMaxFrequencySupportedInuS());
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(100);
 
-        SensorEventForTest[] events = mEventListener.getAllEvents();
-        ArrayList<Long> timestampDeltas = new ArrayList<Long>();
-        for(int i = 1; i < events.length; ++i) {
-            long previousTimestamp = events[i-1].getTimestamp();
-            long timestamp = events[i].getTimestamp();
-            long delta = timestamp - previousTimestamp;
-            long jitterValue = Math.abs(EXPECTED_TIMESTAMP_NS - delta);
-            timestampDeltas.add(jitterValue);
-        }
-
-        Collections.sort(timestampDeltas);
-        long percentile95InNs = timestampDeltas.get(95);
-        long actualPercentValue = (percentile95InNs * 100) / EXPECTED_TIMESTAMP_NS;
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.collectEvents(
+                this.getMaxFrequencySupportedInuS(),
+                100);
+        ArrayList<Double> jitterValues = new ArrayList<Double>();
+        double jitterMean = SensorCtsHelper.getJitterMean(events, jitterValues);
+        double percentile95InNs = SensorCtsHelper.get95PercentileValue(jitterValues);
 
         if(percentile95InNs > THRESHOLD_IN_NS) {
-            for(long jitter : timestampDeltas) {
-                Log.e(LOG_TAG, "Jittering delta: " + jitter);
+            for(double jitter : jitterValues) {
+                Log.e(LOG_TAG, "Jitter: " + jitter);
             }
+            double actualPercentValue = (percentile95InNs * 100) / jitterMean;
             String message = String.format(
-                    "95%%Jitter| 10%%:%dns, observed:%dns(%d%%)",
+                    "95%% Jitter| 10%%:%dns, actual:%fns(%.2f%%)",
                     THRESHOLD_IN_NS,
                     percentile95InNs,
                     actualPercentValue);
@@ -428,6 +463,29 @@
         }
     }
 
+    public void testFrequencyAccuracy() {
+        final long EXPECTED_TIMESTAMP_NS = this.getMaxFrequencySupportedInuS() * 1000;
+        final long THRESHOLD_IN_NS = EXPECTED_TIMESTAMP_NS / 10; // 10%
+
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.collectEvents(
+                this.getMaxFrequencySupportedInuS(),
+                100);
+        ArrayList<Long> timestampDelayValues = new ArrayList<Long>();
+        Double frequencyMean = SensorCtsHelper.getAverageTimestampDelayWithValues(
+                events,
+                timestampDelayValues);
+        if(Math.abs(EXPECTED_TIMESTAMP_NS - frequencyMean) > THRESHOLD_IN_NS) {
+            for(long value : timestampDelayValues) {
+                Log.e(LOG_TAG, "TimestampDelay: " + value);
+            }
+            String message = String.format(
+                    "Frequency| expected:%d, actual:%f",
+                    EXPECTED_TIMESTAMP_NS,
+                    frequencyMean);
+            fail(message);
+        }
+    }
+
     /**
      * Private helpers.
      */
@@ -444,252 +502,84 @@
         }
     }
 
-    private void collectBugreport() {
-        String commands[] = new String[] {
-                "dumpstate",
-                "dumpsys",
-                "logcat -d -v threadtime",
-                "exit"
-        };
-
-        SimpleDateFormat dateFormat = new SimpleDateFormat("M-d-y_H:m:s.S");
-        String outputFile = String.format(
-                "%s/%s_%s",
-                this.getLogTag(),
-                "/sdcard/Download",
-                dateFormat.format(new Date()));
-
-        DataOutputStream processOutput = null;
-        try {
-            Process process = Runtime.getRuntime().exec("/system/bin/sh -");
-            processOutput = new DataOutputStream(process.getOutputStream());
-
-            for(String command : commands) {
-                processOutput.writeBytes(String.format("%s >> %s\n", command, outputFile));
-            }
-
-            processOutput.flush();
-            process.waitFor();
-
-            Log.d(this.getLogTag(), String.format("Bug-Report collected at: %s", outputFile));
-        } catch (IOException e) {
-            fail("Unable to collect Bug Report. " + e.toString());
-        } catch (InterruptedException e) {
-            fail("Unable to collect Bug Report. " + e.toString());
-        } finally {
-            if(processOutput != null) {
-                try {
-                    processOutput.close();
-                } catch(IOException e) {}
-            }
-        }
-    }
-
     /**
      * Test method helper implementations
      */
-    protected void validateSensorEvent(
-            float expectedX,
-            float expectedY,
-            float expectedZ,
-            float threshold) {
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                SensorManager.SENSOR_DELAY_FASTEST);
+    protected void validateNormForSensorEvent(float reference, float threshold, int axisCount) {
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.collectEvents(
+                SensorManager.SENSOR_DELAY_FASTEST,
+                1);
+        TestSensorManager.SensorEventForTest event = events[0];
 
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(1);
-        SensorEventForTest event = mEventListener.getLastEvent();
+        StringBuilder valuesBuilder = new StringBuilder();
+        double norm = 0.0;
+        for(int i = 0; i < axisCount; ++i) {
+            float value = event.values[i];
+            norm += Math.pow(value, 2);
 
-        float xValue = event.getX();
-        assertTrue(
-                String.format("x-axis| expected:%f, actual:%f, threshold:%f", expectedX, xValue, threshold),
-                Math.abs(expectedX - xValue) <= threshold);
+            valuesBuilder.append(value);
+            valuesBuilder.append(", ");
+        }
+        norm = Math.sqrt(norm);
 
-        float yValue = event.getY();
-        assertTrue(
-                String.format("y-axis| expected:%f, actual:%f, threshold:%f", expectedY, yValue, threshold),
-                Math.abs(expectedY - yValue) <= threshold);
-
-        float zValue = event.getZ();
-        assertTrue(
-                String.format("z-axis| expected:%f, actual:%f, threshold:%f", expectedZ, zValue, threshold),
-                Math.abs(expectedZ - zValue) <= threshold);
+        String message = String.format(
+                "Norm| expected:%f, threshold:%f, actual:%f (%s)",
+                reference,
+                threshold,
+                norm,
+                valuesBuilder.toString());
+        assertTrue(message, Math.abs(reference - norm) <= threshold);
     }
 
-    protected void validateVarianceWhileStatic(
-            float referenceX,
-            float referenceY,
-            float referenceZ,
-            float threshold) {
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                this.getMaxFrequencySupportedInuS());
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(100);
-
-        SensorEventForTest[] events = mEventListener.getAllEvents();
-        ArrayList<Float> deltaValuesX = new ArrayList<Float>();
-        ArrayList<Float> deltaValuesY = new ArrayList<Float>();
-        ArrayList<Float> deltaValuesZ = new ArrayList<Float>();
-        for(int i = 0; i < events.length; ++i) {
-            SensorEventForTest event = events[i];
-            deltaValuesX.add(Math.abs(event.getX() - referenceX));
-            deltaValuesY.add(Math.abs(event.getY() - referenceY));
-            deltaValuesZ.add(Math.abs(event.getZ() - referenceZ));
+    protected void validateRegisterUnregisterRepeteadly(TestSensorManager testSensorManager) {
+        for(int i = 0; i < this.getLowNumberOfIterationsToExecute(); ++i) {
+            String iterationInfo = String.format("iteration:%d", i);
+            testSensorManager.collectEvents(SensorManager.SENSOR_DELAY_FASTEST, 1, iterationInfo);
         }
+    }
 
-        Collections.sort(deltaValuesX);
-        float percentile95X = deltaValuesX.get(95);
-        if(percentile95X > threshold) {
-            for(float valueX : deltaValuesX) {
-                Log.e(LOG_TAG, "Variance|X delta: " + valueX);
-            }
-            String message = String.format(
-                    "95%%Variance|X expected:%f, observed:%f",
-                    threshold,
-                    percentile95X);
-            fail(message);
+    protected void validateRegisterUnregisterRepeteadlyBatching(
+            TestSensorManager testSensorManager) {
+        // TODO: refactor if allowed with test wrapper abstractions
+        for(int i = 0; i < this.getLowNumberOfIterationsToExecute(); ++i) {
+            testSensorManager.collectBatchEvents(
+                    SensorManager.SENSOR_DELAY_FASTEST,
+                    SensorCtsHelper.getSecondsAsMicroSeconds(5),
+                    5 /*eventCont*/,
+                    String.format("iteration:%d", i));
         }
+    }
 
-        Collections.sort(deltaValuesY);
-        float percentile95Y = deltaValuesY.get(95);
-        if(percentile95Y > threshold) {
-            for(float valueY : deltaValuesY) {
-                Log.e(LOG_TAG, "Variance|Y delta: " + valueY);
-            }
-            String message = String.format(
-                    "95%%Variance|Y expected:%f, observed:%f",
-                    threshold,
-                    percentile95Y);
-            fail(message);
-        }
+    protected void validateStandardDeviationWhileStatic(
+            float expectedStandardDeviation,
+            int axisCount) {
+        // TODO: refactor the report parameter with test wrappers if available
+        TestSensorManager.SensorEventForTest[] events = mTestSensorManager.collectEvents(
+                this.getMaxFrequencySupportedInuS(),
+                100);
 
-        Collections.sort(deltaValuesZ);
-        float percentile95Z = deltaValuesZ.get(95);
-        if(percentile95Z > threshold) {
-            for(float valueZ : deltaValuesZ) {
-                Log.e(LOG_TAG, "Variance|Z delta: " + valueZ);
+        for(int i = 0; i < axisCount; ++i) {
+            ArrayList<Float> values = new ArrayList<Float>();
+            for(TestSensorManager.SensorEventForTest event : events) {
+                values.add(event.values[i]);
             }
+
+            double standardDeviation = SensorCtsHelper.getStandardDeviation(values);
             String message = String.format(
-                    "95%%Variance|Z expected:%f, observed:%f",
-                    threshold,
-                    percentile95Z);
-            fail(message);
+                    "StandardDeviation| axis:%d, expected:%f, actual:%f",
+                    i,
+                    expectedStandardDeviation,
+                    standardDeviation);
+            assertTrue(message, standardDeviation <= expectedStandardDeviation);
         }
     }
 
     /**
      * Private class definitions to support test of event handlers.
      */
-    protected class SensorEventForTest {
-        private Sensor mSensor;
-        private long mTimestamp;
-        private int mAccuracy;
-
-        private float mValueX;
-        private float mValueY;
-        private float mValueZ;
-
-        public SensorEventForTest(SensorEvent event) {
-            mSensor = event.sensor;
-            mTimestamp = event.timestamp;
-            mAccuracy = event.accuracy;
-            mValueX = event.values[0];
-            mValueY = event.values[1];
-            mValueZ = event.values[2];
-        }
-
-        public Sensor getSensor() {
-            return mSensor;
-        }
-
-        public int getAccuracy() {
-            return mAccuracy;
-        }
-
-        public float getX() {
-            return mValueX;
-        }
-
-        public float getY() {
-            return mValueY;
-        }
-
-        public float getZ() {
-            return mValueZ;
-        }
-
-        public long getTimestamp() {
-            return mTimestamp;
-        }
-    }
-
-    protected class TestSensorListener implements SensorEventListener {
-        private final ConcurrentLinkedDeque<SensorEventForTest> mSensorEventsList =
-                new ConcurrentLinkedDeque<SensorEventForTest>();
-        private volatile CountDownLatch mEventLatch;
-
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            // copy the event because there is no better way to do this in the platform
-            mSensorEventsList.addLast(new SensorEventForTest(event));
-
-            CountDownLatch latch = mEventLatch;
-            if(latch != null) {
-                latch.countDown();
-            }
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-
-        public void waitForEvents(int eventCount) {
-            waitForEvents(eventCount, null);
-        }
-
-        public void waitForEvents(int eventCount, String timeoutInfo) {
-            mEventLatch = new CountDownLatch(eventCount);
-            try {
-                boolean awaitCompleted = mEventLatch.await(getWaitTimeoutInSeconds(), TimeUnit.SECONDS);
-                if(!awaitCompleted) {
-                    collectBugreport();
-                }
-
-                String assertMessage = String.format(
-                        "WaitForEvents:%d, available:%d, %s",
-                        eventCount,
-                        mSensorEventsList.size(),
-                        timeoutInfo);
-                assertTrue(assertMessage, awaitCompleted);
-            } catch(InterruptedException e) { }
-        }
-
-        public SensorEventForTest getLastEvent() {
-            return mSensorEventsList.getLast();
-        }
-
-        public SensorEventForTest[] getAllEvents() {
-            return mSensorEventsList.toArray(new SensorEventForTest[0]);
-        }
-
-        public void clearEvents() {
-            mSensorEventsList.clear();
-        }
-    }
-
     private class TestTriggerListener extends TriggerEventListener {
         @Override
         public void onTrigger(TriggerEvent event) {
         }
     }
-
-    private class TestFlushListener implements FlushCompleteListener {
-        @Override
-        public void onFlushCompleted(Sensor sensor) {
-        }
-    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorGyroscopeTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorGyroscopeTest.java
index d3d72a3..b7c082e 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorGyroscopeTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorGyroscopeTest.java
@@ -19,6 +19,8 @@
 import android.hardware.Sensor;
 
 public class SensorGyroscopeTest extends SensorCommonTests {
+    private final int AXIS_COUNT = 3;
+
     @Override
     protected int getMaxFrequencySupportedInuS() {
         return 10000; // 100Hz
@@ -32,20 +34,12 @@
     @Override
     public void testEventValidity() {
         final float THRESHOLD = 0.1f; // dps
-        validateSensorEvent(
-                0 /*x-axis*/,
-                0 /*y-axis*/,
-                0 /*z-axis*/,
-                THRESHOLD);
+        validateNormForSensorEvent(0 /*reference*/, THRESHOLD, AXIS_COUNT);
     }
 
     @Override
-    public void testVarianceWhileStatic() {
-        final float THRESHOLD = 0.1f; // dps
-        validateVarianceWhileStatic(
-                0 /*x-axis*/,
-                0 /*y-axis*/,
-                0 /*z-axis*/,
-                THRESHOLD);
+    public void testStandardDeviationWhileStatic() {
+        final float STANDARD_DEVIATION = 0.5f; // dps
+        validateStandardDeviationWhileStatic(STANDARD_DEVIATION, AXIS_COUNT);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
new file mode 100644
index 0000000..f764c6c
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2008 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.hardware.Sensor;
+import android.hardware.SensorManager;
+
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.TestSensorManager;
+
+import android.os.PowerManager;
+
+import android.test.AndroidTestCase;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class SensorIntegrationTests extends AndroidTestCase {
+    protected final String LOG_TAG = "SensorIntegrationTests";
+    private PowerManager.WakeLock mWakeLock;
+    private SensorManager mSensorManager;
+
+    /**
+     * Test execution methods
+     */
+    @Override
+    protected void setUp() throws Exception {
+        PowerManager powerManager = (PowerManager) this.getContext().getSystemService(
+                Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+        mWakeLock.acquire();
+
+        mSensorManager = (SensorManager) this.getContext().getSystemService(
+                Context.SENSOR_SERVICE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mSensorManager = null;
+
+        mWakeLock.release();
+        mWakeLock = null;
+    }
+
+    /**
+     * Test cases.
+     */
+    public void testBatchAndFlush() throws InterruptedException {
+        List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+        for(Sensor sensor : sensorList) {
+            // skip all non-continuous mode sensors.
+            switch(sensor.getType()) {
+                case Sensor.TYPE_SIGNIFICANT_MOTION:
+                case Sensor.TYPE_STEP_COUNTER:
+                case Sensor.TYPE_STEP_DETECTOR:
+                case Sensor.TYPE_LIGHT:
+                case Sensor.TYPE_PROXIMITY:
+                case Sensor.TYPE_AMBIENT_TEMPERATURE:
+                    continue;
+            }
+
+            TestSensorManager sensorManager = new TestSensorManager(this, mSensorManager, sensor);
+            sensorManager.registerBatchListener(SensorManager.SENSOR_DELAY_NORMAL, 0);
+
+            // wait for 25 events and call flush
+            sensorManager.getEvents(25);
+            sensorManager.waitForFlush();
+
+            sensorManager.unregisterListener();
+        }
+    }
+
+    /**
+     * Regress:
+     * - b/10641388
+     */
+    public void testAccelerometerDoesNotStopGyroscope() {
+        validateSensorCanBeStoppedIndependently(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE);
+    }
+
+    public void testAccelerometerDoesNotStopMagnetometer() {
+        validateSensorCanBeStoppedIndependently(
+                Sensor.TYPE_ACCELEROMETER,
+                Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    public void testGyroscopeDoesNotStopAccelerometer() {
+        validateSensorCanBeStoppedIndependently(Sensor.TYPE_GYROSCOPE, Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void testGyroscopeDoesNotStopMagnetometer() {
+        validateSensorCanBeStoppedIndependently(Sensor.TYPE_GYROSCOPE, Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    public void testMagnetometerDoesNotStopAccelerometer() {
+        validateSensorCanBeStoppedIndependently(
+                Sensor.TYPE_MAGNETIC_FIELD,
+                Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void testMagnetometerDoesNotStopGyroscope() {
+        validateSensorCanBeStoppedIndependently(Sensor.TYPE_MAGNETIC_FIELD, Sensor.TYPE_GYROSCOPE);
+    }
+
+    /**
+     * Private methods for sensor validation.
+     */
+    public void validateSensorCanBeStoppedIndependently(int sensorTypeTester, int sensorTypeTestee) {
+        // if any of the required sensors is not supported, skip the test
+        Sensor sensorTester = mSensorManager.getDefaultSensor(sensorTypeTester);
+        if(sensorTester == null) {
+            return;
+        }
+        Sensor sensorTestee = mSensorManager.getDefaultSensor(sensorTypeTestee);
+        if(sensorTestee == null) {
+            return;
+        }
+
+        TestSensorManager tester = new TestSensorManager(this, mSensorManager, sensorTester);
+        tester.registerListener(SensorManager.SENSOR_DELAY_NORMAL);
+
+        TestSensorManager testee = new TestSensorManager(this, mSensorManager, sensorTestee);
+        testee.registerBatchListener(
+                (int) TimeUnit.MICROSECONDS.convert(200, TimeUnit.MILLISECONDS),
+                SensorCtsHelper.getSecondsAsMicroSeconds(10));
+
+        testee.getEvents(10);
+        tester.getEvents(5);
+
+        tester.unregisterListener();
+        testee.getEvents(5);
+
+        // clean up
+        tester.close();
+        testee.close();
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorMagneticFieldTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorMagneticFieldTest.java
index 10cabb8..e4041d6 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorMagneticFieldTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorMagneticFieldTest.java
@@ -19,12 +19,9 @@
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collections;
-
 public class SensorMagneticFieldTest extends SensorCommonTests {
+    private final int AXIS_COUNT = 3;
+
     @Override
     protected int getMaxFrequencySupportedInuS() {
         return 100000; // 10Hz
@@ -37,73 +34,15 @@
 
     @Override
     public void testEventValidity() {
-        validateSensorEvent(
-                0 /*x-axis*/,
-                0 /*y-axis*/,
-                0 /*z-axis*/,
-                SensorManager.MAGNETIC_FIELD_EARTH_MAX);
+        validateNormForSensorEvent(
+                SensorManager.MAGNETIC_FIELD_EARTH_MAX,
+                SensorManager.MAGNETIC_FIELD_EARTH_MIN,
+                AXIS_COUNT);
     }
 
     @Override
-    public void testVarianceWhileStatic() {
-        float THRESHOLD_IN_UT = SensorManager.MAGNETIC_FIELD_EARTH_MAX / 10;
-        boolean result = mSensorManager.registerListener(
-                mEventListener,
-                mSensorUnderTest,
-                this.getMaxFrequencySupportedInuS());
-        assertTrue("registerListener", result);
-        mEventListener.waitForEvents(100);
-
-        SensorEventForTest[] events = mEventListener.getAllEvents();
-        ArrayList<Float> deltaValuesX = new ArrayList<Float>();
-        ArrayList<Float> deltaValuesY = new ArrayList<Float>();
-        ArrayList<Float> deltaValuesZ = new ArrayList<Float>();
-        for(int i = 1; i < events.length; ++i) {
-            SensorEventForTest previousEvent = events[i-1];
-            SensorEventForTest event = events[i];
-
-            deltaValuesX.add(Math.abs(event.getX() - previousEvent.getX()));
-            deltaValuesY.add(Math.abs(event.getY() - previousEvent.getY()));
-            deltaValuesZ.add(Math.abs(event.getZ() - previousEvent.getZ()));
-        }
-
-        Collections.sort(deltaValuesX);
-        float percentile95X = deltaValuesX.get(95);
-        if(percentile95X > THRESHOLD_IN_UT) {
-            for(float valueX : deltaValuesX) {
-                Log.e(LOG_TAG, "Variance|X delta: " + valueX);
-            }
-            String message = String.format(
-                    "95%%Variance|X expected:%f, observed:%f",
-                    THRESHOLD_IN_UT,
-                    percentile95X);
-            fail(message);
-        }
-
-        Collections.sort(deltaValuesY);
-        float percentile95Y = deltaValuesY.get(95);
-        if(percentile95Y > THRESHOLD_IN_UT) {
-            for(float valueY : deltaValuesY) {
-                Log.e(LOG_TAG, "Variance|Y delta: " + valueY);
-            }
-            String message = String.format(
-                    "95%%Variance|Y expected:%f, observed:%f",
-                    THRESHOLD_IN_UT,
-                    percentile95Y);
-            fail(message);
-        }
-
-        Collections.sort(deltaValuesZ);
-        float percentile95Z = deltaValuesZ.get(95);
-        if(percentile95Z > THRESHOLD_IN_UT) {
-            for(float valueZ : deltaValuesZ) {
-                Log.e(LOG_TAG, "Variance|Z delta: " + valueZ);
-            }
-            String message = String.format(
-                    "95%%Variance|Z expected:%f, observed:%f",
-                    THRESHOLD_IN_UT,
-                    percentile95Z);
-            fail(message);
-        }
+    public void testStandardDeviationWhileStatic() {
+        final float STANDARD_DEVIATION = 2f; // uT
+        validateStandardDeviationWhileStatic(STANDARD_DEVIATION, AXIS_COUNT);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
index 4af2a45..a0f1a96 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
@@ -23,10 +23,10 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.hardware.FlushCompleteListener;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
+import android.hardware.SensorEventListener2;
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
@@ -149,7 +149,8 @@
             }
 
             final CountDownLatch eventReceived = new CountDownLatch(25);
-            SensorEventListener listener = new SensorEventListener() {
+            final CountDownLatch flushReceived = new CountDownLatch(1);
+            SensorEventListener2 listener = new SensorEventListener2() {
                 @Override
                 public void onSensorChanged(SensorEvent event) {
                     eventReceived.countDown();
@@ -158,22 +159,18 @@
                 @Override
                 public void onAccuracyChanged(Sensor sensor, int accuracy) {
                 }
-            };
 
-            final CountDownLatch flushReceived = new CountDownLatch(1);
-            FlushCompleteListener flushCompleteListener = new FlushCompleteListener() {
                 @Override
                 public void onFlushCompleted(Sensor sensor) {
                     flushReceived.countDown();
                 }
             };
             boolean result = mSensorManager.registerListener(listener, sensor,
-                                            SensorManager.SENSOR_DELAY_NORMAL, 10000000, 0,
-                                            flushCompleteListener);
+                                            SensorManager.SENSOR_DELAY_NORMAL, 10000000);
             assertTrue(result);
             // Wait for 25 events and call flush.
             eventReceived.await();
-            result = mSensorManager.flush(sensor);
+            result = mSensorManager.flush(listener);
             assertTrue(result);
             flushReceived.await();
             mSensorManager.unregisterListener(listener);
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
new file mode 100644
index 0000000..5abdd06
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import java.text.SimpleDateFormat;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Set of static helper methods for CTS tests.
+ */
+public class SensorCtsHelper {
+    /**
+     * This is an static class.
+     */
+    private SensorCtsHelper() {}
+
+    public static <TValue extends Comparable> TValue get95PercentileValue(
+            Collection<TValue> collection) {
+        validateCollection(collection);
+
+        ArrayList<TValue> arrayCopy = new ArrayList<TValue>(collection);
+        Collections.sort(arrayCopy);
+
+        // zero-based array index
+        int arrayIndex = (int)(arrayCopy.size() * 0.95) - 1;
+        if(arrayIndex < 0) {
+            arrayIndex = 0;
+        }
+
+        return arrayCopy.get(arrayIndex);
+    }
+
+    // TODO: are there any internal libraries for this?
+    public static <TValue extends Number> double getMean(Collection<TValue> collection) {
+        validateCollection(collection);
+
+        double sum = 0.0;
+        for(TValue value : collection) {
+            sum += value.doubleValue();
+        }
+        return sum / collection.size();
+    }
+
+    public static <TValue extends Number> double getVariance(Collection<TValue> collection) {
+        validateCollection(collection);
+
+        double mean = getMean(collection);
+        ArrayList<Double> squaredDifferences = new ArrayList<Double>();
+        for(TValue value : collection) {
+            double difference = mean - value.doubleValue();
+            squaredDifferences.add(Math.pow(difference, 2));
+        }
+
+        double variance = getMean(squaredDifferences);
+        return variance;
+    }
+
+    public static <TValue extends Number> double getStandardDeviation(Collection<TValue> collection) {
+        validateCollection(collection);
+
+        double variance = getVariance(collection);
+        return Math.sqrt(variance);
+    }
+
+    /**
+     * Gets the jitter values associated with a set of sensor events.
+     *
+     * @param events The events to use to obtain the jittering information.
+     * @param jitterValues The Collection that will contain the computed jitter values.
+     * @return The mean of the jitter Values.
+     */
+    public static double getJitterMean(
+            TestSensorManager.SensorEventForTest events[],
+            Collection<Double> jitterValues) {
+        ArrayList<Long> timestampDelayValues = new ArrayList<Long>();
+        double averageTimestampDelay = SensorCtsHelper.getAverageTimestampDelayWithValues(events,
+                timestampDelayValues);
+        for(long frequency : timestampDelayValues) {
+            jitterValues.add(Math.abs(averageTimestampDelay - frequency));
+        }
+
+        double jitterMean = SensorCtsHelper.getMean(timestampDelayValues);
+        return jitterMean;
+    }
+
+    /**
+     * Gets the frequency values associated with a set of sensor events.
+     *
+     * @param events The events to use to obtain the frequency information.
+     * @param timestampDelayValues The Collection that will contain the computed frequency values.
+     * @return The mean of the frequency values.
+     */
+    public static double getAverageTimestampDelayWithValues(
+            TestSensorManager.SensorEventForTest events[],
+            Collection<Long> timestampDelayValues) {
+        for(int i = 1; i < events.length; ++i) {
+            long previousTimestamp = events[i-1].timestamp;
+            long timestamp = events[i].timestamp;
+            timestampDelayValues.add(timestamp - previousTimestamp);
+        }
+
+        double timestampDelayMean = SensorCtsHelper.getMean(timestampDelayValues);
+        return timestampDelayMean;
+    }
+
+    public static int getSecondsAsMicroSeconds(int seconds) {
+        return (int) TimeUnit.MICROSECONDS.convert(seconds, TimeUnit.SECONDS);
+    }
+
+    /**
+     * NOTE: The bug report is usually written to /sdcard/Downloads
+     */
+    public static void collectBugreport(String collectorId)
+            throws IOException, InterruptedException {
+        String commands[] = new String[] {
+                "dumpstate",
+                "dumpsys",
+                "logcat -d -v threadtime",
+                "exit"
+        };
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("M-d-y_H:m:s.S");
+        String outputFile = String.format(
+                "%s/%s_%s",
+                collectorId,
+                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
+                dateFormat.format(new Date()));
+
+        DataOutputStream processOutput = null;
+        try {
+            Process process = Runtime.getRuntime().exec("/system/bin/sh -");
+            processOutput = new DataOutputStream(process.getOutputStream());
+
+            for(String command : commands) {
+                processOutput.writeBytes(String.format("%s >> %s\n", command, outputFile));
+            }
+
+            processOutput.flush();
+            process.waitFor();
+
+            Log.d(collectorId, String.format("Bug-Report collected at: %s", outputFile));
+        } finally {
+            if(processOutput != null) {
+                try {
+                    processOutput.close();
+                } catch(IOException e) {}
+            }
+        }
+    }
+
+    public static void performOperationInThreads(int numberOfThreadsToUse, Runnable operation)
+            throws InterruptedException {
+        ArrayList<Thread> threads = new ArrayList<Thread>();
+        for(int i = 0; i < numberOfThreadsToUse; ++i) {
+            threads.add(new Thread(operation));
+        }
+
+        while(!threads.isEmpty()) {
+            Thread thread = threads.remove(0);
+            thread.join();
+        }
+    }
+
+    /**
+     * Private helpers
+     */
+    private static void validateCollection(Collection collection) {
+        if(collection == null || collection.size() == 0) {
+            throw new IllegalStateException("Collection cannot be null or empty");
+        }
+    }
+}
+
+
+
+
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java
new file mode 100644
index 0000000..9a95e9b
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener2;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+
+import java.io.Closeable;
+
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.Assert;
+
+/**
+ * Test class to wrap SensorManager with verifications and test checks.
+ * This class allows to perform operations in the Sensor Manager and performs all the expected test
+ * verification on behalf of th owner.
+ * An object can be used to quickly writing tests that focus on the scenario that needs to be verified,
+ * and not in the implicit verifications that need to take place at any step.
+ */
+public class TestSensorManager implements Closeable {
+    private final int WAIT_TIMEOUT_IN_SECONDS = 30;
+
+    private Assert mAssert;
+    private SensorManager mSensorManager;
+    private Sensor mSensorUnderTest;
+    private TestSensorListener mEventListener;
+
+    public TestSensorManager(Assert assertionObject, SensorManager sensorManager, Sensor sensor) {
+        mAssert = assertionObject;
+        mSensorManager = sensorManager;
+        mSensorUnderTest = sensor;
+
+        mEventListener = new TestSensorListener();
+    }
+
+    public void close() {
+        this.unregisterListener();
+        mEventListener = null;
+        mSensorUnderTest = null;
+    }
+
+    public SensorManager getUnderlyingSensorManager() {
+        return mSensorManager;
+    }
+
+    public Sensor getSensorUnderTest() {
+        return mSensorUnderTest;
+    }
+
+    public void registerListener(int delay, String debugInfo) {
+        mAssert.assertTrue(
+                "registerListener| " + debugInfo,
+                mSensorManager.registerListener(mEventListener, mSensorUnderTest, delay));
+    }
+
+    public void registerListener(int delay) {
+        registerListener(delay, "");
+    }
+
+    public void registerBatchListener(int delay, int reportLatency, String debugInfo) {
+        boolean result = mSensorManager.registerListener(
+                mEventListener,
+                mSensorUnderTest,
+                delay,
+                reportLatency);
+        mAssert.assertTrue("registerBatchListener| " + debugInfo, result);
+    }
+
+    public void registerBatchListener(int delay, int reportLatency) {
+        registerBatchListener(delay, reportLatency, "");
+    }
+
+    public void unregisterListener() {
+        mSensorManager.unregisterListener(mEventListener, mSensorUnderTest);
+    }
+
+    public SensorEventForTest[] getEvents(int count, String debugInfo) {
+        mEventListener.waitForEvents(count, debugInfo);
+        SensorEventForTest[] events = mEventListener.getAllEvents();
+        mEventListener.clearEvents();
+
+        return events;
+    }
+
+    public SensorEventForTest[] getEvents(int count) {
+        return this.getEvents(count, "");
+    }
+
+    public SensorEventForTest[] collectEvents(
+            int collectionDelay,
+            int eventCount,
+            String debugInfo) {
+        this.registerListener(collectionDelay, debugInfo);
+        SensorEventForTest[] events = this.getEvents(eventCount, debugInfo);
+        this.unregisterListener();
+
+        return events;
+    }
+
+    public SensorEventForTest[] collectEvents(int collectionDelay, int eventCount) {
+        return this.collectEvents(collectionDelay, eventCount, "");
+    }
+
+    public SensorEventForTest[] collectBatchEvents(
+            int collectionDelay,
+            int batchReportLatency,
+            int eventCount,
+            String debugInfo) {
+        this.registerBatchListener(collectionDelay, batchReportLatency, debugInfo);
+        SensorEventForTest[] events = this.getEvents(eventCount, debugInfo);
+        this.unregisterListener();
+
+        return events;
+    }
+
+    public SensorEventForTest[] collectBatchEvents(
+            int collectionDelay,
+            int batchReportLatency,
+            int eventCount) {
+        return this.collectBatchEvents(collectionDelay, batchReportLatency, eventCount, "");
+    }
+
+    public void waitForFlush() throws InterruptedException {
+        mAssert.assertTrue(
+                String.format("flush| sensorType:%d", mSensorUnderTest.getType()),
+                mSensorManager.flush(mEventListener));
+        mEventListener.waitForFlushComplete();
+    }
+
+    /**
+     * Definition of support test classes.
+     */
+    public class SensorEventForTest {
+        public final Sensor sensor;
+        public final long timestamp;
+        public final int accuracy;
+        public final float values[];
+
+        public SensorEventForTest(SensorEvent event) {
+            values = new float[event.values.length];
+            System.arraycopy(event.values, 0, values, 0, event.values.length);
+
+            sensor = event.sensor;
+            timestamp = event.timestamp;
+            accuracy = event.accuracy;
+        }
+    }
+
+    private class TestSensorListener implements SensorEventListener2 {
+        private final ConcurrentLinkedDeque<SensorEventForTest> mSensorEventsList =
+                new ConcurrentLinkedDeque<SensorEventForTest>();
+        private volatile CountDownLatch mEventLatch;
+        private volatile CountDownLatch mFlushLatch;
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            CountDownLatch latch = mEventLatch;
+            if(latch != null) {
+                // copy the event because there is no better way to do this in the platform
+                mSensorEventsList.addLast(new SensorEventForTest(event));
+                latch.countDown();
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+
+        @Override
+        public void onFlushCompleted(Sensor sensor) {
+            CountDownLatch latch = mFlushLatch;
+            mFlushLatch = new CountDownLatch(1);
+
+            if(latch != null) {
+                latch.countDown();
+            }
+        }
+
+        public void waitForFlushComplete() throws InterruptedException {
+            CountDownLatch latch = mFlushLatch;
+            mAssert.assertTrue(
+                    "WaitForFlush",
+                    latch.await(WAIT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
+        }
+
+        public void waitForEvents(int eventCount) {
+            waitForEvents(eventCount, "");
+        }
+
+        public void waitForEvents(int eventCount, String timeoutInfo) {
+            mEventLatch = new CountDownLatch(eventCount);
+            try {
+                boolean awaitCompleted = mEventLatch.await(WAIT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+                // TODO: can we collect bug reports on error based only if needed? env var?
+
+                String assertMessage = String.format(
+                        "WaitForEvents| count:%d, available:%d, %s",
+                        eventCount,
+                        mSensorEventsList.size(),
+                        timeoutInfo);
+                mAssert.assertTrue(assertMessage, awaitCompleted);
+            } catch(InterruptedException e) {
+            } finally {
+                mEventLatch = null;
+            }
+        }
+
+        public SensorEventForTest getLastEvent() {
+            return mSensorEventsList.getLast();
+        }
+
+        public SensorEventForTest[] getAllEvents() {
+            return mSensorEventsList.toArray(new SensorEventForTest[0]);
+        }
+
+        public void clearEvents() {
+            mSensorEventsList.clear();
+        }
+    }
+
+    public class TestTriggerListener extends TriggerEventListener {
+        @Override
+        public void onTrigger(TriggerEvent event) {
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
new file mode 100644
index 0000000..4f8fb62
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaPlayer;
+
+import android.util.Log;
+
+/**
+ * Basic sanity test of data returned by MediaCodeCapabilities.
+ */
+public class MediaCodecCapabilitiesTest extends MediaPlayerTestBase {
+
+    private static final String TAG = "MediaCodecCapabilitiesTest";
+    private static final String AVC_MIME = "video/avc";
+    private static final int PLAY_TIME_MS = 30000;
+
+    public void testAvcBaseline1() throws Exception {
+        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
+                CodecProfileLevel.AVCLevel1)) {
+            throw new RuntimeException("AVCLevel1 support is required by CDD");
+        }
+        // We don't have a test stream, but at least we're testing
+        // that supports() returns true for something.
+    }
+
+    public void testAvcBaseline12() throws Exception {
+        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
+                CodecProfileLevel.AVCLevel12)) {
+            Log.i(TAG, "AvcBaseline12 not supported");
+            return;  // TODO: Can we make this mandatory?
+        }
+        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
+                + "&itag=160&source=youtube&user=android-device-test"
+                + "&sparams=ip,ipbits,expire,id,itag,source,user"
+                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
+                + "&signature=341692D20FACCAE25B90EA2C131EA6ADCD8E2384."
+                + "9EB08C174BE401AAD20FB85EE4DBA51A2882BB60"
+                + "&key=test_key1", 256, 144, PLAY_TIME_MS);
+    }
+
+    public void testAvcBaseline30() throws Exception {
+        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
+                CodecProfileLevel.AVCLevel3)) {
+            Log.i(TAG, "AvcBaseline30 not supported");
+            return;
+        }
+        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
+                + "&itag=18&source=youtube&user=android-device-test"
+                + "&sparams=ip,ipbits,expire,id,itag,source,user"
+                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
+                + "&signature=8701A45F6422229D46ABB25A22E2C00C94024606."
+                + "08BCDF16C3F744C49D4C8A8AD1C38B3DC1810918"
+                + "&key=test_key1", 640, 360, PLAY_TIME_MS);
+    }
+
+    public void testAvcHigh31() throws Exception {
+        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh,
+                CodecProfileLevel.AVCLevel31)) {
+            Log.i(TAG, "AvcHigh31 not supported");
+            return;
+        }
+        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
+                + "&itag=22&source=youtube&user=android-device-test"
+                + "&sparams=ip,ipbits,expire,id,itag,source,user"
+                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
+                + "&signature=42969CA8F7FFAE432B7135BC811F96F7C4172C3F."
+                + "1A8A92EA714C1B7C98A05DDF2DE90854CDD7638B"
+                + "&key=test_key1", 1280, 720, PLAY_TIME_MS);
+
+    }
+
+    public void testAvcHigh40() throws Exception {
+        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh,
+                CodecProfileLevel.AVCLevel4)) {
+            Log.i(TAG, "AvcHigh40 not supported");
+            return;
+        }
+        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
+                + "&itag=37&source=youtube&user=android-device-test"
+                + "&sparams=ip,ipbits,expire,id,itag,source,user"
+                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
+                + "&signature=7C3BBFB2F493E1BC396B6D31DDAF2E1367624487."
+                + "64197F3BB46039669E912297DCD68D1FB2811D9F"
+                + "&key=test_key1", 1920, 1080, PLAY_TIME_MS);
+    }
+
+    private boolean supports(String mimeType, int profile, int level) {
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+            if (codecInfo.isEncoder()) {
+                continue;
+            }
+
+            CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+            for (CodecProfileLevel profileLevel : capabilities.profileLevels) {
+                if (profileLevel.profile == profile
+                        && profileLevel.level >= level) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+}