Merge "Porting Unsubmitted Cupcake CookieSyncManagerTest" into eclair
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 4bf8239..6eb1302 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -903,6 +903,9 @@
                        android:resource="@xml/authenticator" />
         </service>
 
+        <activity android:name="android.opengl.cts.GLSurfaceViewStubActivity"
+                  android:label="GLSurfaceViewStub"/>
+
     </application>
 
     <!--Test for PackageManager, please put this at the very beginning-->
diff --git a/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java b/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
index 4ca1cc2..5ac7251 100644
--- a/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
+++ b/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
@@ -16,30 +16,26 @@
 
 package android.test;
 
-import com.android.internal.util.Predicate;
-import com.android.internal.util.Predicates;
-
-import dalvik.annotation.BrokenTest;
-import dalvik.annotation.SideEffect;
-
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.test.suitebuilder.TestMethod;
-import android.test.suitebuilder.annotation.HasAnnotation;
-import android.util.Log;
-
 import java.io.File;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.List;
 import java.util.TimeZone;
 
+import com.android.internal.util.Predicate;
+import com.android.internal.util.Predicates;
+
+import dalvik.annotation.BrokenTest;
+import dalvik.annotation.SideEffect;
+
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestListener;
+import android.os.Bundle;
+import android.test.suitebuilder.TestMethod;
+import android.test.suitebuilder.annotation.HasAnnotation;
+import android.util.Log;
 
 /**
  * This test runner extends the default InstrumentationTestRunner. It overrides
@@ -56,50 +52,37 @@
 
     /**
      * Convenience definition of our log tag.
-     */
+     */ 
     private static final String TAG = "InstrumentationCtsTestRunner";
-
+    
     /**
      * True if (and only if) we are running in single-test mode (as opposed to
      * batch mode).
      */
     private boolean singleTest = false;
-
+    
     @Override
     public void onCreate(Bundle arguments) {
         // We might want to move this to /sdcard, if is is mounted/writable.
         File cacheDir = getTargetContext().getCacheDir();
 
-        // Set some properties that the core tests absolutely need.
+        // Set some properties that the core tests absolutely need. 
         System.setProperty("user.language", "en");
         System.setProperty("user.region", "US");
-
+        
         System.setProperty("java.home", cacheDir.getAbsolutePath());
         System.setProperty("user.home", cacheDir.getAbsolutePath());
         System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
         System.setProperty("javax.net.ssl.trustStore",
                 "/etc/security/cacerts.bks");
-
+        
         TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
-
+        
         if (arguments != null) {
             String classArg = arguments.getString(ARGUMENT_TEST_CLASS);
-            singleTest = classArg != null && classArg.contains("#");
+            singleTest = classArg != null && classArg.contains("#"); 
         }
-
-        // attempt to disable keyguard,  if current test has permission to do so
-        // TODO: move this to a better place, such as InstrumentationTestRunner ?
-        if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
-                == PackageManager.PERMISSION_GRANTED) {
-            Log.i(TAG, "Disabling keyguard");
-            KeyguardManager keyguardManager =
-                (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
-            keyguardManager.newKeyguardLock("cts").disableKeyguard();
-        } else {
-            Log.i(TAG, "Test lacks permission to disable keyguard. " +
-                    "UI based tests may fail if keyguard is up");
-        }
-
+        
         super.onCreate(arguments);
     }
 
@@ -109,36 +92,36 @@
 
         runner.addTestListener(new TestListener() {
             /**
-             * The last test class we executed code from.
+             * The last test class we executed code from.  
              */
             private Class<?> lastClass;
-
+            
             /**
              * The minimum time we expect a test to take.
              */
             private static final int MINIMUM_TIME = 100;
-
+            
             /**
              * The start time of our current test in System.currentTimeMillis().
              */
             private long startTime;
-
+            
             public void startTest(Test test) {
                 if (test.getClass() != lastClass) {
                     lastClass = test.getClass();
                     printMemory(test.getClass());
                 }
-
+                
                 Thread.currentThread().setContextClassLoader(
                         test.getClass().getClassLoader());
-
+                
                 startTime = System.currentTimeMillis();
             }
-
+            
             public void endTest(Test test) {
                 if (test instanceof TestCase) {
                     cleanup((TestCase)test);
-
+                    
                     /*
                      * Make sure all tests take at least MINIMUM_TIME to
                      * complete. If they don't, we wait a bit. The Cupcake
@@ -146,7 +129,7 @@
                      * short time, which causes headache for the CTS.
                      */
                     long timeTaken = System.currentTimeMillis() - startTime;
-
+                    
                     if (timeTaken < MINIMUM_TIME) {
                         try {
                             Thread.sleep(MINIMUM_TIME - timeTaken);
@@ -156,15 +139,15 @@
                     }
                 }
             }
-
+            
             public void addError(Test test, Throwable t) {
                 // This space intentionally left blank.
             }
-
+            
             public void addFailure(Test test, AssertionFailedError t) {
                 // This space intentionally left blank.
             }
-
+            
             /**
              * Dumps some memory info.
              */
@@ -174,7 +157,7 @@
                 long total = runtime.totalMemory();
                 long free = runtime.freeMemory();
                 long used = total - free;
-
+                
                 Log.d(TAG, "Total memory  : " + total);
                 Log.d(TAG, "Used memory   : " + used);
                 Log.d(TAG, "Free memory   : " + free);
@@ -190,7 +173,7 @@
              */
             private void cleanup(TestCase test) {
                 Class<?> clazz = test.getClass();
-
+                
                 while (clazz != TestCase.class) {
                     Field[] fields = clazz.getDeclaredFields();
                     for (int i = 0; i < fields.length; i++) {
@@ -205,15 +188,15 @@
                             }
                         }
                     }
-
+                    
                     clazz = clazz.getSuperclass();
                 }
             }
-
+            
         });
-
+        
         return runner;
-    }
+    }    
 
     @Override
     List<Predicate<TestMethod>> getBuilderRequirements() {
diff --git a/tests/src/android/opengl/cts/GLSurfaceViewStubActivity.java b/tests/src/android/opengl/cts/GLSurfaceViewStubActivity.java
new file mode 100644
index 0000000..5a8f310
--- /dev/null
+++ b/tests/src/android/opengl/cts/GLSurfaceViewStubActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.opengl.cts;
+
+import android.app.Activity;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A minimal activity for testing {@link android.opengl.GLSurfaceView}.
+ */
+public class GLSurfaceViewStubActivity extends Activity {
+
+    private static class Renderer implements GLSurfaceView.Renderer {
+
+        public void onDrawFrame(GL10 gl) {
+            // Do nothing.
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            // Do nothing.
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            // Do nothing.
+        }
+    }
+
+    private GLSurfaceView mView;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mView = new GLSurfaceView(this);
+        mView.setRenderer(new Renderer());
+        setContentView(mView);
+    }
+
+    public GLSurfaceView getView() {
+        return mView;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
index 1d9affa5..5e06d36 100644
--- a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
@@ -1203,7 +1203,7 @@
         assertNull(mContextWrapper.getSystemService("invalid"));
 
         // Test valid service name
-        assertNotNull(mContextWrapper.getSystemService("window"));
+        assertNotNull(mContextWrapper.getSystemService(Context.WINDOW_SERVICE));
     }
 
     @TestTargetNew(
diff --git a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
index bc404fe..14462a0 100644
--- a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
+++ b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
@@ -67,6 +67,15 @@
         mTestAbstractCursor = new TestAbstractCursor(COLUMN_NAMES, list);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        mDatabase.close();
+        if (mDatabaseFile.exists()) {
+            mDatabaseFile.delete();
+        }
+        super.tearDown();
+    }
+
     @TestTargetNew(
         level = TestLevel.COMPLETE,
         method = "AbstractCursor",
diff --git a/tests/tests/database/src/android/database/cts/CursorWrapperTest.java b/tests/tests/database/src/android/database/cts/CursorWrapperTest.java
index c32cd89..507b2a3 100644
--- a/tests/tests/database/src/android/database/cts/CursorWrapperTest.java
+++ b/tests/tests/database/src/android/database/cts/CursorWrapperTest.java
@@ -19,6 +19,8 @@
 import java.io.File;
 import java.util.Arrays;
 
+import junit.framework.TestCase;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IContentProvider;
@@ -39,7 +41,7 @@
 import dalvik.annotation.TestTargetClass;
 
 @TestTargetClass(android.database.CursorWrapper.class)
-public class CursorWrapperTest extends DatabaseCursorTest {
+public class CursorWrapperTest extends TestCase {
 
     private static final String FIRST_NUMBER = "123";
     private static final String SECOND_NUMBER = "5555";
@@ -61,7 +63,6 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        setupTestType(TYPE_CURSORWRAPPER);
         setupDatabase();
     }
 
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
index 0791bcd..bc351f5 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -454,17 +454,26 @@
     })
     @ToBeFixed(bug = "1676383", explanation = "setPageSize does not work as javadoc declares.")
     public void testAccessPageSize() {
-        mDatabaseFile = new File(mDatabaseDir, "database.db");
-        if (mDatabaseFile.exists()) {
-            mDatabaseFile.delete();
+        File databaseFile = new File(mDatabaseDir, "database.db");
+        if (databaseFile.exists()) {
+            databaseFile.delete();
         }
-        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+        SQLiteDatabase database = null;
+        try {
+            database = SQLiteDatabase.openOrCreateDatabase(databaseFile.getPath(), null);
 
-        long initialValue = mDatabase.getPageSize();
-        // check that this does not throw an exception
-        // setting a different page size may not be supported after the DB has been created
-        mDatabase.setPageSize(initialValue);
-        assertEquals(initialValue, mDatabase.getPageSize());
+            long initialValue = database.getPageSize();
+            // check that this does not throw an exception
+            // setting a different page size may not be supported after the DB has been created
+            database.setPageSize(initialValue);
+            assertEquals(initialValue, database.getPageSize());
+
+        } finally {
+            if (database != null) {
+                database.close();
+                databaseFile.delete();
+            }
+        }
     }
 
     @TestTargetNew(
@@ -827,9 +836,16 @@
     public void testIsReadOnly() {
         assertFalse(mDatabase.isReadOnly());
 
-        mDatabase = SQLiteDatabase.openDatabase(mDatabaseFilePath, null,
-                SQLiteDatabase.OPEN_READONLY);
-        assertTrue(mDatabase.isReadOnly());
+        SQLiteDatabase database = null;
+        try {
+            database = SQLiteDatabase.openDatabase(mDatabaseFilePath, null,
+                    SQLiteDatabase.OPEN_READONLY);
+            assertTrue(database.isReadOnly());
+        } finally {
+            if (database != null) {
+                database.close();
+            }
+        }
     }
 
     @TestTargetNew(
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
index f62fea8..3b376fd 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
@@ -87,8 +87,7 @@
 
         new NinePatchDrawable(bmp, chunk, r, name);
 
-        // Omit for now - known failure, fixed in froyo. Bug 2219785
-        //new NinePatchDrawable(new NinePatch(bmp, chunk, name));
+        new NinePatchDrawable(new NinePatch(bmp, chunk, name));
 
         chunk = new byte[MIN_CHUNK_SIZE - 1];
         chunk[MIN_CHUNK_SIZE - 2] = 1;
diff --git a/tests/tests/graphics/src/android/opengl/cts/GLSurfaceViewTest.java b/tests/tests/graphics/src/android/opengl/cts/GLSurfaceViewTest.java
new file mode 100644
index 0000000..4f55f96
--- /dev/null
+++ b/tests/tests/graphics/src/android/opengl/cts/GLSurfaceViewTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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 android.opengl.cts;
+
+import android.opengl.GLSurfaceView;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.util.Log;
+
+import dalvik.annotation.TestTargetClass;
+
+/**
+ * Tests for the GLSurfaceView class.
+ */
+@TestTargetClass(GLSurfaceView.class)
+public class GLSurfaceViewTest extends
+        ActivityInstrumentationTestCase2<GLSurfaceViewStubActivity> {
+
+    private static final int NUM_PAUSE_RESUME_ITERATIONS_WITHOUT_DELAY = 1000;
+
+    private static final int NUM_PAUSE_RESUME_ITERATIONS_WITH_DELAY = 100;
+
+    private static final int PAUSE_RESUME_DELAY = 10;
+
+    private static final boolean LOG_PAUSE_RESUME = false;
+
+    private static final String TAG = "GLSurfaceViewTest";
+
+    private GLSurfaceViewStubActivity mActivity;
+
+    public GLSurfaceViewTest() {
+        super("com.android.cts.stub", GLSurfaceViewStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+    }
+
+    /**
+     * Test repeated pausing and resuming of a GLSurfaceView with a delay
+     * between iterations.
+     * <p>
+     * This test simply verifies that the system is able to perform multiple
+     * pause/resume sequences without crashing. The delay is used to allow
+     * asynchronous events to occur in between the pause and resume operations.
+     * </p>
+     *
+     * @throws InterruptedException
+     */
+    @UiThreadTest
+    public void testPauseResumeWithDelay() throws InterruptedException {
+        GLSurfaceView view = mActivity.getView();
+        for (int i = 0; i < NUM_PAUSE_RESUME_ITERATIONS_WITH_DELAY; i++) {
+            Thread.sleep(PAUSE_RESUME_DELAY);
+            if (LOG_PAUSE_RESUME) {
+                Log.w(TAG, "Pause/Resume (w/ delay) step " + i + " - pause");
+            }
+            view.onPause();
+            Thread.sleep(PAUSE_RESUME_DELAY);
+            if (LOG_PAUSE_RESUME) {
+                Log.w(TAG, "Pause/Resume (w/ delay) step " + i + " - resume");
+            }
+            view.onResume();
+        }
+    }
+
+    /**
+     * Test repeated pausing and resuming of a GLSurfaceView.
+     * <p>
+     * This test simply verifies that the system is able to perform multiple
+     * pause/resume sequences without crashing. No delay is used so that a
+     * larger number of iterations can be done in a short amount of time.
+     * </p>
+     */
+    @UiThreadTest
+    public void testPauseResumeWithoutDelay() {
+        GLSurfaceView view = mActivity.getView();
+        for (int i = 0; i < NUM_PAUSE_RESUME_ITERATIONS_WITHOUT_DELAY; i++) {
+            if (LOG_PAUSE_RESUME) {
+                Log.w(TAG, "Pause/Resume (no delay) step " + i + " - pause");
+            }
+            view.onPause();
+            if (LOG_PAUSE_RESUME) {
+                Log.w(TAG, "Pause/Resume (no delay) step " + i + " - resume");
+            }
+            view.onResume();
+        }
+    }
+}
diff --git a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
index 90b2bf9..9ecbf20 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -22,6 +22,8 @@
 import dalvik.annotation.TestTargets;
 
 import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.os.Looper;
 import android.os.cts.TestThread;
 import android.telephony.CellLocation;
@@ -29,6 +31,8 @@
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 
+import java.util.regex.Pattern;
+
 @TestTargetClass(TelephonyManager.class)
 public class TelephonyManagerTest extends AndroidTestCase {
     private TelephonyManager mTelephonyManager;
@@ -266,4 +270,137 @@
         mTelephonyManager.getDeviceId();
         mTelephonyManager.getDeviceSoftwareVersion();
     }
+
+    /**
+     * Tests that the device properly reports either a valid IMEI if GSM,
+     * a valid MEID if CDMA, or a valid MAC address if only a WiFi device.
+     */
+    @TestTargetNew(
+        level = TestLevel.COMPLETE,
+        method = "getDeviceId",
+        args = {}
+    )
+    public void testGetDeviceId() {
+        String deviceId = mTelephonyManager.getDeviceId();
+        int phoneType = mTelephonyManager.getPhoneType();
+        switch (phoneType) {
+            case TelephonyManager.PHONE_TYPE_GSM:
+                assertImeiDeviceId(deviceId);
+                break;
+
+            case TelephonyManager.PHONE_TYPE_CDMA:
+                assertMeidDeviceId(deviceId);
+                break;
+
+            case TelephonyManager.PHONE_TYPE_NONE:
+                assertMacAddressReported();
+                break;
+
+            default:
+                throw new IllegalArgumentException("Did you add a new phone type? " + phoneType);
+        }
+    }
+
+    private static void assertImeiDeviceId(String deviceId) {
+        assertImeiFormat(deviceId);
+        assertImeiCheckDigit(deviceId);
+        assertReportingBodyIdentifier(deviceId, true); // Must be decimal identifier
+    }
+
+    private static void assertImeiFormat(String deviceId) {
+        // IMEI must include the check digit
+        String imeiPattern = "[0-9]{15}";
+        assertTrue("IMEI device id " + deviceId + " does not match pattern " + imeiPattern,
+                Pattern.matches(imeiPattern, deviceId));
+    }
+
+    private static void assertImeiCheckDigit(String deviceId) {
+        int expectedCheckDigit = getLuhnCheckDigit(deviceId.substring(0, 14));
+        int actualCheckDigit = Character.digit(deviceId.charAt(14), 10);
+        assertEquals("Incorrect check digit for " + deviceId, expectedCheckDigit, actualCheckDigit);
+    }
+
+    /**
+     * Use decimal value (0-9) to index into array to get sum of its digits
+     * needed by Lunh check.
+     *
+     * Example: DOUBLE_DIGIT_SUM[6] = 3 because 6 * 2 = 12 => 1 + 2 = 3
+     */
+    private static final int[] DOUBLE_DIGIT_SUM = {0, 2, 4, 6, 8, 1, 3, 5, 7, 9};
+
+    /**
+     * Calculate the check digit by starting from the right, doubling every
+     * each digit, summing all the digits including the doubled ones, and
+     * finding a number to make the sum divisible by 10.
+     *
+     * @param deviceId not including the check digit
+     * @return the check digit
+     */
+    private static int getLuhnCheckDigit(String deviceId) {
+        int sum = 0;
+        int dontDoubleModulus = deviceId.length() % 2;
+        for (int i = deviceId.length() - 1; i >= 0; --i) {
+            int digit = Character.digit(deviceId.charAt(i), 10);
+            if (i % 2 == dontDoubleModulus) {
+                sum += digit;
+            } else {
+                sum += DOUBLE_DIGIT_SUM[digit];
+            }
+        }
+        sum %= 10;
+        return sum == 0 ? 0 : 10 - sum;
+    }
+
+    private static void assertReportingBodyIdentifier(String deviceId, boolean decimalIdentifier) {
+        // Check the reporting body identifier
+        int reportingBodyIdentifier = Integer.parseInt(deviceId.substring(0, 2), 16);
+        int decimalBound = 0xA0;
+        String message = String.format("%s RR %x not %s than %x",
+                decimalIdentifier ? "IMEI" : "MEID",
+                reportingBodyIdentifier,
+                decimalIdentifier ? "<" : ">=",
+                decimalBound);
+        assertEquals(message, decimalIdentifier, reportingBodyIdentifier < decimalBound);
+    }
+
+    private static void assertMeidDeviceId(String deviceId) {
+        assertHexadecimalMeidFormat(deviceId);
+        assertReportingBodyIdentifier(deviceId, false); // Must be hexadecimal identifier
+    }
+
+    private static void assertHexadecimalMeidFormat(String deviceId) {
+        // MEID must NOT include the check digit.
+        String meidPattern = "[0-9a-fA-F]{14}";
+        assertTrue("MEID hex device id " + deviceId + " does not match pattern " + meidPattern,
+                Pattern.matches(meidPattern, deviceId));
+    }
+
+    private void assertMacAddressReported() {
+        String macAddress = getMacAddress();
+        String macPattern = "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}";
+        assertTrue("MAC Address " + macAddress + " does not match pattern " + macPattern,
+                Pattern.matches(macPattern, macAddress));
+    }
+
+    /** @return mac address which requires the WiFi system to be enabled */
+    private String getMacAddress() {
+        WifiManager wifiManager = (WifiManager) getContext()
+                .getSystemService(Context.WIFI_SERVICE);
+
+        boolean enabled = wifiManager.isWifiEnabled();
+
+        try {
+            if (!enabled) {
+                wifiManager.setWifiEnabled(true);
+            }
+
+            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+            return wifiInfo.getMacAddress();
+
+        } finally {
+            if (!enabled) {
+                wifiManager.setWifiEnabled(false);
+            }
+        }
+    }
 }
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
index 64abfcf..283605d 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
@@ -20,6 +20,7 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.telephony.TelephonyManager;
@@ -28,6 +29,22 @@
 import android.view.WindowManager;
 
 public class DeviceInfoInstrument extends Instrumentation {
+
+    // Should use XML files in frameworks/base/data/etc to generate dynamically.
+    private static final String[] FEATURES_TO_CHECK = {
+        PackageManager.FEATURE_CAMERA,
+        PackageManager.FEATURE_CAMERA_AUTOFOCUS,
+        PackageManager.FEATURE_CAMERA_FLASH,
+        PackageManager.FEATURE_SENSOR_LIGHT,
+        PackageManager.FEATURE_SENSOR_PROXIMITY,
+        PackageManager.FEATURE_TELEPHONY,
+        PackageManager.FEATURE_TELEPHONY_CDMA,
+        PackageManager.FEATURE_TELEPHONY_GSM,
+        PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH,
+        PackageManager.FEATURE_LIVE_WALLPAPER,
+    };
+
+    private static final String FEATURES = "features";
     private static final String PHONE_NUMBER = "phoneNumber";
     public static final String LOCALES = "locales";
     private static final String IMSI = "imsi";
@@ -118,6 +135,10 @@
         String phoneNumber = tm.getLine1Number();
         addResult(PHONE_NUMBER, phoneNumber);
 
+        // features
+        String features = getFeatures();
+        addResult(FEATURES, features);
+
         finish(Activity.RESULT_OK, mResults);
     }
 
@@ -150,4 +171,20 @@
     private void addResult(final String key, final float value){
         mResults.putFloat(key, value);
     }
+
+    /**
+     * Return a summary of the device's feature as a semi-colon-delimited list of colon separated
+     * name and availability pairs like "feature1:true;feature2:false;feature3;true".
+     */
+    private String getFeatures() {
+        StringBuilder builder = new StringBuilder();
+
+        PackageManager packageManager = getContext().getPackageManager();
+        for (String feature : FEATURES_TO_CHECK) {
+            boolean hasFeature = packageManager.hasSystemFeature(feature);
+            builder.append(feature).append(':').append(hasFeature).append(';');
+        }
+
+        return builder.toString();
+    }
 }
diff --git a/tools/host/src/com/android/cts/TestDevice.java b/tools/host/src/com/android/cts/TestDevice.java
index 2146b1e..a53968c 100644
--- a/tools/host/src/com/android/cts/TestDevice.java
+++ b/tools/host/src/com/android/cts/TestDevice.java
@@ -416,6 +416,7 @@
         public static final String IMEI = "imei";
         public static final String IMSI = "imsi";
         public static final String PHONE_NUMBER = "phoneNumber";
+        public static final String FEATURES = "features";
 
         private HashMap<String, String> mInfoMap;
 
@@ -747,6 +748,15 @@
         public String getPhoneNumber() {
             return mInfoMap.get(PHONE_NUMBER);
         }
+
+        /**
+         * Get features.
+         *
+         * @return Features.
+         */
+        public String getFeatures() {
+            return mInfoMap.get(FEATURES);
+        }
     }
 
     /**
diff --git a/tools/host/src/com/android/cts/TestSessionLog.java b/tools/host/src/com/android/cts/TestSessionLog.java
index efe5cc9..f89150d 100644
--- a/tools/host/src/com/android/cts/TestSessionLog.java
+++ b/tools/host/src/com/android/cts/TestSessionLog.java
@@ -69,6 +69,7 @@
     static final String ATTRIBUTE_BUILD_NAME = "buildName";
     static final String ATTRIBUTE_ARCH = "arch";
     static final String ATTRIBUTE_VALUE = "value";
+    static final String ATTRIBUTE_AVAILABLE = "available";
 
     static final String ATTRIBUTE_PASS = "pass";
     static final String ATTRIBUTE_FAILED = "failed";
@@ -84,6 +85,8 @@
     static final String TAG_SUMMARY = "Summary";
     static final String TAG_SCREEN = "Screen";
     static final String TAG_BUILD_INFO = "BuildInfo";
+    static final String TAG_FEATURE_INFO = "FeatureInfo";
+    static final String TAG_FEATURE = "Feature";
     static final String TAG_PHONE_SUB_INFO = "PhoneSubInfo";
     static final String TAG_TEST_RESULT = "TestResult";
     static final String TAG_TESTPACKAGE = "TestPackage";
@@ -323,8 +326,10 @@
                         DeviceParameterCollector.BUILD_DEVICE, bldInfo.getBuildDevice());
 
                 deviceSettingNode.appendChild(devInfoNode);
+
+                addFeatureInfo(doc, deviceSettingNode, bldInfo);
             }
-            
+
             Node hostInfo = doc.createElement(TAG_HOSTINFO);
             root.appendChild(hostInfo);
             String hostName = "";
@@ -389,6 +394,39 @@
     }
 
     /**
+     * Creates a {@link #TAG_FEATURE_INFO} tag with {@link #TAG_FEATURE} elements indicating
+     * what features are supported by the device. It parses a string from the deviceInfo argument
+     * that is in the form of "feature1:true;feature2:false;featuer3;true;" with a trailing
+     * semi-colon.
+     *
+     * <pre>
+     *  <FeatureInfo>
+     *     <Feature name="android.name.of.feature" available="true" />
+     *     ...
+     *   </FeatureInfo>
+     * </pre>
+     * @param document used to create elements
+     * @param parentNode to attach the FeatureInfo element to
+     * @param deviceInfo to get the feature data from
+     */
+    private void addFeatureInfo(Document document, Node parentNode,
+            DeviceParameterCollector deviceInfo) {
+        Node featureInfo = document.createElement(TAG_FEATURE_INFO);
+        parentNode.appendChild(featureInfo);
+
+        String features = deviceInfo.getFeatures();
+        String[] featurePairs = features.split(";");
+        for (String featurePair : featurePairs) {
+            Node feature = document.createElement(TAG_FEATURE);
+            featureInfo.appendChild(feature);
+
+            String[] nameAndAvailability = featurePair.split(":");
+            setAttribute(document, feature, ATTRIBUTE_NAME, nameAndAvailability[0]);
+            setAttribute(document, feature, ATTRIBUTE_AVAILABLE, nameAndAvailability[1]);
+        }
+    }
+
+    /**
      * Output TestSuite and result to XML DOM Document.
      *
      * @param doc The document.
diff --git a/tools/host/src/com/android/cts/Version.java b/tools/host/src/com/android/cts/Version.java
index b3f6766..6477b1d 100644
--- a/tools/host/src/com/android/cts/Version.java
+++ b/tools/host/src/com/android/cts/Version.java
@@ -18,7 +18,7 @@
 
 public class Version {
     // The CTS version string
-    private static final String version = "2.1_r1";
+    private static final String version = "2.1_r2";
     
     private Version() {
         // no instances allowed
diff --git a/tools/host/src/res/cts_result.css b/tools/host/src/res/cts_result.css
index 869c4dd..735fd8b 100644
--- a/tools/host/src/res/cts_result.css
+++ b/tools/host/src/res/cts_result.css
@@ -79,6 +79,7 @@
 #summaryinfo td {
     padding:1px;
     border-width: 0px 0px 0px 0px;
+    vertical-align: top;
 }
 
 /* The test summary */
diff --git a/tools/host/src/res/cts_result.xsl b/tools/host/src/res/cts_result.xsl
index 0ff7e93..0846d91 100644
--- a/tools/host/src/res/cts_result.xsl
+++ b/tools/host/src/res/cts_result.xsl
@@ -102,7 +102,9 @@
                                     <TR>
                                         <TD class="rowtitle">Supported Locales</TD>
                                         <TD>
-                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@locales"/>
+                                            <xsl:call-template name="formatDelimitedString">
+                                                <xsl:with-param name="string" select="TestResult/DeviceInfo/BuildInfo/@locales"/>
+                                            </xsl:call-template>
                                         </TD>
                                     </TR>
                                     <TR>
@@ -165,6 +167,26 @@
                                             <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@imsi"/>
                                         </TD>
                                     </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Features</TD>
+                                        <TD>
+                                            <xsl:for-each select="TestResult/DeviceInfo/FeatureInfo/Feature">
+                                                <xsl:text>[</xsl:text>
+                                                <xsl:choose>
+                                                    <xsl:when test="@available = 'true'">
+                                                        <xsl:text>X</xsl:text>
+                                                    </xsl:when>
+                                                    <xsl:otherwise>
+                                                        <xsl:text>_</xsl:text>
+                                                    </xsl:otherwise>
+                                                </xsl:choose>
+                                                <xsl:text>] </xsl:text>
+
+                                                <xsl:value-of select="@name" />
+                                                <br />
+                                            </xsl:for-each>
+                                        </TD>
+                                    </TR>
                                 </TABLE>
                             </div>
                         </TD>
@@ -403,10 +425,31 @@
                     </TABLE>
                 </xsl:for-each> <!-- end test package -->
             </DIV>
+            </body>
+        </html>
+    </xsl:template>
 
-        </body>
-    </html>
-</xsl:template>
+    <!-- Take a delimited string and insert line breaks after a some number of elements. --> 
+    <xsl:template name="formatDelimitedString">
+        <xsl:param name="string" />
+        <xsl:param name="numTokensPerRow" select="10" />
+        <xsl:param name="tokenIndex" select="1" />
+        <xsl:if test="$string">
+            <!-- Requires the last element to also have a delimiter after it. -->
+            <xsl:variable name="token" select="substring-before($string, ';')" />
+            <xsl:value-of select="$token" />
+            <xsl:text>&#160;</xsl:text>
+          
+            <xsl:if test="$tokenIndex mod $numTokensPerRow = 0">
+                <br />
+            </xsl:if>
 
+            <xsl:call-template name="formatDelimitedString">
+                <xsl:with-param name="string" select="substring-after($string, ';')" />
+                <xsl:with-param name="numTokensPerRow" select="$numTokensPerRow" />
+                <xsl:with-param name="tokenIndex" select="$tokenIndex + 1" />
+            </xsl:call-template>
+        </xsl:if>
+    </xsl:template>
 
 </xsl:stylesheet>