Merge "Camera2: don't need check per frame control for digital zoom test" into lmp-dev
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index e70f44b..91803cb 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -436,7 +436,10 @@
 
     <!-- Strings to interact with users in Sensor Tests -->
     <string name="snsr_test_play_sound">A sound will be played once the verification is complete...</string>
+    <string name="snsr_no_interaction">Leave the device on top of a flat surface.</string>
+    <string name="snsr_interaction_needed">Once the test begins, you will have to wave your hand over the front of the device.</string>
     <string name="snsr_device_steady">Keep the device steady.</string>
+    <string name="snsr_keep_device_rotating_clockwise">Once the test begins, you will have to keep rotating the device clockwise.</string>
     <string name="snsr_wait_for_user">Press \'Next\' to continue.</string>
     <string name="snsr_on_complete_return">After completing the task, go back to this test.</string>
     <string name="snsr_movement_expected">Movement was expected during the test. Found=%1$b.</string>
@@ -447,6 +450,7 @@
     <string name="snsr_setting_airplane_mode">Airplane mode</string>
     <string name="snsr_setting_screen_brightness_mode">Adaptive Brightness</string>
     <string name="snsr_setting_auto_rotate_screen_mode">Auto-rotate screen</string>
+    <string name="snsr_setting_keep_screen_on">Stay awake</string>
     <string name="snsr_setting_location_mode">Location</string>
     <string name="snsr_setting_auto_screen_off_mode">Display Sleep</string>
 
@@ -480,6 +484,8 @@
     <string name="snsr_gyro_rotate_left_side">Rotate the device on its left side until it stands on its side.</string>
     <string name="snsr_gyro_rotate_top_side">Rotate the device on its top side until it stands perpendicular.</string>
     <string name="snsr_gyro_rotate_bottom_side">Rotate the device on its bottom side util it stands perpendicular.</string>
+    <string name="snsr_gyro_rotate_clockwise_integration">Once you begin the test, you will need
+            to rotate the device %1$f degrees counter-clockwise.</string>
 
     <!-- Heart Rate -->
     <string name="snsr_heartrate_test">Heart Rate Test</string>
@@ -498,15 +504,16 @@
     <!-- Sensor Value Accuracy -->
     <string name="snsr_val_acc_test">Sensor Value Accuracy Tests</string>
     <string name="snsr_rot_vec_test">Rotation Vector Accuracy Test</string>
+    <string name="snsr_collected_events_length">Sensor(%2$s). Collected events expected to be greater than zero. Found=%1$d.</string>
     <string name="snsr_event_length">Sensor(%3$s). Event values expected to have size=%1$d. Found=%2$d</string>
+    <string name="snsr_event_length_positive">Sensor(%2$s). Event values expected to have size > 0. Found=%1$d</string>
     <string name="snsr_event_value">Sensor(%3$s). Event value[0] expected to be of value=%1$f. Found=%2$f</string>
     <string name="snsr_event_time">Sensor(%3$s). Event timestamp expected to be at=%1$d. Found=%2$d. Delta=%3$d. Threshold=%4$d.</string>
+    <string name="snsr_event_time_positive">Sensor(%2$s). Event timestamp expected to positive (> 0). Found=%1$d.</string>
 
     <!-- Sensor Batching -->
     <string name="snsr_batch_test">Sensor Batching Tests</string>
     <string name="snsr_batching_walking_needed">Once the test begins, you will have to walk.</string>
-    <string name="snsr_batching_interrupt_needed">Once the test begins, you will have to wave your  hand over the front of the device.</string>
-    <string name="snsr_batching_no_interaction">Leave the device on top of a flat surface.</string>
     <string name="snsr_batching_fifo_count">FifoReservedEventCount=%1$d. Expected to be at most FifoMaxEventCount=%2$d.</string>
     <string name="snsr_batching_first_event_arrival">Batched events expected to arrive after %1$d ns. Found=%2$d ns.</string>
     <string name="snsr_batching_flush_complete">Event \'onFlushComplete\' expected. Found=%1$b.</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
index 9e60dc1..89a20d2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
@@ -96,44 +96,44 @@
     }
 
     public String testProximity() throws Throwable {
-        return runTest(Sensor.TYPE_PROXIMITY, R.string.snsr_batching_interrupt_needed);
+        return runTest(Sensor.TYPE_PROXIMITY, R.string.snsr_interaction_needed);
     }
 
     public String testLight() throws Throwable {
-        return runTest(Sensor.TYPE_LIGHT, R.string.snsr_batching_interrupt_needed);
+        return runTest(Sensor.TYPE_LIGHT, R.string.snsr_interaction_needed);
     }
 
     // TODO: move sensors that do not require interaction to CTS
     public String testGameRotationVector() throws Throwable {
-        return runTest(Sensor.TYPE_GAME_ROTATION_VECTOR, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_GAME_ROTATION_VECTOR, R.string.snsr_no_interaction);
     }
 
     public String testGeomagneticRotationVector() throws Throwable {
-        return runTest(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, R.string.snsr_no_interaction);
     }
 
     public String testAccelerometer() throws Throwable {
-        return runTest(Sensor.TYPE_ACCELEROMETER, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_ACCELEROMETER, R.string.snsr_no_interaction);
     }
 
     public String testGyroscope() throws Throwable {
-        return runTest(Sensor.TYPE_GYROSCOPE, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_GYROSCOPE, R.string.snsr_no_interaction);
     }
 
     public String testGyroscopeUncalibrated() throws Throwable {
-        return runTest(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, R.string.snsr_no_interaction);
     }
 
     public String testMagneticField() throws Throwable {
-        return runTest(Sensor.TYPE_MAGNETIC_FIELD, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_MAGNETIC_FIELD, R.string.snsr_no_interaction);
     }
 
     public String testMagneticFieldUncalibrated() throws Throwable {
-        return runTest(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, R.string.snsr_no_interaction);
     }
 
     public String testRotationVector() throws Throwable {
-        return runTest(Sensor.TYPE_ROTATION_VECTOR, R.string.snsr_batching_no_interaction);
+        return runTest(Sensor.TYPE_ROTATION_VECTOR, R.string.snsr_no_interaction);
     }
 
     // TODO: split batching and flush scenarios
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorValueAccuracyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorValueAccuracyActivity.java
index f659fed..601060f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorValueAccuracyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorValueAccuracyActivity.java
@@ -16,139 +16,296 @@
 
 package com.android.cts.verifier.sensors;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import com.android.cts.verifier.R;
 
 import junit.framework.Assert;
-import android.annotation.TargetApi;
+
 import android.content.Context;
-import android.graphics.Color;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorNotSupportedException;
 import android.hardware.cts.helpers.TestSensorEvent;
-import android.os.Build;
-import android.os.Bundle;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.ISensorVerification;
+import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
 import android.os.SystemClock;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
 /**
- * Activity that verifies sensor event quality. Sensors will be verified with
- * different requested data rates. Also sensor events will be verified while
- * other sensors are also active.
+ * Activity that verifies sensor event quality. Sensors will be verified with different requested
+ * data rates. Also sensor events will be verified while other sensors are also active.
  */
-@TargetApi(Build.VERSION_CODES.KITKAT)
-public class SensorValueAccuracyActivity extends
-        BaseSensorSemiAutomatedTestActivity implements SensorEventListener {
+public class SensorValueAccuracyActivity
+        extends BaseSensorTestActivity
+        implements SensorEventListener {
+    public SensorValueAccuracyActivity() {
+        super(SensorValueAccuracyActivity.class);
+    }
 
-    private final int DATA_COLLECTION_TIME_IN_MS = 500;
-    private final int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST;
-    private final float MAX_ERROR_ACCELEROMETER = 0.5f; // about 5% of g
-    private final float MAX_ERROR_GYROSCOPE = 0.02f; // about 1dps
-    private final float RANGE_ATMOSPHERIC_PRESSURE = 35f;
-    private final float AMBIENT_TEMPERATURE_MIN = 15f;
-    private final float AMBIENT_TEMPERATURE_MAX = 30f;
-    private final float PROXIMITY_MIN = 0f;
-    private final float PROXIMITY_MAX = 100f;
+    private static final int EVENTS_TO_COLLECT = 100;
+    private static final int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST;
 
-    private SensorManager mSensorManager = null;
-    private List<TestSensorEvent> mSensorEvents = new ArrayList<TestSensorEvent>();
-    private static Set<Integer> mCompletedTests = new HashSet<Integer>();
-    private boolean alreadyWarned = false;
+    private static final float MAGNETIC_FIELD_CALIBRATED_UNCALIBRATED_THRESHOLD_UT = 1f;
+    private static final float GYROSCOPE_CALIBRATED_UNCALIBRATED_THRESHOLD_RAD_SEC = 0.01f;
+
+    private static final float RANGE_ATMOSPHERIC_PRESSURE = 35f;
+    private static final float AMBIENT_TEMPERATURE_AVERAGE = 22.5f;
+    private static final float AMBIENT_TEMPERATURE_THRESHOLD = 7.5f;
+    private static final float PROXIMITY_AVERAGE = 50f;
+    private static final float PROXIMITY_THRESHOLD = 50f;
+    private static final double ONE_HUNDRED_EIGHTY_DEGREES = 180.0f;
+
+    private static final double GYROSCOPE_INTEGRATION_THRESHOLD_DEGREES = 10.0f;
+
+    private SensorManager mSensorManager;
+
+    private final List<TestSensorEvent> mSensorEvents = new ArrayList<TestSensorEvent>();
 
     @Override
-    protected void onRun() throws Throwable {
-        List<Sensor> supportedTests = new ArrayList<Sensor>();
-        supportedTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER));
-        supportedTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD));
-        supportedTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE));
-        supportedTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE));
-        supportedTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE));
-
-        appendText("Place device on a level surface and click 'Next' to start.");
-        waitForUser();
-
-        for (Sensor ssr : supportedTests) {
-            if (ssr == null) {
-                continue;
-            }
-            appendText(String.format("\nSensor %s", ssr.getName()));
-            testSensorAccuracy(ssr);
-            appendText("Passed");
-        }
-        appendText("Stationary tests passed\n", Color.GREEN);
-
-        supportedTests.clear();
-        supportedTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY));
-        supportedTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT));
-        appendText("Wave hand over the proximity sensor (usually near top front of device)"
-                + " and click 'Next' to start");
-        waitForUser();
-
-        for (Sensor ssr : supportedTests) {
-            if (ssr == null) {
-                continue;
-            }
-            appendText(String.format("\nSensor %s", ssr.getName()));
-            testSensorAccuracy(ssr);
-            appendText("Passed");
-        }
-        appendText("Proximity and light tests passed\n", Color.GREEN);
-
-        appendText("Place device on a level surface, click 'Next', then rotate device "
-                + "180 degrees counter-clockwise.");
-        appendText("Finally click 'Next' when done rotating.");
-        waitForUser();
-
-        testMagGyroAdditional();
-        appendText("All sensor value accuracy tests passed", Color.GREEN);
+    protected void activitySetUp() {
+        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
     }
 
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mSensorManager = (SensorManager) getApplicationContext()
-                .getSystemService(Context.SENSOR_SERVICE);
+    // TODO: move tests without interaction to CTS
+    public String testPressure() throws Throwable {
+        return verifySensorNorm(
+                Sensor.TYPE_PRESSURE,
+                R.string.snsr_no_interaction,
+                SensorManager.PRESSURE_STANDARD_ATMOSPHERE,
+                RANGE_ATMOSPHERIC_PRESSURE);
     }
 
-    private void testSensorAccuracy(Sensor ssr) throws Throwable {
-        int sensorType = ssr.getType();
-        String sensorName = ssr.getName();
-        if (!mCompletedTests.contains(sensorType)) {
-            startDataCollection(ssr);
-
-            Thread.sleep(DATA_COLLECTION_TIME_IN_MS);
-            stopDataCollection();
-
-            // No need to synchronize because events arrive as they are sampled
-            analyzeData(sensorType, sensorName);
-            mCompletedTests.add(sensorType);
-        }
+    public String testAmbientTemperature() throws Throwable {
+        return verifySensorNorm(
+                Sensor.TYPE_AMBIENT_TEMPERATURE,
+                R.string.snsr_no_interaction,
+                AMBIENT_TEMPERATURE_AVERAGE,
+                AMBIENT_TEMPERATURE_THRESHOLD);
     }
 
-    private void testMagGyroAdditional() throws Throwable {
-        List<Sensor> additionalTests = new ArrayList<Sensor>();
-        additionalTests.add(mSensorManager
-                .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED));
-        additionalTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD));
-        additionalTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED));
-        additionalTests.add(mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE));
-        for (Sensor ssr : additionalTests) {
-            if (ssr == null) {
-                continue;
-            }
-            startDataCollection(ssr);
+    // TODO: add support for proximity and light to test operations and add test cases here
+
+    // TODO: remove from here, refactor and merge with gyroscope and magnetic field tests
+    public String testMagneticFieldCalibratedUncalibrated() throws Throwable {
+        return verifyCalibratedUncalibrated(
+                Sensor.TYPE_MAGNETIC_FIELD,
+                Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+                MAGNETIC_FIELD_CALIBRATED_UNCALIBRATED_THRESHOLD_UT);
+    }
+
+    public String testGyroscopeCalibratedUncalibrated() throws Throwable {
+        appendText(R.string.snsr_keep_device_rotating_clockwise);
+        return verifyCalibratedUncalibrated(
+                Sensor.TYPE_GYROSCOPE,
+                Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
+                GYROSCOPE_CALIBRATED_UNCALIBRATED_THRESHOLD_RAD_SEC);
+    }
+
+    /**
+     * Verifies that the measurements of the gyroscope correspond to predefined angular positions.
+     * The test uses a routine to integrate gyroscope's readings on a predefined rotation to
+     * ensure that it corresponds to the expected angular position.
+     */
+    // TODO: refactor the integration routine into a SensorTestVerification
+    // TODO: use the new verification in GyroscopeMeasurement tests
+    public String testGyroscopeIntegration() throws Throwable {
+        Sensor gyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
+        if (gyroscope == null) {
+            throw new SensorNotSupportedException(Sensor.TYPE_GYROSCOPE);
         }
+
+        appendText(R.string.snsr_no_interaction);
+        String rotationInstructions = getString(
+                R.string.snsr_gyro_rotate_clockwise_integration,
+                ONE_HUNDRED_EIGHTY_DEGREES);
+        appendText(rotationInstructions);
         waitForUser();
+
+        startDataCollection(gyroscope);
+        appendText(R.string.snsr_test_play_sound);
+        Thread.sleep(TimeUnit.SECONDS.toMillis(10));
         stopDataCollection();
-        analyzeMagGyro();
+        playSound();
+
+        // run the verification
+        double integratedGyroscope = 0;
+        long lastTimestamp = 0;
+        for (TestSensorEvent event : mSensorEvents) {
+            float[] eventValues = event.values.clone();
+            long eventTimestamp = event.timestamp;
+            if (lastTimestamp != 0) {
+                long timeDeltaNs = eventTimestamp - lastTimestamp;
+                long nanosecondsInOneSecond = TimeUnit.SECONDS.toNanos(1);
+                integratedGyroscope += eventValues[2] * timeDeltaNs / nanosecondsInOneSecond;
+            }
+            lastTimestamp = eventTimestamp;
+        }
+        integratedGyroscope = Math.toDegrees(integratedGyroscope);
+
+        String integrationMessage = String.format(
+                "Gyroscope integration expected to be=%fdeg. Found=%fdeg, Tolerance=%fdeg",
+                ONE_HUNDRED_EIGHTY_DEGREES,
+                integratedGyroscope,
+                GYROSCOPE_INTEGRATION_THRESHOLD_DEGREES);
+        Assert.assertEquals(
+                integrationMessage,
+                ONE_HUNDRED_EIGHTY_DEGREES,
+                integratedGyroscope,
+                GYROSCOPE_INTEGRATION_THRESHOLD_DEGREES);
+        return integrationMessage;
+    }
+
+    /**
+     * Validates the norm of a sensor.
+     */
+    // TODO: fix up EventOrdering, EventGap and timestamp>0 Verifications so they can be added to
+    // this test
+    private String verifySensorNorm(
+            int sensorType,
+            int instructionsResId,
+            float expectedNorm,
+            float threshold) {
+        appendText(instructionsResId);
+        waitForUser();
+
+        TestSensorOperation verifyNormOperation = new TestSensorOperation(
+                getApplicationContext(),
+                sensorType,
+                SENSOR_RATE,
+                0 /* reportLatencyInUs */,
+                EVENTS_TO_COLLECT);
+
+        // TODO: add event ordering and timestamp > 0 verifications
+        ISensorVerification magnitudeVerification =
+                new MagnitudeVerification(expectedNorm, threshold);
+        verifyNormOperation.addVerification(magnitudeVerification);
+
+        verifyNormOperation.execute();
+        return null;
+    }
+
+    /**
+     * Verifies that the relationship between readings from calibrated and their corresponding
+     * uncalibrated sensors comply to the following equation:
+     *      calibrated = uncalibrated - bias
+     *
+     * NOTES:
+     * Currently given that timestamps might not be synchronized, the verification attempts to
+     * 'match' events from both sensors by aligning them to (time delta mean) / 2.
+     *
+     * @param calibratedSensorType The type of the calibrated sensor to verify.
+     * @param uncalibratedSensorType The type of the uncalibrated sensor to verify.
+     * @param threshold The threshold to consider the difference between the equation as
+     *                            acceptable.
+     */
+    // TODO: find a better synchronization mechanism
+    // TODO: revisit the need of a tolerance once a better synchronization mechanism is available
+    // TODO: refactor this function into a Sensor Test Operation / Verification
+    private String verifyCalibratedUncalibrated(
+            int calibratedSensorType,
+            int uncalibratedSensorType,
+            float threshold) throws Throwable {
+        appendText(R.string.snsr_no_interaction);
+        waitForUser();
+
+        Sensor calibratedSensor = mSensorManager.getDefaultSensor(calibratedSensorType);
+        if (calibratedSensor == null) {
+            throw new SensorNotSupportedException(calibratedSensorType);
+        }
+        Sensor uncalibratedSensor = mSensorManager.getDefaultSensor(uncalibratedSensorType);
+        if (uncalibratedSensor == null) {
+            throw new SensorNotSupportedException(uncalibratedSensorType);
+        }
+
+        // collect the required events
+        final long timeout = TimeUnit.SECONDS.toMillis(10);
+        startDataCollection(calibratedSensor);
+        startDataCollection(uncalibratedSensor);
+        appendText(R.string.snsr_test_play_sound);
+        Thread.sleep(timeout);
+        stopDataCollection();
+
+        // create a set of readings for verification
+        float[] calibratedValues = new float[3];
+        long calibratedTimestamp = 0;
+        long timestampDeltaSum = 0;
+        int calibratedEventCount = 0;
+        int uncalibratedEventCount = 0;
+        ArrayList<CalibratedUncalibratedReading> readings =
+                new ArrayList<CalibratedUncalibratedReading>();
+        for (TestSensorEvent event : mSensorEvents) {
+            if (event.sensor.getType() == calibratedSensorType) {
+                calibratedValues = event.values.clone();
+                calibratedTimestamp = event.receivedTimestamp;
+                ++calibratedEventCount;
+            } else if (event.sensor.getType() == uncalibratedSensorType) {
+                float[] uncalibratedValues = event.values.clone();
+                long timestampDelta = event.receivedTimestamp - calibratedTimestamp;
+                timestampDeltaSum += timestampDelta;
+                ++uncalibratedEventCount;
+
+                CalibratedUncalibratedReading reading = new CalibratedUncalibratedReading(
+                        calibratedValues,
+                        uncalibratedValues,
+                        timestampDelta);
+                readings.add(reading);
+            }
+            // TODO: use delayed asserts to log on else clause
+        }
+
+        // verify readings that are under a timestamp synchronization threshold
+        String calibratedEventsMessage = String.format(
+                "Calibrated (%s) events expected. Found=%d.",
+                calibratedSensor.getName(),
+                calibratedEventCount);
+        Assert.assertTrue(calibratedEventsMessage, calibratedEventCount > 0);
+
+        String uncalibratedEventsMessage = String.format(
+                "Uncalibrated (%s) events expected. Found=%d.",
+                uncalibratedSensor.getName(),
+                uncalibratedEventCount);
+        Assert.assertTrue(uncalibratedEventsMessage, uncalibratedEventCount > 0);
+
+        long timestampDeltaMean = timestampDeltaSum / readings.size();
+        long timestampTolerance = timestampDeltaMean / 2;
+        int verifiedEventsCount = 0;
+        for (CalibratedUncalibratedReading reading : readings) {
+            if (reading.timestampDelta < timestampTolerance) {
+                for (int i = 0; i < 3; ++i) {
+                    float calibrated = reading.calibratedValues[i];
+                    float uncalibrated = reading.uncalibratedValues[i];
+                    float bias = reading.uncalibratedValues[i + 3];
+                    String message = String.format(
+                            "Calibrated (%s) and Uncalibrated (%s) sensor readings are expected to"
+                                    + " satisfy: calibrated = uncalibrated - bias. Axis=%d,"
+                                    + " Calibrated=%s, Uncalibrated=%s, Bias=%s, Threshold=%s",
+                            calibratedSensor.getName(),
+                            uncalibratedSensor.getName(),
+                            i,
+                            calibrated,
+                            uncalibrated,
+                            bias,
+                            threshold);
+                    Assert.assertEquals(message, calibrated, uncalibrated - bias, threshold);
+                }
+                ++verifiedEventsCount;
+            }
+        }
+
+        playSound();
+        String eventsFoundMessage = String.format(
+                "At least one uncalibrated event expected to be verified. Found=%d.",
+                verifiedEventsCount);
+        Assert.assertTrue(eventsFoundMessage, verifiedEventsCount > 0);
+        return eventsFoundMessage;
     }
 
     private void startDataCollection(Sensor sensorUnderTest) throws Throwable {
-        alreadyWarned = false;
-        appendText(String.format(" Gathering data on %s", sensorUnderTest.getName()));
         mSensorEvents.clear();
         mSensorManager.registerListener(this, sensorUnderTest, SENSOR_RATE);
     }
@@ -157,208 +314,6 @@
         mSensorManager.unregisterListener(this);
     }
 
-    private void assertTrueWarning(String msg, boolean condition) {
-        if (!condition && !alreadyWarned) {
-            appendText(msg, Color.YELLOW);
-            alreadyWarned = true;
-        }
-    }
-
-    private void assertEqualsWarning(String msg, double expected, double actual, double delta) {
-        if (Math.abs(expected - actual) > delta) {
-            appendText(msg + String.format("\nexpected:<%f> but was:<%f>", expected, actual),
-                    Color.YELLOW);
-        }
-    }
-
-    private void analyzeData(int sensorType, String sensorName) {
-        int numberOfCollectedEvents = mSensorEvents.size();
-        appendText(String.format(" Collected %d events", numberOfCollectedEvents));
-
-        if (numberOfCollectedEvents < 1) {
-            appendText("No sensor events collected for " + sensorName, Color.YELLOW);
-        }
-
-        // Tests valid for all sensors:
-        for (int i = 0; i < numberOfCollectedEvents; i++) {
-            TestSensorEvent event = mSensorEvents.get(i);
-            Assert.assertTrue("Data should be present in the sensor event", event.values.length > 0);
-            Assert.assertTrue("Timestamp should be greater then zero", event.timestamp > 0);
-        }
-
-        // Specific tests by sensor type:
-        if (sensorType == Sensor.TYPE_ACCELEROMETER) {
-            double norm = 0;
-            for (int i = 0; i < numberOfCollectedEvents; i++) {
-                TestSensorEvent event = mSensorEvents.get(i);
-
-                norm += Math.sqrt(event.values[0] * event.values[0] +
-                        event.values[1] * event.values[1] +
-                        event.values[2] * event.values[2]);
-            }
-            norm /= numberOfCollectedEvents;
-
-            // Suggested tolerance gets a warning (for now) and required
-            // tolerance is an assert
-            assertEqualsWarning(String.format("Not within suggested tolerance of %f m/s/s",
-                    MAX_ERROR_ACCELEROMETER / 10.), SensorManager.GRAVITY_EARTH, norm,
-                    MAX_ERROR_ACCELEROMETER / 10.f);
-            Assert.assertEquals(String.format("Not within required tolerance of %6.4f m/s/s",
-                    MAX_ERROR_ACCELEROMETER), SensorManager.GRAVITY_EARTH, norm,
-                    MAX_ERROR_ACCELEROMETER);
-        } else if (sensorType == Sensor.TYPE_MAGNETIC_FIELD) {
-            double norm = 0;
-            for (int i = 0; i < numberOfCollectedEvents; i++) {
-                TestSensorEvent event = mSensorEvents.get(i);
-
-                norm += Math.sqrt(event.values[0] * event.values[0] +
-                        event.values[1] * event.values[1] +
-                        event.values[2] * event.values[2]);
-            }
-            norm /= numberOfCollectedEvents;
-            assertTrueWarning(String.format("Magnetic field strength should be greater than %f"
-                    + " but value %f was detected", SensorManager.MAGNETIC_FIELD_EARTH_MIN,
-                    norm), norm > SensorManager.MAGNETIC_FIELD_EARTH_MIN);
-            assertTrueWarning(String.format("Magnetic field strength should be less than %f"
-                    + " but value %f was detected", SensorManager.MAGNETIC_FIELD_EARTH_MAX,
-                    norm), norm < SensorManager.MAGNETIC_FIELD_EARTH_MAX);
-
-        } else if (sensorType == Sensor.TYPE_GYROSCOPE) {
-            double norm = 0;
-            for (int i = 0; i < numberOfCollectedEvents; i++) {
-                TestSensorEvent event = mSensorEvents.get(i);
-
-                norm += Math.sqrt(event.values[0] * event.values[0] +
-                        event.values[1] * event.values[1] +
-                        event.values[2] * event.values[2]);
-            }
-            norm /= numberOfCollectedEvents;
-
-            // Suggested tolerance gets a warning (for now) and required
-            // tolerance is an assert
-            assertEqualsWarning(
-                    String.format("Not within tolerance of %f rps", MAX_ERROR_GYROSCOPE / 10.),
-                    0.0f, norm, MAX_ERROR_GYROSCOPE / 10.);
-            Assert.assertEquals(
-                    String.format("Not within tolerance of %f rps", MAX_ERROR_GYROSCOPE),
-                    0.0f, norm, MAX_ERROR_GYROSCOPE);
-        } else if (sensorType == Sensor.TYPE_PRESSURE) {
-            for (int i = 0; i < numberOfCollectedEvents; i++) {
-                TestSensorEvent event = mSensorEvents.get(i);
-
-                float norm = event.values[0];
-                float minAtmosphericPressure = SensorManager.PRESSURE_STANDARD_ATMOSPHERE
-                        - RANGE_ATMOSPHERIC_PRESSURE;
-                float maxAtmosphericPressure = SensorManager.PRESSURE_STANDARD_ATMOSPHERE
-                        + RANGE_ATMOSPHERIC_PRESSURE;
-                assertTrueWarning(String.format("Barometer should be greater than %f"
-                        + " but value %f was detected", minAtmosphericPressure, norm),
-                        norm > minAtmosphericPressure);
-                assertTrueWarning(String.format("Barometer should be less than %f"
-                        + " but value %f was detected", maxAtmosphericPressure, norm),
-                        norm < maxAtmosphericPressure);
-            }
-        } else if (sensorType == Sensor.TYPE_AMBIENT_TEMPERATURE) {
-            for (int i = 0; i < numberOfCollectedEvents; i++) {
-                TestSensorEvent event = mSensorEvents.get(i);
-
-                float norm = event.values[0];
-                assertTrueWarning(String.format("Ambient Temperature should be greater than %f"
-                        + " but value %f was detected", AMBIENT_TEMPERATURE_MIN, norm),
-                        norm > AMBIENT_TEMPERATURE_MIN);
-                assertTrueWarning(String.format("Ambient Temperature should be less than %f"
-                        + " but value %f was detected", AMBIENT_TEMPERATURE_MAX, norm),
-                        norm < AMBIENT_TEMPERATURE_MAX);
-            }
-        } else if (sensorType == Sensor.TYPE_PROXIMITY) {
-            for (int i = 0; i < numberOfCollectedEvents; i++) {
-                TestSensorEvent event = mSensorEvents.get(i);
-
-                float norm = event.values[0];
-                assertTrueWarning(String.format("Proximity should be greater than %f"
-                        + " but value %f was detected", PROXIMITY_MIN, norm),
-                        norm > PROXIMITY_MIN);
-                assertTrueWarning(String.format("Proximity should be less than %f"
-                        + " but value %f was detected", PROXIMITY_MAX, norm),
-                        norm < PROXIMITY_MAX);
-            }
-        } else if (sensorType == Sensor.TYPE_LIGHT) {
-            for (int i = 0; i < numberOfCollectedEvents; i++) {
-                TestSensorEvent event = mSensorEvents.get(i);
-
-                float norm = event.values[0];
-                assertTrueWarning(String.format("Light readings should be greater than %f"
-                        + " but value %f was detected", 0.0f, norm), norm > 0.0f);
-                assertTrueWarning(String.format("Light readings should be less than %f"
-                        + " but value %f was detected", SensorManager.LIGHT_SUNLIGHT_MAX,
-                        norm), norm < SensorManager.LIGHT_SUNLIGHT_MAX);
-            }
-        }
-    }
-
-    private float[] gyrval = new float[3];
-    private float[] magval = new float[3];
-    private double gyrTime = 0, magTime = 0;
-    private final double ONE_HUNDRED_EIGHTY_DEGREES = 180.0f;
-    private final double INTEGRATION_TOLERANCE_DEGREES = 10.0f;
-    // TODO: remove these two tolerances once event.timestamp is consistent in
-    // implementations and can compare sample to sample exactly
-    private final long TIMESTAMP_TOLERANCE = 30000L;
-    private final double MAG_VALUE_TOLERANCE = 3.0f;
-
-    private void analyzeMagGyro() {
-        int numberOfCollectedEvents = mSensorEvents.size();
-        int numberOfUncalGyroEvents = 0, numberOfUncalMagEvents = 0;
-        double integratedGyro = 0;
-        for (int i = 0; i < numberOfCollectedEvents; i++) {
-            TestSensorEvent event = mSensorEvents.get(i);
-            if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
-                gyrval = event.values.clone();
-                if (gyrTime != 0) {
-                    integratedGyro += gyrval[2] * (event.receivedTimestamp - gyrTime) / 1e9;
-                }
-                gyrTime = event.receivedTimestamp;
-            }
-            if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE_UNCALIBRATED) {
-                float[] uncalGyrval = event.values.clone();
-                if ((event.receivedTimestamp - gyrTime) < TIMESTAMP_TOLERANCE) {
-                    for (int j = 0; j < 3; j++) {
-                        Assert.assertEquals("Uncalibrated and calibrated gyroscope do not match",
-                                gyrval[j], uncalGyrval[j] - uncalGyrval[j + 3]);
-                    }
-                    numberOfUncalGyroEvents++;
-                }
-            }
-            if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
-                magval = event.values.clone();
-                magTime = event.receivedTimestamp;
-            }
-            if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED) {
-                float[] uncalMagval = event.values.clone();
-                if ((event.receivedTimestamp - magTime) < TIMESTAMP_TOLERANCE) {
-                    for (int j = 0; j < 3; j++) {
-                        Assert.assertEquals(
-                                "Uncalibrated and calibrated magnetic field do not match",
-                                magval[j], uncalMagval[j] - uncalMagval[j + 3], MAG_VALUE_TOLERANCE);
-                    }
-                    numberOfUncalMagEvents++;
-                }
-            }
-        }
-        if (numberOfUncalMagEvents > 0) {
-            appendText("Calibrated and Uncalibrated MAGNETIC_FIELD agree\n", Color.GREEN);
-        }
-        if (numberOfUncalGyroEvents > 0) {
-            appendText("Calibrated and Uncalibrated GYROSCOPE agree\n", Color.GREEN);
-        }
-        if (integratedGyro > 0) {
-            integratedGyro = Math.toDegrees(integratedGyro);
-            Assert.assertEquals("Gyroscope integration not as expected.  Check gyroscope scale, ",
-                    ONE_HUNDRED_EIGHTY_DEGREES, integratedGyro, INTEGRATION_TOLERANCE_DEGREES);
-            appendText("Gyroscope scale is within tolerance\n", Color.GREEN);
-        }
-    }
-
     @Override
     public void onSensorChanged(SensorEvent sensorEvent) {
         mSensorEvents.add(new TestSensorEvent(sensorEvent, SystemClock.elapsedRealtimeNanos()));
@@ -368,4 +323,18 @@
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }
 
+    private class CalibratedUncalibratedReading {
+        public final float[] calibratedValues;
+        public final float[] uncalibratedValues;
+        public final long timestampDelta;
+
+        public CalibratedUncalibratedReading(
+                float[] calibratedValues,
+                float[] uncalibratedValues,
+                long timestampDelta) {
+            this.calibratedValues = calibratedValues;
+            this.uncalibratedValues = uncalibratedValues;
+            this.timestampDelta = timestampDelta;
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java
index 9e6e04f..48b069d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java
@@ -42,8 +42,12 @@
     private final SensorSettingContainer mAirplaneMode;
     private final SensorSettingContainer mScreenBrightnessMode;
     private final SensorSettingContainer mAutoRotateScreenMode;
+    private final SensorSettingContainer mKeepScreenOnMode;
     private final SensorSettingContainer mLocationMode;
 
+    /**
+     * The handler is a facade for the Activity making use of the {@link SensorFeaturesDeactivator}.
+     */
     public interface ActivityHandler {
         ContentResolver getContentResolver();
         void logInstructions(int instructionsResId, Object ... params);
@@ -57,6 +61,7 @@
         mAirplaneMode = new AirplaneModeSettingContainer();
         mScreenBrightnessMode = new ScreenBrightnessModeSettingContainer();
         mAutoRotateScreenMode = new AutoRotateScreenModeSettingContainer();
+        mKeepScreenOnMode = new KeepScreenOnModeSettingContainer();
         mLocationMode = new LocationModeSettingContainer();
     }
 
@@ -66,6 +71,7 @@
         mAirplaneMode.requestToSetMode(true);
         mScreenBrightnessMode.requestToSetMode(false);
         mAutoRotateScreenMode.requestToSetMode(false);
+        mKeepScreenOnMode.requestToSetMode(false);
         mLocationMode.requestToSetMode(false);
 
         // TODO: try to use adb shell dumpsys sensorservice to find out if there are clients still
@@ -81,6 +87,7 @@
         mAirplaneMode.requestToResetMode();
         mScreenBrightnessMode.requestToResetMode();
         mAutoRotateScreenMode.requestToResetMode();
+        mKeepScreenOnMode.requestToResetMode();
         mLocationMode.requestToResetMode();
     }
 
@@ -134,6 +141,7 @@
         mScreenBrightnessMode.captureInitialState();
         mAutoRotateScreenMode.captureInitialState();
         mLocationMode.captureInitialState();
+        mKeepScreenOnMode.captureInitialState();
         mScreenOffTimeoutInMs = getScreenOffTimeoutInMs();
 
         mInitialStateCaptured = true;
@@ -205,6 +213,22 @@
         }
     }
 
+    private class KeepScreenOnModeSettingContainer extends SensorSettingContainer {
+        public KeepScreenOnModeSettingContainer() {
+            super(mActivityHandler,
+                    Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS,
+                    R.string.snsr_setting_keep_screen_on);
+        }
+
+        @Override
+        protected int getSettingMode() {
+            return Settings.Global.getInt(
+                    mActivityHandler.getContentResolver(),
+                    Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+                    0);
+        }
+    }
+
     private class LocationModeSettingContainer extends SensorSettingContainer {
         public LocationModeSettingContainer() {
             super(mActivityHandler,