Refactor Sensor test platform Operations.
b/17838681

The refactoring is required in preparation to add support to save sensor events upon failure.

Change-Id: I13ed41e3fa5393c61ccee766f0fdb9f4fd6aadd5
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java b/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
index 6454678..3570023 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
@@ -26,7 +26,7 @@
 import android.hardware.cts.helpers.SensorStats;
 import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
 import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
+import android.hardware.cts.helpers.sensoroperations.SensorOperation;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -41,7 +41,7 @@
      * By default tests need to run in a {@link TestSensorEnvironment} that assumes each sensor is
      * running with a load of several listeners, requesting data at different rates.
      *
-     * In a better world the component acting as builder of {@link ISensorOperation} would compute
+     * In a better world the component acting as builder of {@link SensorOperation} would compute
      * this value based on the tests composed.
      *
      * Ideally, each {@link Sensor} object would expose this information to clients.
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
index 6f98e86..a7ad94a 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
@@ -17,7 +17,7 @@
 package android.hardware.cts.helpers;
 
 import android.hardware.Sensor;
-import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
+import android.hardware.cts.helpers.sensoroperations.SensorOperation;
 import android.util.Log;
 
 import java.io.BufferedWriter;
@@ -34,7 +34,7 @@
 import java.util.Set;
 
 /**
- * Class used to store stats related to {@link ISensorOperation}s.  Sensor stats may be linked
+ * Class used to store stats related to {@link SensorOperation}s. Sensor stats may be linked
  * together so that they form a tree.
  */
 public class SensorStats {
@@ -55,8 +55,8 @@
     public static final String STANDARD_DEVIATION_KEY = "standard_deviation";
     public static final String MAGNITUDE_KEY = "magnitude";
 
-    private final Map<String, Object> mValues = new HashMap<String, Object>();
-    private final Map<String, SensorStats> mSensorStats = new HashMap<String, SensorStats>();
+    private final Map<String, Object> mValues = new HashMap<>();
+    private final Map<String, SensorStats> mSensorStats = new HashMap<>();
 
     /**
      * Add a value.
@@ -73,7 +73,7 @@
 
     /**
      * Add a nested {@link SensorStats}. This is useful for keeping track of stats in a
-     * {@link ISensorOperation} tree.
+     * {@link SensorOperation} tree.
      *
      * @param key the key
      * @param stats the sub {@link SensorStats} object.
@@ -104,13 +104,13 @@
     /**
      * 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")}.
+     * {@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);
+        final Map<String, Object> flattenedMap = new HashMap<>(mValues);
         for (Entry<String, SensorStats> statsEntry : mSensorStats.entrySet()) {
             for (Entry<String, Object> valueEntry : statsEntry.getValue().flatten().entrySet()) {
                 String key = statsEntry.getKey() + DELIMITER + valueEntry.getKey();
@@ -166,7 +166,7 @@
     }
 
     private static List<String> getSortedKeys(Map<String, Object> flattenedStats) {
-        List<String> keys = new ArrayList<String>(flattenedStats.keySet());
+        List<String> keys = new ArrayList<>(flattenedStats.keySet());
         Collections.sort(keys);
         return keys;
     }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java
index 5be30a4..d59ea7a 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java
@@ -19,12 +19,12 @@
 import android.content.Context;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
-import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
+import android.hardware.cts.helpers.sensoroperations.SensorOperation;
 
 import java.util.concurrent.TimeUnit;
 
 /**
- * A class that encapsulates base environment information for the {@link ISensorOperation}.
+ * A class that encapsulates base environment information for the {@link SensorOperation}.
  * The environment is self contained and carries its state around all the sensor test framework.
  */
 public class TestSensorEnvironment {
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
deleted file mode 100644
index 5b969f2..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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;
-
-/**
- * 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#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/AlarmOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 88e4954..8848337 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -29,7 +29,7 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * An {@link ISensorOperation} which performs another {@link ISensorOperation} and then wakes up
+ * An {@link SensorOperation} which performs another {@link SensorOperation} and then wakes up
  * after a specified period of time and waits for the child operation to complete.
  * <p>
  * This operation can be used to allow the device to go to sleep and wake it up after a specified
@@ -40,11 +40,11 @@
  * but wake the device one time at the specified period.
  * </p>
  */
-public class AlarmOperation extends AbstractSensorOperation {
+public class AlarmOperation extends SensorOperation {
     private static final String ACTION = "AlarmOperationAction";
     private static final String WAKE_LOCK_TAG = "AlarmOperationWakeLock";
 
-    private final ISensorOperation mOperation;
+    private final SensorOperation mOperation;
     private final Context mContext;
     private final long mSleepDuration;
     private final TimeUnit mTimeUnit;
@@ -55,13 +55,17 @@
     /**
      * Constructor for {@link DelaySensorOperation}
      *
-     * @param operation the child {@link ISensorOperation} to perform after the delay
+     * @param operation the child {@link SensorOperation} to perform after the delay
      * @param context the context used to access the alarm manager
      * @param sleepDuration the amount of time to sleep
      * @param timeUnit the unit of the duration
      */
-    public AlarmOperation(ISensorOperation operation, Context context, long sleepDuration,
+    public AlarmOperation(
+            SensorOperation operation,
+            Context context,
+            long sleepDuration,
             TimeUnit timeUnit) {
+        super(operation.getStats());
         mOperation = operation;
         mContext = context;
         mSleepDuration = sleepDuration;
@@ -120,7 +124,7 @@
      */
     private synchronized void acquireWakeLock() {
         // Don't acquire wake lock if the operation has already completed.
-        if (mCompleted == true || mWakeLock != null) {
+        if (mCompleted || mWakeLock != null) {
             return;
         }
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
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
index b4d1f23..3ee08f6 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
@@ -22,22 +22,22 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * An {@link ISensorOperation} which delays for a specified period of time before performing another
- * {@link ISensorOperation}.
+ * An {@link SensorOperation} which delays for a specified period of time before performing another
+ * {@link SensorOperation}.
  */
-public class DelaySensorOperation implements ISensorOperation {
-    private final ISensorOperation mOperation;
+public class DelaySensorOperation extends SensorOperation {
+    private final SensorOperation 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 operation the child {@link SensorOperation} 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) {
+    public DelaySensorOperation(SensorOperation operation, long delay, TimeUnit timeUnit) {
         if (operation == null || timeUnit == null) {
             throw new IllegalArgumentException("Arguments cannot be null");
         }
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
index bb64dfa..8cfd351 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
@@ -16,17 +16,17 @@
 
 package android.hardware.cts.helpers.sensoroperations;
 
-import android.hardware.cts.helpers.SensorStats;
-
 import junit.framework.Assert;
 
+import android.hardware.cts.helpers.SensorStats;
+
 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.
+ * A fake {@link SensorOperation} that will run for a specified time and then pass or fail. Useful
+ * when debugging the framework.
  */
-public class FakeSensorOperation extends AbstractSensorOperation {
+public class FakeSensorOperation extends SensorOperation {
     private static final int NANOS_PER_MILLI = 1000000;
 
     private final boolean mFail;
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
deleted file mode 100644
index 62a4e9e..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ISensorOperation.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.
-     *
-     * NOTE: the operation is expected to handle interruption by:
-     * - cleaning up on {@link InterruptedException}
-     * - propagating the exception down the stack
-     */
-    public void execute() throws InterruptedException;
-
-    /**
-     * 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
index 5a4466c..e55c6cb 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
@@ -22,7 +22,6 @@
 import android.os.SystemClock;
 
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -33,14 +32,14 @@
 import java.util.concurrent.TimeoutException;
 
 /**
- * A {@link ISensorOperation} that executes a set of children {@link ISensorOperation}s in parallel.
+ * A {@link SensorOperation} that executes a set of children {@link SensorOperation}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.
+ * class can be combined to compose complex {@link SensorOperation}s.
  */
-public class ParallelSensorOperation extends AbstractSensorOperation {
+public class ParallelSensorOperation extends SensorOperation {
     public static final String STATS_TAG = "parallel";
 
-    private final List<ISensorOperation> mOperations = new LinkedList<ISensorOperation>();
+    private final ArrayList<SensorOperation> mOperations = new ArrayList<>();
     private final Long mTimeout;
     private final TimeUnit mTimeUnit;
 
@@ -65,10 +64,10 @@
     }
 
     /**
-     * Add a set of {@link ISensorOperation}s.
+     * Add a set of {@link SensorOperation}s.
      */
-    public void add(ISensorOperation ... operations) {
-        for (ISensorOperation operation : operations) {
+    public void add(SensorOperation ... operations) {
+        for (SensorOperation operation : operations) {
             if (operation == null) {
                 throw new IllegalArgumentException("Arguments cannot be null");
             }
@@ -77,7 +76,7 @@
     }
 
     /**
-     * Executes the {@link ISensorOperation}s in parallel. If an exception occurs one or more
+     * Executes the {@link SensorOperation}s in parallel. If an exception occurs one or more
      * operations, the first exception will be thrown once all operations are completed.
      */
     @Override
@@ -92,11 +91,11 @@
         executor.allowCoreThreadTimeOut(true);
         executor.prestartAllCoreThreads();
 
-        ArrayList<Future<ISensorOperation>> futures = new ArrayList<Future<ISensorOperation>>();
-        for (final ISensorOperation operation : mOperations) {
-            Future<ISensorOperation> future = executor.submit(new Callable<ISensorOperation>() {
+        ArrayList<Future<SensorOperation>> futures = new ArrayList<>();
+        for (final SensorOperation operation : mOperations) {
+            Future<SensorOperation> future = executor.submit(new Callable<SensorOperation>() {
                 @Override
-                public ISensorOperation call() throws Exception {
+                public SensorOperation call() throws Exception {
                     operation.execute();
                     return operation;
                 }
@@ -111,12 +110,12 @@
         }
 
         boolean hasAssertionErrors = false;
-        ArrayList<Integer> timeoutIndices = new ArrayList<Integer>();
-        ArrayList<Throwable> exceptions = new ArrayList<Throwable>();
+        ArrayList<Integer> timeoutIndices = new ArrayList<>();
+        ArrayList<Throwable> exceptions = new ArrayList<>();
         for (int i = 0; i < operationsCount; ++i) {
-            Future<ISensorOperation> future = futures.get(i);
+            Future<SensorOperation> future = futures.get(i);
             try {
-                ISensorOperation operation = getFutureResult(future, executionTimeNs);
+                SensorOperation operation = getFutureResult(future, executionTimeNs);
                 addSensorStats(STATS_TAG, i, operation.getStats());
             } catch (ExecutionException e) {
                 // extract the exception thrown by the worker thread
@@ -151,7 +150,7 @@
     @Override
     public ParallelSensorOperation clone() {
         ParallelSensorOperation operation = new ParallelSensorOperation();
-        for (ISensorOperation subOperation : mOperations) {
+        for (SensorOperation subOperation : mOperations) {
             operation.add(subOperation.clone());
         }
         return operation;
@@ -160,7 +159,7 @@
     /**
      * Helper method that waits for a {@link Future} to complete, and returns its result.
      */
-    private ISensorOperation getFutureResult(Future<ISensorOperation> future, Long timeoutNs)
+    private SensorOperation getFutureResult(Future<SensorOperation> future, Long timeoutNs)
             throws ExecutionException, TimeoutException, InterruptedException {
         if (timeoutNs == null) {
             return future.get();
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
index 3d682fe..2e3af36 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
@@ -19,22 +19,22 @@
 import android.hardware.cts.helpers.SensorStats;
 
 /**
- * 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.
+ * A {@link SensorOperation} that executes a single {@link SensorOperation} a given number of
+ * times. This class can be combined to compose complex {@link SensorOperation}s.
  */
-public class RepeatingSensorOperation extends AbstractSensorOperation {
+public class RepeatingSensorOperation extends SensorOperation {
     public static final String STATS_TAG = "repeating";
 
-    private final ISensorOperation mOperation;
+    private final SensorOperation mOperation;
     private final int mIterations;
 
     /**
      * Constructor for {@link RepeatingSensorOperation}.
      *
-     * @param operation the {@link ISensorOperation} to run.
+     * @param operation the {@link SensorOperation} to run.
      * @param iterations the number of iterations to run the operation for.
      */
-    public RepeatingSensorOperation(ISensorOperation operation, int iterations) {
+    public RepeatingSensorOperation(SensorOperation operation, int iterations) {
         if (operation == null) {
             throw new IllegalArgumentException("Arguments cannot be null");
         }
@@ -44,13 +44,13 @@
     }
 
     /**
-     * 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.
+     * Executes the {@link SensorOperation}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() throws InterruptedException {
         for(int i = 0; i < mIterations; ++i) {
-            ISensorOperation operation = mOperation.clone();
+            SensorOperation operation = mOperation.clone();
             try {
                 operation.execute();
             } catch (AssertionError e) {
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java
new file mode 100644
index 0000000..ea16716
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+/**
+ * Base class 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 SensorOperation}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 abstract class SensorOperation {
+    private final SensorStats mStats;
+
+    protected SensorOperation() {
+        this(new SensorStats());
+    }
+
+    protected SensorOperation(SensorStats stats) {
+        mStats = stats;
+    }
+
+    /**
+     * @return The {@link SensorStats} for the operation.
+     */
+    public SensorStats getStats() {
+        return mStats;
+    }
+
+    /**
+     * Executes the sensor operation.
+     * This may throw {@link RuntimeException}s such as {@link AssertionError}s.
+     *
+     * NOTE: the operation is expected to handle interruption by:
+     * - cleaning up on {@link InterruptedException}
+     * - propagating the exception down the stack
+     */
+    public abstract void execute() throws InterruptedException;
+
+    /**
+     * @return The cloned {@link SensorOperation}.
+     *
+     * NOTE: 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.
+     */
+    public abstract SensorOperation clone();
+
+    /**
+     * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)}
+     */
+    protected void addSensorStats(String key, SensorStats stats) {
+        getStats().addSensorStats(key, stats);
+    }
+
+    /**
+     * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)} that allows an index
+     * to be added. This is useful for {@link SensorOperation}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);
+    }
+}
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
index bc48725..033f3c5 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
@@ -24,7 +24,7 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * Tests for the primitive {@link ISensorOperation}s including {@link DelaySensorOperation},
+ * Tests for the primitive {@link SensorOperation}s including {@link DelaySensorOperation},
  * {@link ParallelSensorOperation}, {@link RepeatingSensorOperation} and
  * {@link SequentialSensorOperation}.
  */
@@ -38,7 +38,7 @@
     public void testFakeSensorOperation() throws InterruptedException {
         final int opDurationMs = 100;
 
-        ISensorOperation op = new FakeSensorOperation(opDurationMs, TimeUnit.MILLISECONDS);
+        SensorOperation op = new FakeSensorOperation(opDurationMs, TimeUnit.MILLISECONDS);
 
         assertFalse(op.getStats().flatten().containsKey("executed"));
         long start = System.currentTimeMillis();
@@ -65,12 +65,12 @@
         final int subOpDurationMs = 100;
 
         FakeSensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
-        ISensorOperation op = new DelaySensorOperation(subOp, opDurationMs, TimeUnit.MILLISECONDS);
+        SensorOperation op = new DelaySensorOperation(subOp, opDurationMs, TimeUnit.MILLISECONDS);
 
         long startMs = System.currentTimeMillis();
         op.execute();
-        long dirationMs = System.currentTimeMillis() - startMs;
-        long durationDeltaMs = Math.abs(opDurationMs + subOpDurationMs - dirationMs);
+        long durationMs = System.currentTimeMillis() - startMs;
+        long durationDeltaMs = Math.abs(opDurationMs + subOpDurationMs - durationMs);
         assertTrue(durationDeltaMs < TEST_DURATION_THRESHOLD_MS);
     }
 
@@ -83,7 +83,7 @@
 
         ParallelSensorOperation op = new ParallelSensorOperation();
         for (int i = 0; i < subOpCount; i++) {
-            ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
+            SensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
                     TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
@@ -124,7 +124,7 @@
         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);
+            SensorOperation subOp = new FakeSensorOperation(i % 50 == 5, i, TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
 
@@ -164,7 +164,7 @@
         ParallelSensorOperation op = new ParallelSensorOperation(1, TimeUnit.SECONDS);
         for (int i = 0; i < subOpCount; i++) {
             // Trigger timeouts in the 5th, 55th operations (5 seconds vs 1 seconds)
-            ISensorOperation subOp = new FakeSensorOperation(i % 50 == 5 ? 5 : 0, TimeUnit.SECONDS);
+            SensorOperation subOp = new FakeSensorOperation(i % 50 == 5 ? 5 : 0, TimeUnit.SECONDS);
             op.add(subOp);
         }
 
@@ -196,8 +196,8 @@
         final int iterations = 10;
         final int subOpDurationMs = 100;
 
-        ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
-        ISensorOperation op = new RepeatingSensorOperation(subOp, iterations);
+        SensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
+        SensorOperation op = new RepeatingSensorOperation(subOp, iterations);
 
         Set<String> statsKeys = op.getStats().flatten().keySet();
         assertEquals(0, statsKeys.size());
@@ -223,7 +223,7 @@
         final int iterations = 100;
         final int failCount = 75;
 
-        ISensorOperation subOp = new FakeSensorOperation(0, TimeUnit.MILLISECONDS) {
+        SensorOperation subOp = new FakeSensorOperation(0, TimeUnit.MILLISECONDS) {
             private int mExecutedCount = 0;
             private SensorStats mFakeStats = new SensorStats();
 
@@ -249,7 +249,7 @@
                 return mFakeStats;
             }
         };
-        ISensorOperation op = new RepeatingSensorOperation(subOp, iterations);
+        SensorOperation op = new RepeatingSensorOperation(subOp, iterations);
 
         Set<String> statsKeys = op.getStats().flatten().keySet();
         assertEquals(0, statsKeys.size());
@@ -283,7 +283,7 @@
 
         SequentialSensorOperation op = new SequentialSensorOperation();
         for (int i = 0; i < subOpCount; i++) {
-            ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
+            SensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
                     TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
@@ -315,7 +315,7 @@
         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,
+            SensorOperation subOp = new FakeSensorOperation(i + 1 == failCount, 0,
                     TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
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
index 2ed0ca6..85d189a 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
@@ -18,24 +18,23 @@
 
 import android.hardware.cts.helpers.SensorStats;
 
-import java.util.LinkedList;
-import java.util.List;
+import java.util.ArrayList;
 
 /**
- * A {@link ISensorOperation} that executes a set of children {@link ISensorOperation}s in a
+ * A {@link SensorOperation} that executes a set of children {@link SensorOperation}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.
+ * compose complex {@link SensorOperation}s.
  */
-public class SequentialSensorOperation extends AbstractSensorOperation {
+public class SequentialSensorOperation extends SensorOperation {
     public static final String STATS_TAG = "sequential";
 
-    private final List<ISensorOperation> mOperations = new LinkedList<ISensorOperation>();
+    private final ArrayList<SensorOperation> mOperations = new ArrayList<>();
 
     /**
-     * Add a set of {@link ISensorOperation}s.
+     * Add a set of {@link SensorOperation}s.
      */
-    public void add(ISensorOperation ... operations) {
-        for (ISensorOperation operation : operations) {
+    public void add(SensorOperation ... operations) {
+        for (SensorOperation operation : operations) {
             if (operation == null) {
                 throw new IllegalArgumentException("Arguments cannot be null");
             }
@@ -44,13 +43,13 @@
     }
 
     /**
-     * Executes the {@link ISensorOperation}s in the order they were added. If an exception occurs
+     * Executes the {@link SensorOperation}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() throws InterruptedException {
         for (int i = 0; i < mOperations.size(); i++) {
-            ISensorOperation operation = mOperations.get(i);
+            SensorOperation operation = mOperations.get(i);
             try {
                 operation.execute();
             } catch (AssertionError e) {
@@ -69,7 +68,7 @@
     @Override
     public SequentialSensorOperation clone() {
         SequentialSensorOperation operation = new SequentialSensorOperation();
-        for (ISensorOperation subOperation : mOperations) {
+        for (SensorOperation subOperation : mOperations) {
             operation.add(subOperation.clone());
         }
         return operation;
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
index 6e53dbb..ded1522 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
@@ -35,21 +35,19 @@
 import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
 import android.os.Handler;
 
-import java.util.Collection;
 import java.util.HashSet;
 import java.util.concurrent.TimeUnit;
 
 /**
- * A {@link ISensorOperation} used to verify that sensor events and sensor values are correct.
+ * A {@link SensorOperation} 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 TestSensorOperation extends AbstractSensorOperation {
-    private final Collection<ISensorVerification> mVerifications =
-            new HashSet<ISensorVerification>();
+public class TestSensorOperation extends SensorOperation {
+    private final HashSet<ISensorVerification> mVerifications = new HashSet<>();
 
     private final TestSensorManager mSensorManager;
     private final TestSensorEnvironment mEnvironment;
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
index b500ea7..5f4f5d8 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
@@ -22,36 +22,36 @@
 import android.os.PowerManager.WakeLock;
 
 /**
- * An {@link ISensorOperation} which holds a wakelock while performing another
- * {@link ISensorOperation}.
+ * An {@link SensorOperation} which holds a wake-lock while performing another
+ * {@link SensorOperation}.
  */
-public class WakeLockOperation extends AbstractSensorOperation {
+public class WakeLockOperation extends SensorOperation {
     private static final String TAG = "WakeLockOperation";
 
-    private final ISensorOperation mOperation;
+    private final SensorOperation mOperation;
     private final Context mContext;
-    private final int mWakelockFlags;
+    private final int mWakeLockFlags;
 
     /**
      * Constructor for {@link WakeLockOperation}.
      *
-     * @param operation the child {@link ISensorOperation} to perform after the delay
+     * @param operation the child {@link SensorOperation} to perform after the delay
      * @param context the context used to access the power manager
-     * @param wakelockFlags the flags used when acquiring the wakelock
+     * @param wakeLockFlags the flags used when acquiring the wake-lock
      */
-    public WakeLockOperation(ISensorOperation operation, Context context, int wakelockFlags) {
+    public WakeLockOperation(SensorOperation operation, Context context, int wakeLockFlags) {
         mOperation = operation;
         mContext = context;
-        mWakelockFlags = wakelockFlags;
+        mWakeLockFlags = wakeLockFlags;
     }
 
     /**
      * Constructor for {@link WakeLockOperation}.
      *
-     * @param operation the child {@link ISensorOperation} to perform after the delay
+     * @param operation the child {@link SensorOperation} to perform after the delay
      * @param context the context used to access the power manager
      */
-    public WakeLockOperation(ISensorOperation operation, Context context) {
+    public WakeLockOperation(SensorOperation operation, Context context) {
         this(operation, context, PowerManager.PARTIAL_WAKE_LOCK);
     }
 
@@ -61,7 +61,7 @@
     @Override
     public void execute() throws InterruptedException {
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        WakeLock wakeLock = pm.newWakeLock(mWakelockFlags, TAG);
+        WakeLock wakeLock = pm.newWakeLock(mWakeLockFlags, TAG);
 
         wakeLock.acquire();
         try {
@@ -83,7 +83,7 @@
      * {@inheritDoc}
      */
     @Override
-    public ISensorOperation clone() {
-        return new WakeLockOperation(mOperation, mContext, mWakelockFlags);
+    public SensorOperation clone() {
+        return new WakeLockOperation(mOperation, mContext, mWakeLockFlags);
     }
 }