Adding an improved version of the Accelerometer tester, which works more
reliably. Also a first cut at a Magnetometer tester, but this requires
real-world testing to make sure it actually works.

Change-Id: I1e0f3ac1e25e8bdd247f9dea253d8bc2a59c673c
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index dbdc952..605404a 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -52,5 +52,13 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".sensors.MagnetometerTestActivity" android:label="@string/snsr_mag_test"
+        android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest> 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestActivity.java
index 4de1465..85e7c70 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestActivity.java
@@ -17,28 +17,41 @@
 package com.android.cts.verifier.sensors;
 
 import android.app.Activity;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.opengl.GLSurfaceView;
 import android.os.Bundle;
 
 /**
  * CTS Verifier case for verifying correct integration of accelerometer.
- * Displays a wedge using OpenGL that, on a correctly-integrated device,
- * always points down.
+ * Displays a wedge using OpenGL that, on a correctly-integrated device, always
+ * points down.
  */
 public class AccelerometerTestActivity extends Activity {
     private GLSurfaceView mGLSurfaceView;
 
+    private SensorManager mSensorManager;
+
+    private SensorEventListener mListener;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mSensorManager = (SensorManager) getApplicationContext().getSystemService(
+                Context.SENSOR_SERVICE);
         mGLSurfaceView = new GLSurfaceView(this);
-        mGLSurfaceView.setRenderer(new AccelerometerTestRenderer(this));
+        AccelerometerTestRenderer renderer = new AccelerometerTestRenderer(this);
+        mListener = renderer;
+        mGLSurfaceView.setRenderer(renderer);
         setContentView(mGLSurfaceView);
     }
 
     @Override
     protected void onPause() {
         super.onPause();
+        mSensorManager.unregisterListener(mListener);
         mGLSurfaceView.onPause();
     }
 
@@ -46,5 +59,7 @@
     protected void onResume() {
         super.onResume();
         mGLSurfaceView.onResume();
+        mSensorManager.registerListener(mListener, mSensorManager.getSensorList(
+                Sensor.TYPE_ACCELEROMETER).get(0), SensorManager.SENSOR_DELAY_UI);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestRenderer.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestRenderer.java
index f314b63..5264cbb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestRenderer.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestRenderer.java
@@ -32,7 +32,6 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
 import android.opengl.GLSurfaceView;
 import android.opengl.GLU;
 import android.opengl.GLUtils;
@@ -81,19 +80,19 @@
             mIndexBuffer = ibb.asShortBuffer();
 
             /**
-             * Coordinates of the vertices making up a simple wedge.
-             * Six total vertices, representing two isosceles triangles, side by side,
-             * centered on the origin separated by 0.25 units, with elongated ends pointing down
-             * the negative Z axis.
+             * Coordinates of the vertices making up a simple wedge. Six total
+             * vertices, representing two isosceles triangles, side by side,
+             * centered on the origin separated by 0.25 units, with elongated
+             * ends pointing down the negative Z axis.
              */
             float[] coords = {
-                // X, Y, Z
-                -0.125f, -0.25f, -0.25f,
-                -0.125f,  0.25f, -0.25f,
-                -0.125f,  0.0f,   0.559016994f,
-                 0.125f, -0.25f, -0.25f,
-                 0.125f,  0.25f, -0.25f,
-                 0.125f,  0.0f,   0.559016994f,
+                    // X, Y, Z
+                    -0.125f, -0.25f, -0.25f,
+                    -0.125f,  0.25f, -0.25f,
+                    -0.125f,  0.0f,   0.559016994f,
+                     0.125f, -0.25f, -0.25f,
+                     0.125f,  0.25f, -0.25f,
+                     0.125f,  0.0f,   0.559016994f,
             };
 
             for (int i = 0; i < VERTS; i++) {
@@ -157,55 +156,76 @@
     }
 
     /**
-     * Device's current rotation angle around X axis.
+     * A representation of the Z-axis in vector form.
      */
-    private float mAngleX;
+    private static final float[] Z_AXIS = new float[] {
+            0, 0, 1
+    };
 
     /**
-     * Device's current rotation angle around Y axis.
+     * Computes the cross product of two vectors, storing the resulting
+     * pseudovector in out. All arrays must be length 3 or more, and out is
+     * overwritten.
+     * 
+     * @param left the left operand of the cross product
+     * @param right the right operand of the cross product
+     * @param out the array into which to store the cross-product pseudovector's
+     *            data
      */
-    private float mAngleY;
+    public static void crossProduct(float[] left, float[] right, float[] out) {
+        out[0] = left[1] * right[2] - left[2] * right[1];
+        out[1] = left[2] * right[0] - left[0] * right[2];
+        out[2] = left[0] * right[1] - left[1] * right[0];
+    }
 
     /**
-     * Device's current rotation angle around Z axis.
+     * Computes the dot product of two vectors.
+     * 
+     * @param left the first dot product operand
+     * @param right the second dot product operand
+     * @return the dot product of left and right
      */
-    private float mAngleZ;
+    public static float dotProduct(float[] left, float[] right) {
+        return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
+    }
+
+    /**
+     * Normalizes the input vector into a unit vector.
+     * 
+     * @param vector the vector to normalize. Contents are overwritten.
+     */
+    public static void normalize(float[] vector) {
+        double mag = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2]
+                * vector[2]);
+        vector[0] /= mag;
+        vector[1] /= mag;
+        vector[2] /= mag;
+    }
+
+    /**
+     * The angle around mCrossProd to rotate to align Z-axis with gravity.
+     */
+    protected float mAngle;
 
     private Context mContext;
 
     /**
-     * Animation's current rotation angle around X axis.
+     * The (pseudo)vector around which to rotate to align Z-axis with gravity.
      */
-    private float mCurAngleX;
-
-    /**
-     * Animation's current rotation angle around Y axis.
-     */
-    private float mCurAngleY;
-
-    /**
-     * Animation's current rotation angle around Z axis.
-     */
-    private float mCurAngleZ;
-
-    private SensorManager mSensorManager;
+    protected float[] mCrossProd = new float[3];
 
     private int mTextureID;
 
     private Wedge mWedge;
 
     /**
-     * Registers with the SensorManager for accelerometer data, and sets up the
-     * Triangle to draw.
+     * It's a constructor. Can you dig it?
      * 
      * @param context the Android Context that owns this renderer
      */
     public AccelerometerTestRenderer(Context context) {
         mContext = context;
         mWedge = new Wedge();
-        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
-        mSensorManager.registerListener(this, mSensorManager.getSensorList(
-                Sensor.TYPE_ACCELEROMETER).get(0), SensorManager.SENSOR_DELAY_UI);
     }
 
     public void onAccuracyChanged(Sensor arg0, int arg1) {
@@ -216,15 +236,8 @@
      * Actually draws the wedge.
      */
     public void onDrawFrame(GL10 gl) {
-        // initial texture setup
-        gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);
-
-        // clear the screen and prepare to draw
-        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
-        gl.glMatrixMode(GL10.GL_MODELVIEW);
-        gl.glLoadIdentity();
-
         // set up the texture for drawing
+        gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);
         gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
         gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
         gl.glActiveTexture(GL10.GL_TEXTURE0);
@@ -232,37 +245,31 @@
         gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
         gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
 
-        // back up the Z axis (out of the screen) a bit, and look down at the
-        // wedge
-        GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
-
-        /*
-         * mCurAngle is used to animate the motion of the wedge toward the
-         * physical target rotation angle. Each frame moves the angle half the
-         * distance to where the accelerometer tells us it should be. We do this
-         * as a crude way to smooth out the animation a little so that the wedge
-         * isn't quite so jumpy in response to accelerometer noise. Looking at
-         * that was making me a little motion sick.
-         */
-        mCurAngleX += (mAngleX - mCurAngleX) / 2;
-        mCurAngleY += (mAngleY - mCurAngleY) / 2;
-        mCurAngleZ += (mAngleZ - mCurAngleZ) / 2;
-        gl.glRotatef(mCurAngleX * 180 / -(float) Math.PI, 1.0f, 0.0f, 0.0f);
-        gl.glRotatef(mCurAngleY * 180 / -(float) Math.PI, 0.0f, 1.0f, 0.0f);
-        gl.glRotatef(mCurAngleZ * 180 / -(float) Math.PI, 0.0f, 0.0f, 1.0f);
-
+        // clear the screen and draw
+        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+        gl.glMatrixMode(GL10.GL_MODELVIEW);
+        gl.glLoadIdentity();
+        gl.glRotatef(-mAngle * 180 / (float) Math.PI, mCrossProd[0], mCrossProd[1], mCrossProd[2]);
         mWedge.draw(gl);
     }
 
     public void onSensorChanged(SensorEvent event) {
         if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
             /*
-             * for this test we want *only* accelerometer data, so we can't use
-             * the convenience methods on SensorManager; so compute manually
+             * For this test we want *only* accelerometer data, so we can't use
+             * the convenience methods on SensorManager; so compute manually.
              */
-            mAngleX = (float) Math.atan2(event.values[2], event.values[1]) - (float) Math.PI / 2;
-            mAngleY = (float) Math.atan2(event.values[2], event.values[0]) - (float) Math.PI / 2;
-            mAngleZ = (float) Math.atan2(event.values[1], event.values[0]) - (float) Math.PI / 2;
+            normalize(event.values);
+
+            /*
+             * TODO: DIRTY ROTTEN KLUDGE. I don't know why this works or why
+             * it's necessary, but if you don't do this, 2 of the axes don't
+             * rotate correctly. I suspect I have handedness wrong, somewhere.
+             */
+            event.values[1] *= -1;
+
+            crossProduct(event.values, Z_AXIS, mCrossProd);
+            mAngle = (float) Math.acos(dotProduct(event.values, Z_AXIS));
         }
     }
 
@@ -272,7 +279,7 @@
         gl.glMatrixMode(GL10.GL_PROJECTION);
         gl.glLoadIdentity();
         gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
-
+        GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
     }
 
     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
@@ -285,16 +292,13 @@
         // create the texture we use on the wedge
         int[] textures = new int[1];
         gl.glGenTextures(1, textures, 0);
-
         mTextureID = textures[0];
+        
         gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
-
         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
-
         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
-
         gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE);
 
         InputStream is = mContext.getResources().openRawResource(R.raw.sns_texture);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagnetometerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagnetometerTestActivity.java
new file mode 100644
index 0000000..1ca5203
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagnetometerTestActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 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 com.android.cts.verifier.sensors;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+
+/**
+ * CTS Verifier case for verifying correct integration of accelerometer.
+ * Displays a wedge using OpenGL that, on a correctly-integrated device, always
+ * points down.
+ */
+public class MagnetometerTestActivity extends Activity {
+    private GLSurfaceView mGLSurfaceView;
+
+    private AccelerometerTestRenderer mListener;
+
+    private SensorManager mSensorManager;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSensorManager = (SensorManager) getApplicationContext().getSystemService(
+                Context.SENSOR_SERVICE);
+        mGLSurfaceView = new GLSurfaceView(this);
+        AccelerometerTestRenderer renderer = new MagnetometerTestRenderer(this);
+        mListener = renderer;
+        mGLSurfaceView.setRenderer(renderer);
+        setContentView(mGLSurfaceView);
+    }
+
+    protected void onPause() {
+        super.onPause();
+        mSensorManager.unregisterListener(mListener);
+        mGLSurfaceView.onPause();
+    }
+
+    protected void onResume() {
+        super.onResume();
+        mSensorManager.registerListener(mListener, mSensorManager.getSensorList(
+                Sensor.TYPE_MAGNETIC_FIELD).get(0), SensorManager.SENSOR_DELAY_UI);
+        mGLSurfaceView.onResume();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagnetometerTestRenderer.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagnetometerTestRenderer.java
new file mode 100644
index 0000000..854e936
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagnetometerTestRenderer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 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 com.android.cts.verifier.sensors;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+
+public class MagnetometerTestRenderer extends AccelerometerTestRenderer {
+    public MagnetometerTestRenderer(Context context) {
+        super(context);
+    }
+
+    private static final float[] Y_AXIS = new float[] {
+            0, 1, 0
+    };
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
+            /*
+             * for this test we want *only* magnetometer data, so we can't use
+             * the convenience methods on SensorManager; so compute manually
+             */
+            normalize(event.values);
+            
+            crossProduct(event.values, Y_AXIS, mCrossProd);
+            mAngle = (float) Math.acos(dotProduct(event.values, Y_AXIS));
+        }
+    }
+}