Adding a CTS Verifier activity to summarize features reported by the device.
Can be used to sanity-check reported features at a glance.

Change-Id: I6240e3833eb24a77a3e92dd3997e57f08b2dbb4c
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index ebd0114..dbdc952 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -38,6 +38,19 @@
             </intent-filter>
         </activity>
 
-    </application>
+        <activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+        </activity>
 
-</manifest> 
\ No newline at end of file
+        <activity android:name=".sensors.AccelerometerTestActivity" android:label="@string/snsr_accel_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/res/drawable-mdpi/fs_error.png b/apps/CtsVerifier/res/drawable-mdpi/fs_error.png
new file mode 100644
index 0000000..8270104
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/fs_error.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/fs_good.png b/apps/CtsVerifier/res/drawable-mdpi/fs_good.png
new file mode 100644
index 0000000..7786ac7
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/fs_good.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/fs_indeterminate.png b/apps/CtsVerifier/res/drawable-mdpi/fs_indeterminate.png
new file mode 100644
index 0000000..68f51ba
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/fs_indeterminate.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/fs_warning.png b/apps/CtsVerifier/res/drawable-mdpi/fs_warning.png
new file mode 100644
index 0000000..12baca5
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/fs_warning.png
Binary files differ
diff --git a/apps/CtsVerifier/res/layout/fs_main.xml b/apps/CtsVerifier/res/layout/fs_main.xml
new file mode 100644
index 0000000..3ba7e24
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/fs_main.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+         android:orientation="vertical"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent">
+
+     <TextView android:id="@+id/fs_warnings"
+               android:layout_width="wrap_content"
+               android:layout_height="wrap_content"
+               android:text="@string/empty"/>
+                           
+     <ListView android:id="@id/android:list"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="#000000"
+               android:layout_weight="1"
+               android:drawSelectorOnTop="false"/>
+
+     <TextView android:id="@id/android:empty"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="#000000"
+               android:text="@string/fs_no_data"/>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/fs_row.xml b/apps/CtsVerifier/res/layout/fs_row.xml
new file mode 100644
index 0000000..e1b26eb
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/fs_row.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="match_parent"
+     android:layout_height="wrap_content"
+     android:orientation="horizontal">
+
+     <ImageView android:id="@+id/fs_icon"
+         android:layout_width="wrap_content"
+         android:layout_height="wrap_content"/>
+
+     <TextView android:id="@+id/fs_feature"
+         android:textSize="14sp"
+         android:layout_width="wrap_content"
+         android:layout_height="wrap_content"/>
+ </LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/raw/sns_texture.png b/apps/CtsVerifier/res/raw/sns_texture.png
new file mode 100644
index 0000000..7e3b9a1
--- /dev/null
+++ b/apps/CtsVerifier/res/raw/sns_texture.png
Binary files differ
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 9c1399d..57c0b81 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -19,4 +19,15 @@
     <string name="continue_button_text">Continue</string>
     <string name="test_list_title">Manual Test List</string>
     <string name="suid_binaries">SUID Binaries</string>
+
+    <!-- strings for FeatureSummaryActivity -->
+    <string name="feature_summary">Hardware/Software Feature Summary</string>
+    <string name="fs_disallowed">WARNING: device reports a disallowed feature name</string>
+    <string name="fs_missing_wifi_telephony">WARNING: device reports neither WiFi nor telephony</string>
+    <string name="fs_no_data">No data.</string>
+    <string name="empty"></string>
+
+    <!-- strings for AccelerometerTestActivity and MagnetometerTestActivity -->
+    <string name="snsr_accel_test">Accelerometer Test</string>
+    <string name="snsr_mag_test">Magnetometer Test</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 7bd3488..143f44d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -50,7 +50,6 @@
     @Override
     protected void onListItemClick(ListView listView, View view, int position, long id) {
         super.onListItemClick(listView, view, position, id);
-        ListAdapter adapter = getListAdapter();
         Intent intent = getIntent(position);
         startActivity(intent);
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
new file mode 100644
index 0000000..d448616
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+/*
+ * This file references fs_error.png, fs_good.png, fs_indeterminate.png,
+ * and fs_warning.png which are licensed under Creative Commons 3.0
+ * by fatcow.com.
+ * http://www.fatcow.com/free-icons/
+ * http://creativecommons.org/licenses/by/3.0/us/
+ */
+package com.android.cts.verifier.features;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+
+import com.android.cts.verifier.R;
+
+import android.app.ListActivity;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+
+public class FeatureSummaryActivity extends ListActivity {
+    /**
+     * Simple storage class for data about an Android feature.
+     */
+    static class Feature {
+        /**
+         * The name of the feature. Should be one of the PackageManager.FEATURE*
+         * constants.
+         */
+        public String name;
+
+        /**
+         * Indicates whether the field is present on the current device.
+         */
+        public boolean present;
+
+        /**
+         * Indicates whether the field is required for the current device.
+         */
+        public boolean required;
+
+        /**
+         * Constructor does not include 'present' because that's a detected
+         * value, and not set during creation.
+         * 
+         * @param name value for this.name
+         * @param required value for this.required
+         */
+        public Feature(String name, boolean required) {
+            this.name = name;
+            this.required = required;
+            this.present = false;
+        }
+    }
+
+    /**
+     * A list of all known features. If a constant is added to PackageManager,
+     * this list needs to be updated. We could detect these fields via
+     * Reflection, but we can't determine whether the features are required or
+     * not that way, so we need this block anyway.
+     */
+    public static final Feature[] ALL_FEATURES = {
+            new Feature(PackageManager.FEATURE_BLUETOOTH, true),
+            new Feature(PackageManager.FEATURE_CAMERA, true),
+            new Feature(PackageManager.FEATURE_CAMERA_AUTOFOCUS, false),
+            new Feature(PackageManager.FEATURE_CAMERA_FLASH, false),
+            new Feature(PackageManager.FEATURE_LIVE_WALLPAPER, false),
+            new Feature(PackageManager.FEATURE_LOCATION, true),
+            new Feature(PackageManager.FEATURE_LOCATION_GPS, true),
+            new Feature(PackageManager.FEATURE_LOCATION_NETWORK, true),
+            new Feature(PackageManager.FEATURE_MICROPHONE, true),
+            new Feature(PackageManager.FEATURE_SENSOR_ACCELEROMETER, true),
+            new Feature(PackageManager.FEATURE_SENSOR_COMPASS, true),
+            new Feature(PackageManager.FEATURE_SENSOR_LIGHT, false),
+            new Feature(PackageManager.FEATURE_SENSOR_PROXIMITY, false),
+            new Feature(PackageManager.FEATURE_TELEPHONY, false),
+            new Feature(PackageManager.FEATURE_TELEPHONY_CDMA, false),
+            new Feature(PackageManager.FEATURE_TELEPHONY_GSM, false),
+            new Feature(PackageManager.FEATURE_TOUCHSCREEN, true),
+            new Feature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH, false),
+            new Feature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT, false),
+            new Feature(PackageManager.FEATURE_WIFI, false),
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fs_main);
+
+        // some values used to detect warn-able conditions involving multiple features
+        boolean hasWifi = false;
+        boolean hasTelephony = false;
+        boolean hasIllegalFeature = false;
+
+        // get list of all features device thinks it has, & store in a HashMap for fast lookups
+        HashMap<String, String> actualFeatures = new HashMap<String, String>();
+        for (FeatureInfo fi : getPackageManager().getSystemAvailableFeatures()) {
+            actualFeatures.put(fi.name, fi.name);
+        }
+
+        // data structure that the SimpleAdapter will use to populate ListView
+        ArrayList<HashMap<String, Object>> listViewData = new ArrayList<HashMap<String, Object>>();
+
+        // roll over all known features & check whether device reports them
+        boolean present = false;
+        int statusIcon;
+        for (Feature f : ALL_FEATURES) {
+            HashMap<String, Object> row = new HashMap<String, Object>();
+            listViewData.add(row);
+            present = actualFeatures.containsKey(f.name);
+            if (present) {
+                // device reports it -- yay! set the happy icon
+                hasWifi = hasWifi || PackageManager.FEATURE_WIFI.equals(f.name);
+                hasTelephony = hasTelephony || PackageManager.FEATURE_TELEPHONY.equals(f.name);
+                statusIcon = R.drawable.fs_good;
+                actualFeatures.remove(f.name);
+            } else if (!present && f.required) {
+                // it's required, but device doesn't report it. Boo, set the bogus icon
+                statusIcon = R.drawable.fs_error;
+            } else {
+                // device doesn't report it, but it's not req'd, so can't tell if there's a problem
+                statusIcon = R.drawable.fs_indeterminate;
+            }
+            row.put("feature", f.name);
+            row.put("icon", statusIcon);
+        }
+
+        // now roll over any remaining features (which are non-standard)
+        for (String feature : actualFeatures.keySet()) {
+            if (feature == null || "".equals(feature))
+                continue;
+            HashMap<String, Object> row = new HashMap<String, Object>();
+            listViewData.add(row);
+            row.put("feature", feature);
+            if (feature.startsWith("android")) { // intentionally not "android."
+                // sorry, you're not allowed to squat in the official namespace; set bogus icon
+                row.put("icon", R.drawable.fs_error);
+                hasIllegalFeature = true;
+            } else {
+                // non-standard features are okay, but flag them just in case
+                row.put("icon", R.drawable.fs_warning);
+            }
+        }
+
+        // sort the ListView's data to group by icon type, for easier reading by humans
+        final HashMap<Integer, Integer> idMap = new HashMap<Integer, Integer>();
+        idMap.put(R.drawable.fs_error, 0);
+        idMap.put(R.drawable.fs_warning, 1);
+        idMap.put(R.drawable.fs_indeterminate, 2);
+        idMap.put(R.drawable.fs_good, 3);
+        Collections.sort(listViewData, new Comparator<HashMap<String, Object>>() {
+            public int compare(HashMap<String, Object> left, HashMap<String, Object> right) {
+                int leftId = idMap.get((Integer) (left.get("icon")));
+                int rightId = idMap.get((Integer) (right.get("icon")));
+                if (leftId == rightId) {
+                    return ((String) left.get("feature")).compareTo((String) right.get("feature"));
+                }
+                if (leftId < rightId)
+                    return -1;
+                return 1;
+            }
+        });
+
+        // Set up the SimpleAdapter used to populate the ListView
+        SimpleAdapter adapter = new SimpleAdapter(this, listViewData, R.layout.fs_row,
+            new String[] { "feature", "icon" },
+            new int[] { R.id.fs_feature, R.id.fs_icon });
+        adapter.setViewBinder(new SimpleAdapter.ViewBinder() {
+            public boolean setViewValue(View view, Object data, String repr) {
+                try {
+                    if (view instanceof ImageView) {
+                        ((ImageView) view).setImageResource((Integer) data);
+                    } else if (view instanceof TextView) {
+                        ((TextView) view).setText((String) data);
+                    } else {
+                        return false;
+                    }
+                    return true;
+                } catch (ClassCastException e) {
+                    return false;
+                }
+            }
+        });
+        setListAdapter(adapter);
+
+        // finally, check for our second-order error cases and set warning text if necessary
+        StringBuffer sb = new StringBuffer();
+        if (hasIllegalFeature) {
+            sb.append(getResources().getString(R.string.fs_disallowed)).append("\n");
+        }
+        if (!hasWifi && !hasTelephony) {
+            sb.append(getResources().getString(R.string.fs_missing_wifi_telephony)).append("\n");
+        }
+        ((TextView) (findViewById(R.id.fs_warnings))).setText(sb.toString());
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestActivity.java
new file mode 100644
index 0000000..4de1465
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestActivity.java
@@ -0,0 +1,50 @@
+/*
+ * 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.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 AccelerometerTestActivity extends Activity {
+    private GLSurfaceView mGLSurfaceView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mGLSurfaceView = new GLSurfaceView(this);
+        mGLSurfaceView.setRenderer(new AccelerometerTestRenderer(this));
+        setContentView(mGLSurfaceView);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mGLSurfaceView.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mGLSurfaceView.onResume();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestRenderer.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestRenderer.java
new file mode 100644
index 0000000..d41059f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerTestRenderer.java
@@ -0,0 +1,317 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+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;
+
+import com.android.cts.verifier.R;
+
+public class AccelerometerTestRenderer implements GLSurfaceView.Renderer, SensorEventListener {
+
+    /**
+     * A representation of a 3D triangular wedge or arrowhead shape, suitable
+     * for pointing a direction.
+     */
+    private static class Wedge {
+        private final static int VERTS = 6;
+
+        /**
+         * Storage for the vertices.
+         */
+        private FloatBuffer mFVertexBuffer;
+
+        /**
+         * Storage for the drawing sequence of the vertices. This contains
+         * integer indices into the mFVertextBuffer structure.
+         */
+        private ShortBuffer mIndexBuffer;
+
+        /**
+         * Storage for the texture used on the surface of the wedge.
+         */
+        private FloatBuffer mTexBuffer;
+
+        public Wedge() {
+            // Buffers to be passed to gl*Pointer() functions
+            // must be direct & use native ordering
+
+            ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 6 * 4);
+            vbb.order(ByteOrder.nativeOrder());
+            mFVertexBuffer = vbb.asFloatBuffer();
+
+            ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4);
+            tbb.order(ByteOrder.nativeOrder());
+            mTexBuffer = tbb.asFloatBuffer();
+
+            ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 8 * 2);
+            ibb.order(ByteOrder.nativeOrder());
+            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.
+             */
+            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,
+            };
+
+            for (int i = 0; i < VERTS; i++) {
+                for (int j = 0; j < 3; j++) {
+                    mFVertexBuffer.put(coords[i * 3 + j] * 2.0f);
+                }
+            }
+
+            for (int i = 0; i < VERTS; i++) {
+                for (int j = 0; j < 2; j++) {
+                    mTexBuffer.put(coords[i * 3 + j] * 2.0f + 0.5f);
+                }
+            }
+
+            // left face
+            mIndexBuffer.put((short) 0);
+            mIndexBuffer.put((short) 1);
+            mIndexBuffer.put((short) 2);
+
+            // right face
+            mIndexBuffer.put((short) 5);
+            mIndexBuffer.put((short) 4);
+            mIndexBuffer.put((short) 3);
+
+            // top side, 2 triangles to make rect
+            mIndexBuffer.put((short) 2);
+            mIndexBuffer.put((short) 5);
+            mIndexBuffer.put((short) 3);
+            mIndexBuffer.put((short) 3);
+            mIndexBuffer.put((short) 0);
+            mIndexBuffer.put((short) 2);
+
+            // bottom side, 2 triangles to make rect
+            mIndexBuffer.put((short) 5);
+            mIndexBuffer.put((short) 2);
+            mIndexBuffer.put((short) 1);
+            mIndexBuffer.put((short) 1);
+            mIndexBuffer.put((short) 4);
+            mIndexBuffer.put((short) 5);
+
+            // base, 2 triangles to make rect
+            mIndexBuffer.put((short) 0);
+            mIndexBuffer.put((short) 3);
+            mIndexBuffer.put((short) 4);
+            mIndexBuffer.put((short) 4);
+            mIndexBuffer.put((short) 1);
+            mIndexBuffer.put((short) 0);
+
+            mFVertexBuffer.position(0);
+            mTexBuffer.position(0);
+            mIndexBuffer.position(0);
+        }
+
+        public void draw(GL10 gl) {
+            gl.glFrontFace(GL10.GL_CCW);
+            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
+            gl.glEnable(GL10.GL_TEXTURE_2D);
+            gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer);
+            gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 24, GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
+        }
+    }
+
+    /**
+     * Device's current rotation angle around X axis.
+     */
+    private float mAngleX;
+
+    /**
+     * Device's current rotation angle around Y axis.
+     */
+    private float mAngleY;
+
+    /**
+     * Device's current rotation angle around Z axis.
+     */
+    private float mAngleZ;
+
+    private Context mContext;
+
+    /**
+     * Animation's current rotation angle around X axis.
+     */
+    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;
+
+    private int mTextureID;
+
+    private Wedge mWedge;
+
+    /**
+     * Registers with the SensorManager for accelerometer data, and sets up the
+     * Triangle to draw.
+     * 
+     * @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);
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor arg0, int arg1) {
+        // no-op
+    }
+
+    /**
+     * 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.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+        gl.glActiveTexture(GL10.GL_TEXTURE0);
+        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
+        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);
+
+        mWedge.draw(gl);
+    }
+
+    @Override
+    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
+             */
+            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;
+        }
+    }
+
+    public void onSurfaceChanged(GL10 gl, int w, int h) {
+        gl.glViewport(0, 0, w, h);
+        float ratio = (float) w / h;
+        gl.glMatrixMode(GL10.GL_PROJECTION);
+        gl.glLoadIdentity();
+        gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
+
+    }
+
+    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        // set up general OpenGL config
+        gl.glClearColor(0.6f, 0f, 0.4f, 1); // a nice purpley magenta
+        gl.glShadeModel(GL10.GL_SMOOTH);
+        gl.glEnable(GL10.GL_DEPTH_TEST);
+        gl.glEnable(GL10.GL_TEXTURE_2D);
+
+        // 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);
+        Bitmap bitmap;
+        try {
+            bitmap = BitmapFactory.decodeStream(is);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+                // Ignore.
+            }
+        }
+
+        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
+        bitmap.recycle();
+    }
+}