Refactor sensor operations.
Change-Id: I633a5d3f1857e5a0dc766f0bef7385d8a2fc45dd
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
index ee26831..44bed6c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
@@ -18,7 +18,7 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
-import android.hardware.cts.helpers.sensorTestOperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
import java.util.concurrent.TimeUnit;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
index d148fb9..cdb5b3f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
@@ -18,7 +18,7 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
-import android.hardware.cts.helpers.sensorTestOperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
/**
* Semi-automated test that focuses on characteristics associated with Accelerometer measurements.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
index 7a089bc..115f20a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
@@ -21,7 +21,7 @@
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.hardware.cts.helpers.SensorManagerTestVerifier;
-import android.hardware.cts.helpers.sensorTestOperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
/**
* Semi-automated test that focuses characteristics associated with Accelerometer measurements.
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
index 64acce9..bcf9755 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
@@ -19,12 +19,13 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
import android.hardware.cts.helpers.SensorTestCase;
import android.hardware.cts.helpers.SensorTestInformation;
-import android.hardware.cts.helpers.sensorTestOperations.ParallelCompositeSensorTestOperation;
-import android.hardware.cts.helpers.sensorTestOperations.RepeatingSensorTestOperation;
-import android.hardware.cts.helpers.sensorTestOperations.SequentialCompositeSensorTestOperation;
-import android.hardware.cts.helpers.sensorTestOperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.ParallelSensorOperation;
+import android.hardware.cts.helpers.sensoroperations.RepeatingSensorOperation;
+import android.hardware.cts.helpers.sensoroperations.SequentialSensorOperation;
+import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -39,6 +40,8 @@
* -w com.android.cts.hardware/android.test.InstrumentationCtsTestRunner
*/
public class SensorIntegrationTests extends SensorTestCase {
+ private static final String TAG = "SensorIntegrationTests";
+
/**
* Builder for the test suite.
* This is the method that will build dynamically the set of test cases to execute.
@@ -91,7 +94,7 @@
Sensor.TYPE_MAGNETIC_FIELD,
Sensor.TYPE_GYROSCOPE };
- ParallelCompositeSensorTestOperation operation = new ParallelCompositeSensorTestOperation();
+ ParallelSensorOperation operation = new ParallelSensorOperation();
for(int sensorType : sensorTypes) {
VerifySensorOperation continuousOperation = new VerifySensorOperation(
context,
@@ -100,7 +103,7 @@
0 /* reportLatencyInUs */,
100 /* event count */);
continuousOperation.verifyEventOrdering();
- operation.add(new RepeatingSensorTestOperation(continuousOperation, ITERATIONS));
+ operation.add(new RepeatingSensorOperation(continuousOperation, ITERATIONS));
VerifySensorOperation batchingOperation = new VerifySensorOperation(
context,
@@ -109,9 +112,10 @@
SensorCtsHelper.getSecondsAsMicroSeconds(BATCHING_RATE_IN_SECONDS),
100);
batchingOperation.verifyEventOrdering();
- operation.add(new RepeatingSensorTestOperation(batchingOperation, ITERATIONS));
+ operation.add(new RepeatingSensorOperation(batchingOperation, ITERATIONS));
}
operation.execute();
+ SensorStats.logStats(TAG, operation.getStats());
}
/**
@@ -140,7 +144,7 @@
final int INSTANCES_TO_USE = 5;
final int ITERATIONS_TO_EXECUTE = 100;
- ParallelCompositeSensorTestOperation operation = new ParallelCompositeSensorTestOperation();
+ ParallelSensorOperation operation = new ParallelSensorOperation();
int sensorTypes[] = {
Sensor.TYPE_ACCELEROMETER,
Sensor.TYPE_MAGNETIC_FIELD,
@@ -148,8 +152,7 @@
for(int sensorType : sensorTypes) {
for(int instance = 0; instance < INSTANCES_TO_USE; ++instance) {
- SequentialCompositeSensorTestOperation sequentialOperation =
- new SequentialCompositeSensorTestOperation();
+ SequentialSensorOperation sequentialOperation = new SequentialSensorOperation();
for(int iteration = 0; iteration < ITERATIONS_TO_EXECUTE; ++iteration) {
VerifySensorOperation sensorOperation = new VerifySensorOperation(
this.getContext(),
@@ -165,6 +168,7 @@
}
operation.execute();
+ SensorStats.logStats(TAG, operation.getStats());
}
/**
@@ -221,7 +225,6 @@
0 /*reportLatencyInUs*/,
100 /* event count */);
tester.verifyEventOrdering();
- tester.start();
VerifySensorOperation testee = new VerifySensorOperation(
context,
@@ -230,12 +233,15 @@
0 /*reportLatencyInUs*/,
100 /* event count */);
testee.verifyEventOrdering();
- testee.start();
- testee.waitForCompletion();
- tester.waitForCompletion();
+ ParallelSensorOperation operation = new ParallelSensorOperation();
+ operation.add(tester, testee);
+ operation.execute();
+ SensorStats.logStats(TAG, operation.getStats());
+ testee = testee.clone();
testee.execute();
+ SensorStats.logStats(TAG, testee.getStats());
}
/**
diff --git a/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java b/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
index 6898dd1..27e7b02 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
@@ -19,9 +19,10 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
import android.hardware.cts.helpers.SensorTestCase;
import android.hardware.cts.helpers.SensorTestInformation;
-import android.hardware.cts.helpers.sensorTestOperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
import java.util.HashMap;
import java.util.Map;
@@ -83,6 +84,7 @@
* </p>
*/
public class SingleSensorTests extends SensorTestCase {
+ private static final String TAG = "SingleSensorTests";
/**
* This test verifies that the sensor's properties complies with the required properites set in
@@ -206,5 +208,6 @@
SensorManager.SENSOR_DELAY_FASTEST, 0, 100);
op.setDefaultVerifications();
op.execute();
+ SensorStats.logStats(TAG, op.getStats());
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
index cd5a9c6..fd221a9 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
+import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
import android.os.Environment;
import android.util.Log;
@@ -359,7 +360,7 @@
*/
public static String formatAssertionMessage(
String verificationName,
- SensorTestOperation test,
+ ISensorOperation test,
Sensor sensor,
String format,
Object ... params) {
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorManagerTestVerifier.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorManagerTestVerifier.java
index f5ec118..e9e89d0 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorManagerTestVerifier.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorManagerTestVerifier.java
@@ -158,6 +158,11 @@
return this.collectEvents(eventCount, "");
}
+ public TestSensorEvent[] collectEvents(long duration, TimeUnit timeUnit) {
+ // TODO: Allow this class to support duration as well as event count
+ throw new UnsupportedOperationException();
+ }
+
public void startFlush() {
String message = SensorCtsHelper.formatAssertionMessage(
"Flush",
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
new file mode 100644
index 0000000..48f8136
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 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.cts.helpers.sensoroperations.ISensorOperation;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Class used to store stats related to {@link ISensorOperation}s. Sensor stats may be linked
+ * together so that they form a tree.
+ */
+public class SensorStats {
+ public static final String DELIMITER = "__";
+
+ private final Map<String, Object> mValues = new HashMap<String, Object>();
+ private final Map<String, SensorStats> mSensorStats = new HashMap<String, SensorStats>();
+
+ /**
+ * Add a value.
+ *
+ * @param key the key.
+ * @param value the value as an {@link Object}.
+ */
+ public synchronized void addValue(String key, Object value) {
+ if (value == null) {
+ return;
+ }
+ mValues.put(key, value);
+ }
+
+ /**
+ * Add a nested {@link SensorStats}. This is useful for keeping track of stats in a
+ * {@link ISensorOperation} tree.
+ *
+ * @param key the key
+ * @param stats the sub {@link SensorStats} object.
+ */
+ public synchronized void addSensorStats(String key, SensorStats stats) {
+ if (stats == null) {
+ return;
+ }
+ mSensorStats.put(key, stats);
+ }
+
+ /**
+ * Flattens the map and all sub {@link SensorStats} objects. Keys will be flattened using
+ * {@value #DELIMITER}. For example, if a sub {@link SensorStats} is added with key
+ * {@code "key1"} containing the key value pair {@code ("key2", "value")}, the flattened map
+ * will contain the entry {@code ("key1__key2", "value")}.
+ *
+ * @return a {@link Map} containing all stats from the value and sub {@link SensorStats}.
+ */
+ public synchronized Map<String, Object> flatten() {
+ final Map<String, Object> flattenedMap = new HashMap<String, Object>(mValues);
+ for (Entry<String, SensorStats> statsEntry : mSensorStats.entrySet()) {
+ for (Entry<String, Object> valueEntry : statsEntry.getValue().flatten().entrySet()) {
+ String key = statsEntry.getKey() + DELIMITER + valueEntry.getKey();
+ flattenedMap.put(key, valueEntry.getValue());
+ }
+ }
+ return flattenedMap;
+ }
+
+ /**
+ * Utility method to log the stats to the logcat.
+ */
+ public static void logStats(String tag, SensorStats stats) {
+ final Map<String, Object> flattened = stats.flatten();
+ final List<String> keys = new ArrayList<String>(flattened.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ Object value = flattened.get(key);
+ if (value instanceof Double || value instanceof Float) {
+ Log.v(tag, String.format("%s: %.4f", key, value));
+ } else {
+ Log.v(tag, String.format("%s: %s", key, value.toString()));
+ }
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStatsTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStatsTest.java
new file mode 100644
index 0000000..8ba0f41
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStatsTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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 junit.framework.TestCase;
+
+import java.util.Map;
+
+/**
+ * Unit tests for the {@link SensorStats} class.
+ */
+public class SensorStatsTest extends TestCase {
+
+ /**
+ * Test that {@link SensorStats#flatten()} works correctly.
+ */
+ public void testFlatten() {
+ SensorStats stats = new SensorStats();
+ stats.addValue("value0", 0);
+ stats.addValue("value1", 1);
+
+ SensorStats subStats = new SensorStats();
+ subStats.addValue("value2", 2);
+ subStats.addValue("value3", 3);
+
+ SensorStats subSubStats = new SensorStats();
+ subSubStats.addValue("value4", 4);
+ subSubStats.addValue("value5", 5);
+
+ subStats.addSensorStats("stats1", subSubStats);
+ stats.addSensorStats("stats0", subStats);
+
+ // Add empty stats, expect no value in flattened map
+ stats.addSensorStats("stats2", new SensorStats());
+
+ // Add null values, expect no value in flattened map
+ stats.addSensorStats("stats3", null);
+ stats.addValue("value6", null);
+
+ Map<String, Object> flattened = stats.flatten();
+
+ assertEquals(6, flattened.size());
+ assertEquals(0, (int) (Integer) flattened.get("value0"));
+ assertEquals(1, (int) (Integer) flattened.get("value1"));
+ assertEquals(2, (int) (Integer) flattened.get("stats0__value2"));
+ assertEquals(3, (int) (Integer) flattened.get("stats0__value3"));
+ assertEquals(4, (int) (Integer) flattened.get("stats0__stats1__value4"));
+ assertEquals(5, (int) (Integer) flattened.get("stats0__stats1__value5"));
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorTestOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorTestOperation.java
deleted file mode 100644
index 902c8024..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorTestOperation.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.hardware.cts.helpers;
-
-import junit.framework.Assert;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Base test class that supports a basic test operation performed in a sensor.
- * The class follows a command patter as a base for its work.
- *
- * Remarks:
- * - The class wraps verifications and test checks that are needed to verify the operation.
- * - The operation runs in a background thread where it performs the bulk of its work.
- */
-public abstract class SensorTestOperation {
- private final SensorTestExceptionHandler mExceptionHandler = new SensorTestExceptionHandler();
-
- protected final String LOG_TAG = "TestRunner";
- protected final long WAIT_TIMEOUT_IN_MILLISECONDS =
- TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES);
-
- private Thread mThread;
-
- protected int mIterationCount;
-
- /**
- * Public API definition.
- */
- public synchronized void start() throws Throwable {
- if(mThread != null) {
- throw new IllegalStateException("The operation has already been started.");
- }
-
- mThread = new Thread() {
- @Override
- public void run() {
- try {
- doWork();
- } catch (Throwable e) {
- // log the exception so it can be sent back to the appropriate test thread
- this.getUncaughtExceptionHandler().uncaughtException(this, e);
- }
- }
- };
-
- ++mIterationCount;
- mThread.setUncaughtExceptionHandler(mExceptionHandler);
- mThread.start();
- }
-
- public synchronized void waitForCompletion() throws Throwable {
- if(mThread == null) {
- // let a wait on a stopped operation to be no-op
- return;
- }
- mThread.join(WAIT_TIMEOUT_IN_MILLISECONDS);
- if(mThread.isAlive()) {
- // the test is hung so collect the state of the system and fail
- String operationName = this.getClass().getSimpleName();
- String message = String.format(
- "%s hung. %s. BugReport collected at: %s",
- operationName,
- this.toString(),
- SensorCtsHelper.collectBugreport(operationName));
- Assert.fail(message);
- }
- mThread = null;
- mExceptionHandler.rethrow();
- }
-
- public void execute() throws Throwable {
- this.start();
- this.waitForCompletion();
- }
-
- @Override
- public String toString() {
- return String.format("ThreadId:%d, Iteration:%d", mThread.getId(), mIterationCount);
- }
-
- /**
- * Subclasses implement this method to perform the work associated with the operation they
- * represent.
- */
- protected abstract void doWork() throws Throwable;
-
- /**
- * Private helpers.
- */
- private class SensorTestExceptionHandler implements Thread.UncaughtExceptionHandler {
- private final Object mLock = new Object();
-
- private Throwable mThrowable;
-
- @Override
- public void uncaughtException(Thread thread, Throwable throwable) {
- synchronized(mLock) {
- // the fist exception is in general the one that is more interesting
- if(mThrowable != null) {
- return;
- }
- mThrowable = throwable;
- }
- }
-
- public void rethrow() throws Throwable {
- Throwable throwable;
- synchronized(mLock) {
- throwable = mThrowable;
- mThrowable = null;
- }
- if(throwable != null) {
- throw throwable;
- }
- }
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/ParallelCompositeSensorTestOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/ParallelCompositeSensorTestOperation.java
deleted file mode 100644
index 3730f4b..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/ParallelCompositeSensorTestOperation.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.hardware.cts.helpers.sensorTestOperations;
-
-import android.hardware.cts.helpers.SensorTestOperation;
-
-import java.util.ArrayList;
-
-/**
- * A test operation that groups a set of SensorTestOperations and allows to execute them all in
- * parallel.
- * This class can be combined to compose other primitive SensorTestOperations.
- */
-public class ParallelCompositeSensorTestOperation extends SensorTestOperation {
- private final ArrayList<SensorTestOperation> mOperations = new ArrayList<SensorTestOperation>();
-
- /**
- * There is no synchronization
- * @param operations
- */
- public void add(SensorTestOperation ... operations) {
- synchronized (mOperations) {
- for(SensorTestOperation operation : operations) {
- mOperations.add(operation);
- }
- }
- }
-
- @Override
- protected void doWork() throws Throwable {
- synchronized (mOperations) {
- for(SensorTestOperation operation : mOperations) {
- operation.start();
- }
- for(SensorTestOperation operation : mOperations) {
- operation.waitForCompletion();
- }
- }
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/RepeatingSensorTestOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/RepeatingSensorTestOperation.java
deleted file mode 100644
index 7a3c450..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/RepeatingSensorTestOperation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.hardware.cts.helpers.sensorTestOperations;
-
-import android.hardware.cts.helpers.SensorTestOperation;
-
-/**
- * High level SensorTestOperation that executes the inner operation in a loop.
- */
-public class RepeatingSensorTestOperation extends SensorTestOperation {
- private final SensorTestOperation mSensorTestOperation;
- private final int mRepetitionCount;
-
- public RepeatingSensorTestOperation(SensorTestOperation operation, int repetitionCount) {
- mSensorTestOperation = operation;
- mRepetitionCount = repetitionCount;
- }
-
- @Override
- protected void doWork() throws Throwable {
- for(int i = 0; i < mRepetitionCount; ++i) {
- mSensorTestOperation.execute();
- }
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/SequentialCompositeSensorTestOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/SequentialCompositeSensorTestOperation.java
deleted file mode 100644
index 4b921680..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/SequentialCompositeSensorTestOperation.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.hardware.cts.helpers.sensorTestOperations;
-
-import android.hardware.cts.helpers.SensorTestOperation;
-
-import java.util.ArrayList;
-
-/**
- * A test operation that groups a set of SensorTestOperations and allows to execute them in a
- * sequence, each operation executes in the order they are added to the composite container.
- * This class can be combined to compose other primitive SensorTestOperations.
- */
-public class SequentialCompositeSensorTestOperation extends SensorTestOperation {
- private final ArrayList<SensorTestOperation> mOperations = new ArrayList<SensorTestOperation>();
-
- /**
- * There is no synchronization
- * @param operations
- */
- public void add(SensorTestOperation ... operations) {
- synchronized (mOperations) {
- for(SensorTestOperation operation : operations) {
- mOperations.add(operation);
- }
- }
- }
-
- @Override
- protected void doWork() throws Throwable {
- synchronized (mOperations) {
- for(SensorTestOperation operation : mOperations) {
- operation.execute();
- }
- }
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
new file mode 100644
index 0000000..5f6e558
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
@@ -0,0 +1,51 @@
+package android.hardware.cts.helpers.sensoroperations;
+
+import android.hardware.cts.helpers.SensorStats;
+
+/**
+ * A {@link ISensorOperation} which contains a common implementation for gathering
+ * {@link SensorStats}.
+ */
+public abstract class AbstractSensorOperation implements ISensorOperation {
+
+ private final SensorStats mStats = new SensorStats();
+
+ /**
+ * Wrapper around {@link SensorStats#addValue(String, Object)}
+ */
+ protected void addValue(String key, Object value) {
+ mStats.addValue(key, value);
+ }
+
+ /**
+ * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)}
+ */
+ protected void addSensorStats(String key, SensorStats stats) {
+ mStats.addSensorStats(key, stats);
+ }
+
+ /**
+ * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)} that allows an index
+ * to be added. This is useful for {@link ISensorOperation}s that have many iterations or child
+ * operations. The key added is in the form {@code key + "_" + index} where index may be zero
+ * padded.
+ */
+ protected void addSensorStats(String key, int index, SensorStats stats) {
+ addSensorStats(String.format("%s_%03d", key, index), stats);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SensorStats getStats() {
+ return mStats;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public abstract ISensorOperation clone();
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
new file mode 100644
index 0000000..ad0ce94
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 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.sensoroperations;
+
+import android.hardware.cts.helpers.SensorStats;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link ISensorOperation} which delays for a specified period of time before performing another
+ * {@link ISensorOperation}.
+ */
+public class DelaySensorOperation implements ISensorOperation {
+ private static final int NANOS_PER_MILLI = 1000000;
+
+ private final ISensorOperation mOperation;
+ private final long mDelay;
+ private final TimeUnit mTimeUnit;
+
+ /**
+ * Constructor for {@link DelaySensorOperation}
+ *
+ * @param operation the child {@link ISensorOperation} to perform after the delay
+ * @param delay the amount of time to delay
+ * @param timeUnit the unit of the delay
+ */
+ public DelaySensorOperation(ISensorOperation operation, long delay, TimeUnit timeUnit) {
+ if (operation == null || timeUnit == null) {
+ throw new IllegalArgumentException("Arguments cannot be null");
+ }
+ mOperation = operation;
+ mDelay = delay;
+ mTimeUnit = timeUnit;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() {
+ sleep(TimeUnit.NANOSECONDS.convert(mDelay, mTimeUnit));
+ mOperation.execute();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SensorStats getStats() {
+ return mOperation.getStats();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DelaySensorOperation clone() {
+ return new DelaySensorOperation(mOperation.clone(), mDelay, mTimeUnit);
+ }
+
+ /**
+ * Helper method to sleep for a given number of ns. Exposed for unit testing.
+ */
+ void sleep(long delayNs) {
+ try {
+ Thread.sleep(delayNs / NANOS_PER_MILLI, (int) (delayNs % NANOS_PER_MILLI));
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
new file mode 100644
index 0000000..0c7e771
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 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.sensoroperations;
+
+import junit.framework.Assert;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A fake {@ISensorOperation} that will run for a specified time and then pass or fail. Useful when
+ * debugging the framework.
+ */
+public class FakeSensorOperation extends AbstractSensorOperation {
+ private static final int NANOS_PER_MILLI = 1000000;
+
+ private final boolean mFail;
+ private final long mDelay;
+ private final TimeUnit mTimeUnit;
+
+ /**
+ * Constructor for {@link FakeSensorOperation} that passes
+ */
+ public FakeSensorOperation(long delay, TimeUnit timeUnit) {
+ this(false, delay, timeUnit);
+ }
+
+ /**
+ * Constructor for {@link FakeSensorOperation}
+ */
+ public FakeSensorOperation(boolean fail, long delay, TimeUnit timeUnit) {
+ if (timeUnit == null) {
+ throw new IllegalArgumentException("Arguments cannot be null");
+ }
+ mFail = fail;
+ mDelay = delay;
+ mTimeUnit = timeUnit;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() {
+ long delayNs = TimeUnit.NANOSECONDS.convert(mDelay, mTimeUnit);
+ try {
+ Thread.sleep(delayNs / NANOS_PER_MILLI, (int) (delayNs % NANOS_PER_MILLI));
+ addValue("executed", new Boolean(true));
+ if (mFail) {
+ Assert.fail("FakeSensorOperation failed");
+ }
+ }catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FakeSensorOperation clone() {
+ return new FakeSensorOperation(mFail, mDelay, mTimeUnit);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ISensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ISensorOperation.java
new file mode 100644
index 0000000..4ae56ea
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ISensorOperation.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 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.sensoroperations;
+
+import android.hardware.cts.helpers.SensorStats;
+
+/**
+ * Interface used by all sensor operations. This allows for complex operations such as chaining
+ * operations together or running operations in parallel.
+ * <p>
+ * Certain restrictions exist for {@link ISensorOperation}s:
+ * <p><ul>
+ * <li>{@link #execute()} should only be called once and behavior is undefined for subsequent calls.
+ * Once {@link #execute()} is called, the class should not be modified. Generally, there is no
+ * synchronization for operations.</li>
+ * <li>{@link #getStats()} should only be called after {@link #execute()}. If it is called before,
+ * the returned value is undefined.</li>
+ * <li>{@link #clone()} may be called any time and should return an operation with the same
+ * parameters as the original.</li>
+ * </ul>
+ */
+public interface ISensorOperation {
+
+ /**
+ * Executes the sensor operation. This may throw {@link RuntimeException}s such as
+ * {@link AssertionError}s.
+ */
+ public void execute();
+
+ /**
+ * Get the stats for the operation.
+ *
+ * @return The {@link SensorStats} for the operation.
+ */
+ public SensorStats getStats();
+
+ /**
+ * Clones the {@link ISensorOperation}. The implementation should also clone all child
+ * operations, so that a cloned operation will run with the exact same parameters as the
+ * original. The stats should not be cloned.
+ *
+ * @return The cloned {@link ISensorOperation}
+ */
+ public ISensorOperation clone();
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
new file mode 100644
index 0000000..9dda510
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
@@ -0,0 +1,255 @@
+/*
+ * 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.hardware.cts.helpers.sensoroperations;
+
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ISensorOperation} that executes a set of children {@link ISensorOperation}s in parallel.
+ * The children are run in parallel but are given an index label in the order they are added. This
+ * class can be combined to compose complex {@link ISensorOperation}s.
+ */
+public class ParallelSensorOperation extends AbstractSensorOperation {
+ public static final String STATS_TAG = "parallel";
+
+ private static final String TAG = "ParallelSensorOperation";
+ private static final int NANOS_PER_MILLI = 1000000;
+
+ private final List<ISensorOperation> mOperations = new LinkedList<ISensorOperation>();
+ private final Long mTimeout;
+ private final TimeUnit mTimeUnit;
+
+ /**
+ * Constructor for the {@link ParallelSensorOperation} without a timeout.
+ */
+ public ParallelSensorOperation() {
+ mTimeout = null;
+ mTimeUnit = null;
+ }
+
+ /**
+ * Constructor for the {@link ParallelSensorOperation} with a timeout.
+ */
+ public ParallelSensorOperation(long timeout, TimeUnit timeUnit) {
+ if (timeUnit == null) {
+ throw new IllegalArgumentException("Arguments cannot be null");
+ }
+ mTimeout = timeout;
+ mTimeUnit = timeUnit;
+ }
+
+ /**
+ * Add a set of {@link ISensorOperation}s.
+ */
+ public void add(ISensorOperation ... operations) {
+ for (ISensorOperation operation : operations) {
+ if (operation == null) {
+ throw new IllegalArgumentException("Arguments cannot be null");
+ }
+ mOperations.add(operation);
+ }
+ }
+
+ /**
+ * Executes the {@link ISensorOperation}s in parallel. If an exception occurs one or more
+ * operations, the first exception will be thrown once all operations are completed.
+ */
+ @Override
+ public void execute() {
+ Long timeoutTimeNs = null;
+ if (mTimeout != null && mTimeUnit != null) {
+ timeoutTimeNs = System.nanoTime() + TimeUnit.NANOSECONDS.convert(mTimeout, mTimeUnit);
+ }
+
+ List<OperationThread> threadPool = new ArrayList<OperationThread>(mOperations.size());
+ for (final ISensorOperation operation : mOperations) {
+ OperationThread thread = new OperationThread(operation);
+ thread.start();
+ threadPool.add(thread);
+ }
+
+ List<Integer> timeoutIndices = new ArrayList<Integer>();
+ List<OperationExceptionInfo> exceptions = new ArrayList<OperationExceptionInfo>();
+ Throwable earliestException = null;
+ Long earliestExceptionTime = null;
+
+ for (int i = 0; i < threadPool.size(); i++) {
+ OperationThread thread = threadPool.get(i);
+ join(thread, timeoutTimeNs);
+ if (thread.isAlive()) {
+ timeoutIndices.add(i);
+ thread.interrupt();
+ }
+
+ Throwable exception = thread.getException();
+ Long exceptionTime = thread.getExceptionTime();
+ if (exception != null && exceptionTime != null) {
+ if (exception instanceof AssertionError) {
+ exceptions.add(new OperationExceptionInfo(i, (AssertionError) exception));
+ }
+ if (earliestExceptionTime == null || exceptionTime < earliestExceptionTime) {
+ earliestException = exception;
+ earliestExceptionTime = exceptionTime;
+ }
+ }
+
+ addSensorStats(STATS_TAG, i, thread.getSensorOperation().getStats());
+ }
+
+ if (earliestException == null) {
+ if (timeoutIndices.size() > 0) {
+ Assert.fail(getTimeoutMessage(timeoutIndices));
+ }
+ } else if (earliestException instanceof AssertionError) {
+ String msg = getExceptionMessage(exceptions, timeoutIndices);
+ throw new AssertionError(msg, earliestException);
+ } else if (earliestException instanceof RuntimeException) {
+ throw (RuntimeException) earliestException;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ParallelSensorOperation clone() {
+ ParallelSensorOperation operation = new ParallelSensorOperation();
+ for (ISensorOperation subOperation : mOperations) {
+ operation.add(subOperation.clone());
+ }
+ return operation;
+ }
+
+ /**
+ * Helper method that joins a thread at a given time in the future.
+ */
+ private void join(Thread thread, Long timeoutTimeNs) {
+ try {
+ if (timeoutTimeNs == null) {
+ thread.join();
+ } else {
+ // Cap wait time to 1ns so that join doesn't block indefinitely.
+ long waitTimeNs = Math.max(timeoutTimeNs - System.nanoTime(), 1);
+ thread.join(waitTimeNs / NANOS_PER_MILLI, (int) waitTimeNs % NANOS_PER_MILLI);
+ }
+ } catch (InterruptedException e) {
+ // Log and ignore
+ Log.w(TAG, "Thread interrupted during join, operations may timeout before expected"
+ + " time");
+ }
+ }
+
+ /**
+ * Helper method for joining the exception messages used in assertions.
+ */
+ private String getExceptionMessage(List<OperationExceptionInfo> exceptions,
+ List<Integer> timeoutIndices) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(exceptions.get(0).toString());
+ for (int i = 1; i < exceptions.size(); i++) {
+ sb.append(", ").append(exceptions.get(i).toString());
+ }
+ if (timeoutIndices.size() > 0) {
+ sb.append(", ").append(getTimeoutMessage(timeoutIndices));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Helper method for formatting the operation timed out message used in assertions
+ */
+ private String getTimeoutMessage(List<Integer> indices) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Operation");
+ if (indices.size() != 1) {
+ sb.append("s");
+ }
+ sb.append(" ").append(indices.get(0));
+ for (int i = 1; i < indices.size(); i++) {
+ sb.append(", ").append(indices.get(i));
+ }
+ sb.append(" timed out");
+ return sb.toString();
+ }
+
+ /**
+ * Helper class for holding operation index and exception
+ */
+ private class OperationExceptionInfo {
+ private final int mIndex;
+ private final AssertionError mException;
+
+ public OperationExceptionInfo(int index, AssertionError exception) {
+ mIndex = index;
+ mException = exception;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Operation %d failed: \"%s\"", mIndex, mException.getMessage());
+ }
+ }
+
+ /**
+ * Helper class to run the {@link ISensorOperation} in its own thread.
+ */
+ private class OperationThread extends Thread {
+ final private ISensorOperation mOperation;
+ private Throwable mException = null;
+ private Long mExceptionTime = null;
+
+ public OperationThread(ISensorOperation operation) {
+ mOperation = operation;
+ }
+
+ /**
+ * Run the thread catching {@link RuntimeException}s and {@link AssertionError}s and
+ * the time it happened.
+ */
+ @Override
+ public void run() {
+ try {
+ mOperation.execute();
+ } catch (AssertionError e) {
+ mExceptionTime = System.nanoTime();
+ mException = e;
+ } catch (RuntimeException e) {
+ mExceptionTime = System.nanoTime();
+ mException = e;
+ }
+ }
+
+ public ISensorOperation getSensorOperation() {
+ return mOperation;
+ }
+
+ public Throwable getException() {
+ return mException;
+ }
+
+ public Long getExceptionTime() {
+ return mExceptionTime;
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
new file mode 100644
index 0000000..28884cd
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
@@ -0,0 +1,70 @@
+/*
+ * 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.hardware.cts.helpers.sensoroperations;
+
+/**
+ * A {@link ISensorOperation} that executes a single {@link ISensorOperation} a given number of
+ * times. This class can be combined to compose complex {@link ISensorOperation}s.
+ */
+public class RepeatingSensorOperation extends AbstractSensorOperation {
+ public static final String STATS_TAG = "repeating";
+
+ private final ISensorOperation mOperation;
+ private final int mIterations;
+
+ /**
+ * Constructor for {@link RepeatingSensorOperation}.
+ *
+ * @param operation the {@link ISensorOperation} to run.
+ * @param iterations the number of iterations to run the operation for.
+ */
+ public RepeatingSensorOperation(ISensorOperation operation, int iterations) {
+ if (operation == null) {
+ throw new IllegalArgumentException("Arguments cannot be null");
+ }
+ mOperation = operation;
+ mIterations = iterations;
+
+ }
+
+ /**
+ * Executes the {@link ISensorOperation}s the given number of times. If an exception occurs
+ * in one iterations, it is thrown and all subsequent iterations will not run.
+ */
+ @Override
+ public void execute() {
+ for(int i = 0; i < mIterations; ++i) {
+ ISensorOperation operation = mOperation.clone();
+ try {
+ operation.execute();
+ } catch (AssertionError e) {
+ String msg = String.format("Iteration %d failed: \"%s\"", i, e.getMessage());
+ throw new AssertionError(msg, e);
+ } finally {
+ addSensorStats(STATS_TAG, i, operation.getStats());
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public RepeatingSensorOperation clone() {
+ return new RepeatingSensorOperation(mOperation.clone(), mIterations);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
new file mode 100644
index 0000000..244a974
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2014 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.sensoroperations;
+
+import android.hardware.cts.helpers.SensorStats;
+
+import junit.framework.TestCase;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for the primitive {@link ISensorOperation}s including {@link DelaySensorOperation},
+ * {@link ParallelSensorOperation}, {@link RepeatingSensorOperation} and
+ * {@link SequentialSensorOperation}.
+ */
+public class SensorOperationTest extends TestCase {
+ private static final int THRESHOLD_MS = 50;
+
+ /**
+ * Test that the {@link FakeSensorOperation} functions correctly. Other tests in this class
+ * rely on this operation.
+ */
+ public void testFakeSensorOperation() {
+ final int opDurationMs = 100;
+
+ ISensorOperation op = new FakeSensorOperation(opDurationMs, TimeUnit.MILLISECONDS);
+
+ assertFalse(op.getStats().flatten().containsKey("executed"));
+ long start = System.currentTimeMillis();
+ op.execute();
+ long duration = System.currentTimeMillis() - start;
+ assertTrue(Math.abs(opDurationMs - duration) < THRESHOLD_MS);
+ assertTrue(op.getStats().flatten().containsKey("executed"));
+
+ op = new FakeSensorOperation(true, 0, TimeUnit.MILLISECONDS);
+ try {
+ op.execute();
+ fail("AssertionError expected");
+ } catch (AssertionError e) {
+ // Expected
+ }
+ }
+
+ /**
+ * Test that the {@link DelaySensorOperation} functions correctly.
+ */
+ public void testDelaySensorOperation() {
+ final int opDurationMs = 500;
+ final int subOpDurationMs = 100;
+
+ FakeSensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
+ ISensorOperation op = new DelaySensorOperation(subOp, opDurationMs, TimeUnit.MILLISECONDS);
+
+ long start = System.currentTimeMillis();
+ op.execute();
+ long duration = System.currentTimeMillis() - start;
+ assertTrue(Math.abs(opDurationMs + subOpDurationMs - duration) < THRESHOLD_MS);
+ }
+
+ /**
+ * Test that the {@link ParallelSensorOperation} functions correctly.
+ */
+ public void testParallelSensorOperation() {
+ final int subOpCount = 100;
+ final int subOpDurationMs = 500;
+
+ ParallelSensorOperation op = new ParallelSensorOperation();
+ for (int i = 0; i < subOpCount; i++) {
+ ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
+ TimeUnit.MILLISECONDS);
+ op.add(subOp);
+ }
+
+ Set<String> statsKeys = op.getStats().flatten().keySet();
+ assertEquals(0, statsKeys.size());
+
+ long start = System.currentTimeMillis();
+ op.execute();
+ long duration = System.currentTimeMillis() - start;
+ assertTrue(Math.abs(subOpDurationMs - duration) < THRESHOLD_MS);
+
+ statsKeys = op.getStats().flatten().keySet();
+ assertEquals(subOpCount, statsKeys.size());
+ for (int i = 0; i < subOpCount; i++) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
+ ParallelSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ }
+ }
+
+ /**
+ * Test that the {@link ParallelSensorOperation} functions correctly if there is a failure in
+ * a child operation.
+ */
+ public void testParallelSensorOperation_fail() {
+ final int subOpCount = 100;
+
+ ParallelSensorOperation op = new ParallelSensorOperation();
+ for (int i = 0; i < subOpCount; i++) {
+ // Trigger failures in the 5th, 55th operations at t=5ms, t=55ms
+ ISensorOperation subOp = new FakeSensorOperation(i % 50 == 5, i, TimeUnit.MILLISECONDS);
+ op.add(subOp);
+ }
+
+ Set<String> statsKeys = op.getStats().flatten().keySet();
+ assertEquals(0, statsKeys.size());
+
+ try {
+ op.execute();
+ fail("AssertionError expected");
+ } catch (AssertionError e) {
+ // Expected
+ System.out.println(e.getMessage());
+ // TODO: Verify that the exception rethrown was at t=5ms.
+ }
+
+ statsKeys = op.getStats().flatten().keySet();
+ assertEquals(subOpCount, statsKeys.size());
+ for (int i = 0; i < subOpCount; i++) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
+ ParallelSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ }
+ }
+
+ /**
+ * Test that the {@link ParallelSensorOperation} functions correctly if a child exceeds the
+ * timeout.
+ */
+ public void testParallelSensorOperation_timeout() {
+ final int subOpCount = 100;
+
+ ParallelSensorOperation op = new ParallelSensorOperation(100, TimeUnit.MILLISECONDS);
+ for (int i = 0; i < subOpCount; i++) {
+ // Trigger timeouts in the 5th, 55th operations (5 seconds vs 0 seconds)
+ ISensorOperation subOp = new FakeSensorOperation(i % 50 == 5 ? 5 : 0, TimeUnit.SECONDS);
+ op.add(subOp);
+ }
+
+ Set<String> statsKeys = op.getStats().flatten().keySet();
+ assertEquals(0, statsKeys.size());
+
+ try {
+ op.execute();
+ fail("AssertionError expected");
+ } catch (AssertionError e) {
+ // Expected
+ System.out.println(e.getMessage());
+ // TODO: Verify that the exception rethrown was at t=5ms.
+ }
+
+ statsKeys = op.getStats().flatten().keySet();
+ assertEquals(subOpCount - 2, statsKeys.size());
+ for (int i = 0; i < subOpCount; i++) {
+ if (i % 50 != 5) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
+ ParallelSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ }
+ }
+ }
+
+ /**
+ * Test that the {@link RepeatingSensorOperation} functions correctly.
+ */
+ public void testRepeatingSensorOperation() {
+ final int iterations = 10;
+ final int subOpDurationMs = 100;
+
+ ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
+ ISensorOperation op = new RepeatingSensorOperation(subOp, iterations);
+
+ Set<String> statsKeys = op.getStats().flatten().keySet();
+ assertEquals(0, statsKeys.size());
+
+ long start = System.currentTimeMillis();
+ op.execute();
+ long duration = System.currentTimeMillis() - start;
+ assertTrue(Math.abs(subOpDurationMs * iterations - duration) < THRESHOLD_MS);
+
+ statsKeys = op.getStats().flatten().keySet();
+ assertEquals(iterations, statsKeys.size());
+ for (int i = 0; i < iterations; i++) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
+ RepeatingSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ }
+ }
+
+ /**
+ * Test that the {@link RepeatingSensorOperation} functions correctly if there is a failure in
+ * a child operation.
+ */
+ public void testRepeatingSensorOperation_fail() {
+ final int iterations = 100;
+ final int failCount = 75;
+
+ ISensorOperation subOp = new FakeSensorOperation(0, TimeUnit.MILLISECONDS) {
+ private int mExecutedCount = 0;
+
+ @Override
+ public void execute() {
+ super.execute();
+ mExecutedCount++;
+
+ if (failCount == mExecutedCount) {
+ fail("FakeSensorOperation failed");
+ }
+ }
+
+ @Override
+ public FakeSensorOperation clone() {
+ // Don't clone
+ return this;
+ }
+ };
+ ISensorOperation op = new RepeatingSensorOperation(subOp, iterations);
+
+ Set<String> statsKeys = op.getStats().flatten().keySet();
+ assertEquals(0, statsKeys.size());
+
+ try {
+ op.execute();
+ fail("AssertionError expected");
+ } catch (AssertionError e) {
+ // Expected
+ System.out.println(e.getMessage());
+ }
+
+ statsKeys = op.getStats().flatten().keySet();
+ assertEquals(failCount, statsKeys.size());
+ for (int i = 0; i < failCount; i++) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
+ RepeatingSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ }
+ }
+
+ /**
+ * Test that the {@link SequentialSensorOperation} functions correctly.
+ */
+ public void testSequentialSensorOperation() {
+ final int subOpCount = 10;
+ final int subOpDurationMs = 100;
+
+ SequentialSensorOperation op = new SequentialSensorOperation();
+ for (int i = 0; i < subOpCount; i++) {
+ ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
+ TimeUnit.MILLISECONDS);
+ op.add(subOp);
+ }
+
+ Set<String> statsKeys = op.getStats().flatten().keySet();
+ assertEquals(0, statsKeys.size());
+
+ long start = System.currentTimeMillis();
+ op.execute();
+ long duration = System.currentTimeMillis() - start;
+ assertTrue(Math.abs(subOpDurationMs * subOpCount - duration) < THRESHOLD_MS);
+
+ statsKeys = op.getStats().flatten().keySet();
+ assertEquals(subOpCount, statsKeys.size());
+ for (int i = 0; i < subOpCount; i++) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
+ SequentialSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ }
+ }
+
+ /**
+ * Test that the {@link SequentialSensorOperation} functions correctly if there is a failure in
+ * a child operation.
+ */
+ public void testSequentialSensorOperation_fail() {
+ final int subOpCount = 100;
+ final int failCount = 75;
+
+ SequentialSensorOperation op = new SequentialSensorOperation();
+ for (int i = 0; i < subOpCount; i++) {
+ // Trigger a failure in the 75th operation only
+ ISensorOperation subOp = new FakeSensorOperation(i + 1 == failCount, 0,
+ TimeUnit.MILLISECONDS);
+ op.add(subOp);
+ }
+
+ Set<String> statsKeys = op.getStats().flatten().keySet();
+ assertEquals(0, statsKeys.size());
+
+ try {
+ op.execute();
+ fail("AssertionError expected");
+ } catch (AssertionError e) {
+ // Expected
+ System.out.println(e.getMessage());
+ }
+
+ statsKeys = op.getStats().flatten().keySet();
+ assertEquals(failCount, statsKeys.size());
+ for (int i = 0; i < failCount; i++) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
+ SequentialSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
new file mode 100644
index 0000000..b62b867
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
@@ -0,0 +1,74 @@
+/*
+ * 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.hardware.cts.helpers.sensoroperations;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A {@link ISensorOperation} that executes a set of children {@link ISensorOperation}s in a
+ * sequence. The children are executed in the order they are added. This class can be combined to
+ * compose complex {@link ISensorOperation}s.
+ */
+public class SequentialSensorOperation extends AbstractSensorOperation {
+ public static final String STATS_TAG = "sequential";
+
+ private final List<ISensorOperation> mOperations = new LinkedList<ISensorOperation>();
+
+ /**
+ * Add a set of {@link ISensorOperation}s.
+ */
+ public void add(ISensorOperation ... operations) {
+ for (ISensorOperation operation : operations) {
+ if (operation == null) {
+ throw new IllegalArgumentException("Arguments cannot be null");
+ }
+ mOperations.add(operation);
+ }
+ }
+
+ /**
+ * Executes the {@link ISensorOperation}s in the order they were added. If an exception occurs
+ * in one operation, it is thrown and all subsequent operations will not run.
+ */
+ @Override
+ public void execute() {
+ for (int i = 0; i < mOperations.size(); i++) {
+ ISensorOperation operation = mOperations.get(i);
+ try {
+ operation.execute();
+ } catch (AssertionError e) {
+ String msg = String.format("Operation %d failed: \"%s\"", i, e.getMessage());
+ throw new AssertionError(msg, e);
+ } finally {
+ addSensorStats(STATS_TAG, i, operation.getStats());
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SequentialSensorOperation clone() {
+ SequentialSensorOperation operation = new SequentialSensorOperation();
+ for (ISensorOperation subOperation : mOperations) {
+ operation.add(subOperation.clone());
+ }
+ return operation;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/VerifySensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifySensorOperation.java
similarity index 84%
rename from tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/VerifySensorOperation.java
rename to tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifySensorOperation.java
index b2f268a..de83a94 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorTestOperations/VerifySensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifySensorOperation.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.cts.helpers.sensorTestOperations;
+package android.hardware.cts.helpers.sensoroperations;
import android.content.Context;
import android.hardware.Sensor;
@@ -22,7 +22,6 @@
import android.hardware.cts.helpers.SensorCtsHelper;
import android.hardware.cts.helpers.SensorManagerTestVerifier;
import android.hardware.cts.helpers.SensorTestInformation;
-import android.hardware.cts.helpers.SensorTestOperation;
import android.hardware.cts.helpers.SensorVerificationHelper;
import android.hardware.cts.helpers.SensorVerificationHelper.VerificationResult;
import android.hardware.cts.helpers.TestSensorEvent;
@@ -30,22 +29,20 @@
import junit.framework.Assert;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
- * A {@link SensorTestOperation} used to verify that sensor events and sensor values are correct.
+ * A {@link ISensorOperation} used to verify that sensor events and sensor values are correct.
* <p>
* Provides methods to set test expectations as well as providing a set of default expectations
* depending on sensor type. When {{@link #execute()} is called, the sensor will collect the
* events and then run all the tests.
* </p>
*/
-public class VerifySensorOperation extends SensorTestOperation {
+public class VerifySensorOperation extends AbstractSensorOperation {
private static final String TAG = "VerifySensorOperation";
private static final boolean DEBUG = false;
@@ -55,7 +52,9 @@
private int mSensorType = 0;
private int mRateUs = 0;
private int mMaxBatchReportLatencyUs = 0;
- private int mEventCount = 0;
+ private Integer mEventCount = null;
+ private Long mDuration = null;
+ private TimeUnit mTimeUnit = null;
private boolean mVerifyEventOrdering = false;
@@ -103,6 +102,28 @@
}
/**
+ * Create a {@link VerifySensorOperation}.
+ *
+ * @param context the {@link Context}.
+ * @param sensorType the sensor type
+ * @param rateUs the rate that
+ * @param maxBatchReportLatencyUs the max batch report latency
+ * @param duration the duration to gather events for
+ * @param timeUnit the time unit of the duration
+ */
+ public VerifySensorOperation(Context context, int sensorType, int rateUs,
+ int maxBatchReportLatencyUs, long duration, TimeUnit timeUnit) {
+ mContext = context;
+ mSensorType = sensorType;
+ mRateUs = rateUs;
+ mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
+ mDuration = duration;
+ mTimeUnit = timeUnit;
+ mSensor = new SensorManagerTestVerifier(mContext, mSensorType, mRateUs,
+ mMaxBatchReportLatencyUs);
+ }
+
+ /**
* Set all of the default test expectations.
*/
public void setDefaultVerifications() {
@@ -434,56 +455,65 @@
* Collect the specified number of events from the sensor and run all enabled verifications.
*/
@Override
- public void doWork() {
- TestSensorEvent[] events = mSensor.collectEvents(mEventCount);
+ public void execute() {
+ addValue("sensor_name", SensorTestInformation.getSensorName(mSensorType));
+ addValue("sensor_handle", mSensor.getUnderlyingSensor().getHandle());
+
+ TestSensorEvent[] events;
+ if (mEventCount != null) {
+ events = mSensor.collectEvents(mEventCount);
+ } else {
+ events = mSensor.collectEvents(mDuration, mTimeUnit);
+ }
boolean failed = false;
StringBuilder sb = new StringBuilder();
VerificationResult result = null;
- Map<String, Object> stats = new HashMap<String, Object>();
if (mVerifyEventOrdering) {
result = SensorVerificationHelper.verifyEventOrdering(events);
// evaluateResults first so it is always called.
- failed = evaluateResults(result, sb, stats) || failed;
+ failed |= evaluateResults(result, sb);
}
if (mVerifyFrequency) {
result = SensorVerificationHelper.verifyFrequency(events, mFrequencyExpected,
mFrequencyThreshold);
- failed = evaluateResults(result, sb, stats) || failed;
+ failed |= evaluateResults(result, sb);
}
if (mVerifyJitter) {
result = SensorVerificationHelper.verifyJitter(events, mJitterExpected,
mJitterThreshold);
- failed = evaluateResults(result, sb, stats) || failed;
+ failed |= evaluateResults(result, sb);
}
if (mVerifyMean) {
result = SensorVerificationHelper.verifyMean(events, mMeanExpected, mMeanThreshold);
- failed = evaluateResults(result, sb, stats) || failed;
+ failed |= evaluateResults(result, sb);
}
if (mVerifyMagnitude) {
result = SensorVerificationHelper.verifyMagnitude(events, mMagnitudeExpected,
mMagnitudeThreshold);
- failed = evaluateResults(result, sb, stats) || failed;
+ failed |= evaluateResults(result, sb);
}
if (mVerifySignum) {
result = SensorVerificationHelper.verifySignum(events, mSignumExpected,
mSignumThreshold);
- failed = evaluateResults(result, sb, stats) || failed;
+ failed |= evaluateResults(result, sb);
}
if (mVerifyStandardDeviation) {
result = SensorVerificationHelper.verifyStandardDeviation(events,
mStandardDeviationThreshold);
- failed = evaluateResults(result, sb, stats) || failed;
+ failed |= evaluateResults(result, sb);
}
- logStats(events, stats);
+ if (DEBUG) {
+ logStats(events);
+ }
if (failed) {
Assert.fail(String.format("%s, handle %d: %s",
@@ -493,6 +523,43 @@
}
/**
+ * {@inheritDoc}
+ */
+ @Override
+ public VerifySensorOperation clone() {
+ VerifySensorOperation operation;
+ if (mEventCount != null) {
+ operation = new VerifySensorOperation(mContext, mSensorType, mRateUs,
+ mMaxBatchReportLatencyUs, mEventCount);
+ } else {
+ operation = new VerifySensorOperation(mContext, mSensorType, mRateUs,
+ mMaxBatchReportLatencyUs, mDuration, mTimeUnit);
+ }
+ if (mVerifyEventOrdering) {
+ operation.verifyEventOrdering();
+ }
+ if (mVerifyFrequency) {
+ operation.verifyFrequency(mFrequencyExpected, mFrequencyThreshold);
+ }
+ if (mVerifyJitter) {
+ operation.verifyJitter(mJitterExpected, mJitterThreshold);
+ }
+ if (mVerifyMean) {
+ operation.verifyMean(mMeanExpected, mMeanThreshold);
+ }
+ if (mVerifyMagnitude) {
+ operation.verifyMagnitude(mMagnitudeExpected, mMagnitudeThreshold);
+ }
+ if (mVerifySignum) {
+ operation.verifySignum(mSignumExpected, mSignumThreshold);
+ }
+ if (mVerifyStandardDeviation) {
+ operation.verifyStandardDeviation(mStandardDeviationThreshold);
+ }
+ return operation;
+ }
+
+ /**
* Return true if the operation rate is not one of {@link SensorManager#SENSOR_DELAY_GAME},
* {@link SensorManager#SENSOR_DELAY_UI}, or {@link SensorManager#SENSOR_DELAY_NORMAL}.
*/
@@ -505,10 +572,9 @@
/**
* Evaluate the results of a test, aggregate the stats, and build the error message.
*/
- private boolean evaluateResults(VerificationResult result, StringBuilder sb,
- Map<String, Object> stats) {
+ private boolean evaluateResults(VerificationResult result, StringBuilder sb) {
for (String key : result.getKeys()) {
- stats.put(key, result.getValue(key));
+ addValue(key, result.getValue(key));
}
if (result.isFailed()) {
@@ -522,33 +588,22 @@
}
/**
- * Log the stats to the logcat.
+ * Log the events to the logcat.
*/
- private void logStats(TestSensorEvent[] events, Map<String, Object> stats) {
+ private void logStats(TestSensorEvent[] events) {
if (events.length <= 0) {
return;
}
- if (DEBUG) {
- List<Double> jitterValues = null;
- if (events.length > 1) {
- jitterValues = SensorCtsHelper.getJitterValues(events);
- }
-
- logTestSensorEvent(0, events[0], null, null);
- for (int i = 1; i < events.length; i++) {
- Double jitter = jitterValues == null ? null : jitterValues.get(i - 1);
- logTestSensorEvent(i, events[i], events[i - 1], jitter);
- }
+ List<Double> jitterValues = null;
+ if (events.length > 1) {
+ jitterValues = SensorCtsHelper.getJitterValues(events);
}
- List<String> keys = new ArrayList<String>(stats.keySet());
- Collections.sort(keys);
- Log.v(TAG, String.format("Stats for %s, handle %d:",
- SensorTestInformation.getSensorName(mSensorType),
- mSensor.getUnderlyingSensor().getHandle()));
- for (String key : keys) {
- Log.v(TAG, String.format("%s: %s", key, stats.get(key).toString()));
+ logTestSensorEvent(0, events[0], null, null);
+ for (int i = 1; i < events.length; i++) {
+ Double jitter = jitterValues == null ? null : jitterValues.get(i - 1);
+ logTestSensorEvent(i, events[i], events[i - 1], jitter);
}
}