Add support to save test details in CtsVerifier report.

- Wires the results of each test case to be stored in CtsVerifier report.
- Add support to discover test cases similarly to JUnit
- Use resources for UI strings in accelerometer tests.
- Use resources for UI strings in gyroscope tests.
- Use resources for UI strings in magnetic field tests.

Change-Id: I8e29aa459e9943ad188b3b10a651a0ea73d8110d
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 021f724..ac4d0fb 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -41,7 +41,7 @@
 # Builds and launches CTS Verifier on a device.
 .PHONY: cts-verifier
 cts-verifier: CtsVerifier adb
-	adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier.apk \
+	adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier/CtsVerifier.apk \
 		&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
 
 #
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 5683391..b9749b1 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -424,10 +424,35 @@
     <string name="offhostService">OffhostService</string>
 
     <!-- Strings for Sensor Test Activities -->
+    <string name="snsr_test_summary">Tests passed: %1$d, Tests skipped: %2$d, Tests failed: %3$d</string>
+    <string name="snsr_test_complete">Test completed.</string>
+    <string name="snsr_test_pass">PASS</string>
+    <string name="snsr_test_skipped">SKIPPED</string>
+    <string name="snsr_test_fail">FAIL</string>
+    <string name="snsr_executing_test">\nExecuting test case \'%1$s\'..\n</string>
+    <string name="snsr_orientation_portrait">[Device orientation]: Portrait.</string>
+    <string name="snsr_orientation_landscape">[Device orientation]: Landscape.</string>
+
+    <!-- Strings to interact with users in Sensor Tests -->
+    <string name="snsr_test_play_sound">A sound will be played once the verification is complete...</string>
+    <string name="snsr_device_steady">Keep the device steady.</string>
+    <string name="snsr_wait_for_user">Press \'Next\' to continue.</string>
+    <string name="snsr_on_complete_return">After completing the task, go back to this test.</string>
+    <string name="snsr_airplane_mode_set">Airplane mode set.</string>
+    <string name="snsr_airplane_mode_request">You will be redirected to set \'Airplane Mode\' ON.</string>
+    <string name="snsr_screen_off_timeout">Screen Off Timeout set to: %1$d seconds.</string>
+    <string name="snsr_screen_off_request">You will be redirected to set \'Display Sleep\' to %1$d seconds.</string>
+
     <!-- Accelerometer -->
     <string name="snsr_accel_test">Accelerometer Test</string>
     <string name="snsr_accel_test_info">This test verifies that the accelerometer is working properly. As you move the device around through space, the triangle should always point down (i.e. in the direction of gravity.) If it does not, the accelerometer is improperly configured.</string>
     <string name="snsr_accel_m_test">Accelerometer Measurement Tests</string>
+    <string name="snsr_accel_test_face_up">Place the device in a flat surface with the screen facing the ceiling.</string>
+    <string name="snsr_accel_test_face_down">Place the device in a flat surface with the screen facing it.</string>
+    <string name="snsr_accel_test_right_side">Place the device in a flat surface resting vertically on its right side.</string>
+    <string name="snsr_accel_test_left_side">Place the device in a flat surface resting vertically on its left side.</string>
+    <string name="snsr_accel_test_top_side">Place the device in a flat surface resting vertically on its top side.</string>
+    <string name="snsr_accel_test_bottom_side">Place the device in a flat surface resting vertically on its bottom side.</string>
 
     <!-- Gyroscope -->
     <string name="snsr_gyro_test">Gyroscope Test</string>
@@ -438,6 +463,16 @@
     <string name="snsr_gyro_test_degrees_title">Wrong units?</string>
     <string name="snsr_gyro_test_degrees_message">These values looks like degrees per second. These should be radians per second!</string>
     <string name="snsr_gyro_m_test">Gyroscope Measurement Test</string>
+    <string name="snsr_gyro_device_placement">Place the device in a flat surface with the screen
+        facing the ceiling, make sure the device aligns with the orientation specified for each
+        scenario. Then follow the instructions:</string>
+    <string name="snsr_gyro_device_static">Leave the device static.</string>
+    <string name="snsr_gyro_rotate_clockwise">Rotate the device clockwise.</string>
+    <string name="snsr_gyro_rotate_counter_clockwise">Rotate the device counter clockwise.</string>
+    <string name="snsr_gyro_rotate_right_side">Rotate the device on its right side until it stands on its side.</string>
+    <string name="snsr_gyro_rotate_left_side">Rotate the device on its left side until it stands on its side.</string>
+    <string name="snsr_gyro_rotate_top_side">Rotate the device on its top side until it stands perpendicular.</string>
+    <string name="snsr_gyro_rotate_bottom_side">Rotate the device on its bottom side util it stands perpendicular.</string>
 
     <!-- Heart Rate -->
     <string name="snsr_heartrate_test">Heart Rate Test</string>
@@ -447,6 +482,11 @@
 
     <!-- Magnetic Field -->
     <string name="snsr_mag_m_test">Magnetic Field Measurement Tests</string>
+    <string name="snsr_mag_verify_norm">Verifying the Norm...</string>
+    <string name="snsr_mag_verify_std_dev">Verifying the Standard Deviation...</string>
+    <string name="snsr_mag_calibration_description">Please calibrate the Magnetometer by moving
+        it in 8 shapes in different orientations.</string>
+    <string name="snsr_mag_calibration_complete">When done, leave the device in a flat surface.</string>
 
     <!-- Sensor Value Accuracy -->
     <string name="snsr_val_acc_test">Sensor Value Accuracy Tests</string>
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 edac3c5..f40bc75 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.sensors;
 
+import com.android.cts.verifier.R;
+
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
@@ -33,37 +35,37 @@
 
     public String testFaceUp() throws Throwable {
         return verifyMeasurements(
-                "Place the device in a flat surface with the screen facing the ceiling",
+                R.string.snsr_accel_test_face_up,
                 0, 0, SensorManager.STANDARD_GRAVITY);
     }
 
     public String testFaceDown() throws Throwable {
         return delayedVerifyMeasurements(
-                "Press 'Next' and place the device in a flat surface with the screen facing it",
+                R.string.snsr_accel_test_face_down,
                 0, 0, -SensorManager.STANDARD_GRAVITY);
     }
 
     public String testRightSide() throws Throwable {
         return verifyMeasurements(
-                "Place the device in a flat surface resting vertically on its right side",
+                R.string.snsr_accel_test_right_side,
                 -SensorManager.STANDARD_GRAVITY, 0, 0);
     }
 
     public String testLeftSide() throws Throwable {
         return verifyMeasurements(
-                "Place the device in a flat surface resting vertically on its left side",
+                R.string.snsr_accel_test_left_side,
                 SensorManager.STANDARD_GRAVITY, 0, 0);
     }
 
     public String testTopSide() throws Throwable {
         return verifyMeasurements(
-                "Place the device in a flat surface resting vertically on its top side",
+                R.string.snsr_accel_test_top_side,
                 0, -SensorManager.STANDARD_GRAVITY, 0);
     }
 
     public String testBottomSide() throws Throwable {
         return verifyMeasurements(
-                "Place the device in a flat surface resting vertically on its bottom side",
+                R.string.snsr_accel_test_bottom_side,
                 0, SensorManager.STANDARD_GRAVITY, 0);
     }
 
@@ -101,11 +103,10 @@
         return null;
     }
 
-    private String delayedVerifyMeasurements(
-            String message,
-            float ... expectations) throws Throwable {
-        appendText(String.format("\n%s.", message));
-        appendText("A sound will be played once the verification is complete...");
+    private String delayedVerifyMeasurements(int descriptionResId, float ... expectations)
+            throws Throwable {
+        appendText(descriptionResId);
+        appendText(R.string.snsr_test_play_sound);
         waitForUser();
         Thread.sleep(TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS));
 
@@ -116,9 +117,10 @@
         }
     }
 
-    private String verifyMeasurements(String message, float ... expectations) throws Throwable {
-        appendText(String.format("\n%s.", message));
-        appendText("Press 'Next' when ready and keep the device steady.");
+    private String verifyMeasurements(int descriptionResId, float ... expectations)
+            throws Throwable {
+        appendText(descriptionResId);
+        appendText(R.string.snsr_device_steady);
         waitForUser();
 
         return verifyMeasurements(expectations);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
index a550cde..8685d43 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.sensors;
 
+import com.android.cts.verifier.R;
+
 import android.graphics.Color;
 import android.hardware.cts.helpers.SensorNotSupportedException;
 
@@ -47,7 +49,7 @@
             message = e.getMessage();
         }
         setTestResult(getTestId(), testResult, message);
-        appendText("\nTest completed. Press 'Next' to finish.\n");
+        appendText(R.string.snsr_test_complete);
         waitForUser();
         finish();
     }
@@ -63,7 +65,7 @@
     protected abstract void onRun() throws Throwable;
 
     protected void logSuccess() {
-        appendText("SUCCESS", Color.GREEN);
+        appendText(R.string.snsr_test_pass, Color.GREEN);
     }
 
     private String getTestId() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
index f63c5b7..ead7e2c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
@@ -71,6 +71,10 @@
     private Thread mWorkerThread;
     private CountDownLatch mCountDownLatch;
 
+    private volatile int mTestPassedCounter;
+    private volatile int mTestSkippedCounter;
+    private volatile int mTestFailedCounter;
+
     protected BaseSensorTestActivity(Class testClass) {
         mTestClass = testClass;
     }
@@ -97,58 +101,39 @@
 
     @Override
     public void run() {
-        String testClassName = mTestClass.getName();
-
         try {
             activitySetUp();
         } catch (Throwable e) {
-            setTestResult(testClassName, SensorTestResult.SKIPPED, e.getMessage());
+            SensorTestResult testSkipped = SensorTestResult.SKIPPED;
+            String testSummary = e.getMessage();
+            setTestResult(getTestClassName(), testSkipped, testSummary);
+            logTestDetails(testSkipped, testSummary);
             return;
         }
 
         // TODO: it might be necessary to implement fall through so passed tests do not need to
         //       be re-executed
-        int testPassedCounter = 0;
-        int testSkippedCounter = 0;
-        int testFailedCounter = 0;
+        StringBuilder overallTestResults = new StringBuilder();
         for (Method testMethod : findTestMethods()) {
-            String testName = String.format("%s.%s", testClassName, testMethod.getName());
-            try {
-                appendText("\nExecuting test case '" + testName + "'...");
-                String testDetails = (String) testMethod.invoke(this);
-                setTestResult(testName, SensorTestResult.PASS, testDetails);
-                ++testPassedCounter;
-            } catch (InvocationTargetException e) {
-                // get the inner exception, because we use reflection APIs to execute the test
-                Throwable cause = e.getCause();
-                SensorTestResult testResult;
-                if (cause instanceof SensorNotSupportedException) {
-                    testResult = SensorTestResult.SKIPPED;
-                    ++testSkippedCounter;
-                } else {
-                    testResult = SensorTestResult.FAIL;
-                    ++testFailedCounter;
-                }
-                setTestResult(testName, testResult, cause.getMessage());
-            } catch (Throwable e) {
-                setTestResult(testName, SensorTestResult.FAIL, e.getMessage());
-                ++testFailedCounter;
-            }
+            SensorTestDetails testDetails = executeTest(testMethod);
+            setTestResult(testDetails.name, testDetails.result, testDetails.summary);
+            logTestDetails(testDetails.result, testDetails.summary);
+            overallTestResults.append(testDetails.toString() + "\n");
         }
-        setOverallTestResult(
-                testClassName,
-                testPassedCounter,
-                testSkippedCounter,
-                testFailedCounter);
+        // log to screen and save the overall test summary (activity level)
+        SensorTestDetails testDetails = getOverallTestDetails();
+        logTestDetails(testDetails.result, testDetails.summary);
+        overallTestResults.append(testDetails.summary);
+        setTestResult(testDetails.name, testDetails.result, overallTestResults.toString());
 
         try {
             activityCleanUp();
         } catch (Throwable e) {
-            appendText("An error occurred on Activity CleanUp.");
-            appendText(e.getLocalizedMessage(), Color.RED);
+            appendText(e.getMessage(), Color.RED);
+            Log.e(LOG_TAG, "An error occurred on Activity CleanUp.", e);
         }
 
-        appendText("\nTest completed. Press 'Next' to finish.\n");
+        appendText(R.string.snsr_test_complete);
         waitForUser();
         finish();
     }
@@ -158,6 +143,17 @@
         mCountDownLatch.countDown();
     }
 
+    private static class SensorTestDetails {
+        public SensorTestResult result;
+        public String name;
+        public String summary;
+
+        @Override
+        public String toString() {
+            return String.format("%s|%s|%s", name, result.name(), summary);
+        }
+    }
+
     protected enum SensorTestResult {
         SKIPPED,
         PASS,
@@ -168,6 +164,22 @@
      * For use only by {@link BaseSensorSemiAutomatedTestActivity} and other base classes.
      */
     protected void setTestResult(String testId, SensorTestResult testResult, String testDetails) {
+        switch(testResult) {
+            case SKIPPED:
+                TestResult.setPassedResult(this, testId, testDetails);
+                break;
+            case PASS:
+                TestResult.setPassedResult(this, testId, testDetails);
+                break;
+            case FAIL:
+                TestResult.setFailedResult(this, testId, testDetails);
+                break;
+            default:
+                throw new InvalidParameterException("Unrecognized testResult.");
+        }
+    }
+
+    private void logTestDetails(SensorTestResult testResult, String testSummary) {
         int textColor;
         int logPriority;
         String testResultString;
@@ -175,29 +187,26 @@
             case SKIPPED:
                 textColor = Color.YELLOW;
                 logPriority = Log.INFO;
-                testResultString = "SKIPPED";
-                TestResult.setPassedResult(this, testId, testDetails);
+                testResultString = getString(R.string.snsr_test_skipped);
                 break;
             case PASS:
                 textColor = Color.GREEN;
                 logPriority = Log.DEBUG;
-                testResultString = "PASS";
-                TestResult.setPassedResult(this, testId, testDetails);
+                testResultString = getString(R.string.snsr_test_pass);
                 break;
             case FAIL:
                 textColor = Color.RED;
                 logPriority = Log.ERROR;
-                testResultString = "FAIL";
-                TestResult.setFailedResult(this, testId, testDetails);
+                testResultString = getString(R.string.snsr_test_fail);
                 break;
             default:
                 throw new InvalidParameterException("Unrecognized testResult.");
         }
-        if (TextUtils.isEmpty(testDetails)) {
-            testDetails = testResultString;
+        if (TextUtils.isEmpty(testSummary)) {
+            testSummary = testResultString;
         }
-        appendText(testDetails, textColor);
-        Log.println(logPriority, LOG_TAG, testDetails);
+        appendText("\n" + testSummary, textColor);
+        Log.println(logPriority, LOG_TAG, testSummary);
     }
 
     /**
@@ -216,10 +225,20 @@
      */
     protected void activityCleanUp() throws Throwable {}
 
+    protected void appendText(int resId, int textColor) {
+        appendText(getString(resId), textColor);
+    }
+
+    @Deprecated
     protected void appendText(String text, int textColor) {
         this.runOnUiThread(new TextAppender(mLogView, text, textColor));
     }
 
+    protected void appendText(int resId) {
+        appendText(getString(resId));
+    }
+
+    @Deprecated
     protected void appendText(String text) {
         this.runOnUiThread(new TextAppender(mLogView, text));
     }
@@ -238,6 +257,7 @@
     }
 
     protected void waitForUser() {
+        appendText(R.string.snsr_wait_for_user);
         updateButton(true);
         try {
             mSemaphore.acquire();
@@ -269,24 +289,60 @@
         return testMethods;
     }
 
-    private void setOverallTestResult(
-            String testClassName,
-            int testPassedCount,
-            int testSkippedCount,
-            int testFailedCount) {
-        SensorTestResult overallTestResult = SensorTestResult.PASS;
-        if (testFailedCount > 0) {
-            overallTestResult = SensorTestResult.FAIL;
-        } else if (testSkippedCount > 0 || testPassedCount == 0) {
-            overallTestResult = SensorTestResult.SKIPPED;
+    private SensorTestDetails executeTest(Method testMethod) {
+        SensorTestDetails testDetails = new SensorTestDetails();
+        testDetails.name = String.format("%s.%s", getTestClassName(), testMethod.getName());
+
+        try {
+            appendText(getString(R.string.snsr_executing_test, testDetails.name));
+            testDetails.summary = (String) testMethod.invoke(this);
+            testDetails.result = SensorTestResult.PASS;
+            ++mTestPassedCounter;
+        } catch (InvocationTargetException e) {
+            // get the inner exception, because we use reflection APIs to execute the test
+            Throwable cause = e.getCause();
+            testDetails.summary = cause.getMessage();
+            if (cause instanceof SensorNotSupportedException) {
+                testDetails.result = SensorTestResult.SKIPPED;
+                ++mTestSkippedCounter;
+            } else {
+                testDetails.result = SensorTestResult.FAIL;
+                ++mTestFailedCounter;
+            }
+        } catch (Throwable e) {
+            testDetails.summary = e.getMessage();
+            testDetails.result = SensorTestResult.FAIL;
+            ++mTestFailedCounter;
         }
 
-        String testSummary = String.format(
-                "\n\nTestsPassed=%d, TestsSkipped=%d, TestFailed=%d",
-                testPassedCount,
-                testSkippedCount,
-                testFailedCount);
-        setTestResult(testClassName, overallTestResult, testSummary);
+        return testDetails;
+    }
+
+    private SensorTestDetails getOverallTestDetails() {
+        SensorTestDetails testDetails = new SensorTestDetails();
+        testDetails.name = getTestClassName();
+
+        testDetails.result = SensorTestResult.PASS;
+        if (mTestFailedCounter > 0) {
+            testDetails.result = SensorTestResult.FAIL;
+        } else if (mTestSkippedCounter > 0 || mTestPassedCounter == 0) {
+            testDetails.result = SensorTestResult.SKIPPED;
+        }
+
+        testDetails.summary = getString(
+                R.string.snsr_test_summary,
+                mTestPassedCounter,
+                mTestSkippedCounter,
+                mTestFailedCounter);
+
+        return testDetails;
+    }
+
+    private String getTestClassName() {
+        if (mTestClass == null) {
+            return "<unknown>";
+        }
+        return mTestClass.getName();
     }
 
     private class TextAppender implements Runnable {
@@ -335,12 +391,12 @@
 
     protected void askToSetAirplaneMode() throws InterruptedException {
         if (isAirplaneModeOn()) {
-            appendText("Airplane mode set.");
+            appendText(R.string.snsr_airplane_mode_set);
+            appendText(R.string.snsr_on_complete_return);
             return;
         }
 
-        appendText("You will be redirected to set 'Airplane Mode' ON, after doing so, go back to " +
-                "this App. Press Next to continue.\n");
+        appendText(R.string.snsr_airplane_mode_request);
         waitForUser();
         launchAndWaitForSubactivity(Settings.ACTION_WIRELESS_SETTINGS);
 
@@ -352,12 +408,11 @@
     protected void askToSetScreenOffTimeout(int timeoutInSec) throws InterruptedException {
         long timeoutInMs = TimeUnit.SECONDS.toMillis(timeoutInSec);
         if (isScreenOffTimeout(timeoutInMs)) {
-            appendText("Screen Off Timeout set to: " + timeoutInSec + " seconds.");
+            appendText(getString(R.string.snsr_screen_off_timeout, timeoutInSec));
             return;
         }
 
-        appendText("You will be redirected to set 'Display Sleep' to " + timeoutInSec + " seconds" +
-                ", after doing so, go back to this App. Press Next to continue.\n");
+        appendText(getString(R.string.snsr_screen_off_request, timeoutInSec));
         waitForUser();
         launchAndWaitForSubactivity(Settings.ACTION_DISPLAY_SETTINGS);
 
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 066bda4..0c6b931 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.sensors;
 
+import com.android.cts.verifier.R;
+
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
@@ -24,45 +26,61 @@
 /**
  * Semi-automated test that focuses on characteristics associated with Accelerometer measurements.
  */
-public class GyroscopeMeasurementTestActivity extends BaseSensorSemiAutomatedTestActivity {
-    @Override
-    protected void onRun() throws Throwable {
-        appendText("Place the device in a flat surface with the screen facing the ceiling, make "
-                + "sure the device aligns with the orientation specified for each scenario. Then "
-                + "follow the instructions for each scenario:");
+public class GyroscopeMeasurementTestActivity extends BaseSensorTestActivity {
+    public GyroscopeMeasurementTestActivity() {
+        super(GyroscopeMeasurementTestActivity.class);
+    }
 
-        verifyMeasurements(
-                "leave the device static",
+    @Override
+    protected void activitySetUp() {
+        appendText(R.string.snsr_gyro_device_placement);
+    }
+
+    public String testDeviceStatic() throws Throwable {
+        return verifyMeasurements(
+                R.string.snsr_gyro_device_static,
                 true /*portrait*/,
                 0, 0, 0);
+    }
 
-        verifyMeasurements(
-                "rotate the device clockwise",
+    public String testRotateClockwise() throws Throwable {
+        return verifyMeasurements(
+                R.string.snsr_gyro_rotate_clockwise,
                 true /*portrait*/,
                 0, 0, -1);
+    }
 
-        verifyMeasurements(
-                "rotate the device counter-clockwise",
+    public String testRotateCounterClockwise() throws Throwable {
+        return verifyMeasurements(
+                R.string.snsr_gyro_rotate_counter_clockwise,
                 true /*portrait*/,
                 0, 0, +1);
+    }
 
-        verifyMeasurements(
-                "rotate the device on its right until it stands on its side",
+    public String testRotateRightSide() throws Throwable {
+        return verifyMeasurements(
+                R.string.snsr_gyro_rotate_right_side,
                 true /*portrait*/,
                 0, +1, 0);
+    }
 
-        verifyMeasurements(
-                "rotate the device on its left until it stands on its side",
+    public String testRotateLeftSide() throws Throwable {
+        return verifyMeasurements(
+                R.string.snsr_gyro_rotate_left_side,
                 true /*portrait*/,
                 0, -1, 0);
+    }
 
-        verifyMeasurements(
-                "rotate the device on its top until it stands perpendicular",
+    public String testRotateTopSide() throws Throwable {
+        return verifyMeasurements(
+                R.string.snsr_gyro_rotate_top_side,
                 false /*portrait*/,
                 -1, 0, 0);
+    }
 
-        verifyMeasurements(
-                "rotate the device on its bottom until it stands perpendicular",
+    public String testRotateBottomSide() throws Throwable {
+        return verifyMeasurements(
+                R.string.snsr_gyro_rotate_bottom_side,
                 false /*portrait*/,
                 +1, 0, 0);
     }
@@ -87,13 +105,16 @@
      * - the values representing the expectation of the test
      * - the mean of values sampled from the sensor
      */
-    private void verifyMeasurements(
-            String scenarioInstructions,
+    private String verifyMeasurements(
+            int scenarioInstructionsResId,
             boolean usePortraitOrientation,
             int ... expectations) throws Throwable {
-        final String orientation = usePortraitOrientation ? "Portrait": "Landscape";
-        appendText(String.format("\n[Device orientation]: %s", orientation));
-        appendText(String.format("Press 'Next' and %s.", scenarioInstructions));
+        if (usePortraitOrientation) {
+            appendText(R.string.snsr_orientation_portrait);
+        } else {
+            appendText(R.string.snsr_orientation_landscape);
+        }
+        appendText(scenarioInstructionsResId);
         waitForUser();
 
         Thread.sleep(500 /*ms*/);
@@ -107,6 +128,6 @@
                 expectations,
                 new float[]{0.2f, 0.2f, 0.2f} /*noiseThreshold*/));
         verifySignum.execute();
-        logSuccess();
+        return null;
     }
 }
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 a131b2b..0177a85 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.sensors;
 
+import com.android.cts.verifier.R;
+
 import android.graphics.Color;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
@@ -33,16 +35,24 @@
  * Also, it is recommended to execute these tests outdoors, or at least far from magnetic
  * disturbances.
  */
-public class MagneticFieldMeasurementTestActivity extends BaseSensorSemiAutomatedTestActivity {
+public class MagneticFieldMeasurementTestActivity extends BaseSensorTestActivity {
+    public MagneticFieldMeasurementTestActivity() {
+        super(MagneticFieldMeasurementTestActivity.class);
+    }
+
     @Override
-    protected void onRun() throws Throwable {
+    public void activitySetUp() {
         calibrateMagnetometer();
+    }
 
-        appendText("Verifying the Norm...");
-        verifyNorm();
+    public String testNorm() throws Throwable {
+        appendText(R.string.snsr_mag_verify_norm);
+        return verifyNorm();
+    }
 
-        appendText("\nVerifying the Standard Deviation...");
-        verifyStandardDeviation();
+    public String testStandardDeviation() throws Throwable {
+        appendText(R.string.snsr_mag_verify_std_dev);
+        return verifyStandardDeviation();
     }
 
     private void calibrateMagnetometer() {
@@ -51,11 +61,12 @@
             public void onSensorChanged(SensorEvent event) {
                 float values[] = event.values;
                 clearText();
-                appendText("Please calibrate the Magnetometer by moving it in 8 shapes in "
-                        + "different orientations.");
+                appendText(R.string.snsr_mag_calibration_description);
                 appendText(String.format("->  (%.2f, %.2f, %.2f) uT", values[0], values[1],
                         values[2]), Color.GRAY);
-                appendText("Then leave the device in a flat surface and press Next...\n");
+
+                // TODO: automate finding out when the magnetometer is calibrated
+                appendText(R.string.snsr_mag_calibration_complete);
             }
 
             @Override
@@ -96,7 +107,7 @@
      * - the values representing the expectation of the test
      * - the values sampled from the sensor
      */
-    private void verifyNorm() throws Throwable {
+    private String verifyNorm() throws Throwable {
         float expectedMagneticFieldEarth =
                 (SensorManager.MAGNETIC_FIELD_EARTH_MAX + SensorManager.MAGNETIC_FIELD_EARTH_MIN) / 2;
         float magneticFieldEarthThreshold =
@@ -111,7 +122,7 @@
                 expectedMagneticFieldEarth,
                 magneticFieldEarthThreshold));
         verifyNorm.execute();
-        logSuccess();
+        return null;
     }
 
     /**
@@ -137,7 +148,7 @@
      * Additionally, the device's debug output (adb logcat) dumps the set of values associated with
      * the failure to help track down the issue.
      */
-    private void verifyStandardDeviation() throws Throwable {
+    private String verifyStandardDeviation() throws Throwable {
         TestSensorOperation verifyStdDev = new TestSensorOperation(
                 this.getApplicationContext(),
                 Sensor.TYPE_MAGNETIC_FIELD,
@@ -147,6 +158,6 @@
         verifyStdDev.addVerification(new StandardDeviationVerification(
                 new float[]{2f, 2f, 2f} /* uT */));
         verifyStdDev.execute();
-        logSuccess();
+        return null;
     }
 }